tianyi 1 week ago
commit
d1993be0f6
100 changed files with 31722 additions and 0 deletions
  1. 569 0
      App.vue
  2. 690 0
      TrtcCloud/lib/TrtcCloudImpl.js
  3. 612 0
      TrtcCloud/lib/TrtcCode.js
  4. 454 0
      TrtcCloud/lib/TrtcDefines.js
  5. 10 0
      TrtcCloud/lib/constants.js
  6. 944 0
      TrtcCloud/lib/index.js
  7. 279 0
      TrtcCloud/permission.js
  8. 22 0
      TrtcCloud/view/TrtcLocalView.nvue
  9. 28 0
      TrtcCloud/view/TrtcRemoteView.nvue
  10. 87 0
      common/cache.js
  11. 29 0
      common/config.js
  12. 313 0
      common/httpRequest.js
  13. 200 0
      common/queue.js
  14. BIN
      components/.DS_Store
  15. 350 0
      components/btnPopous/btnPopous.vue
  16. 409 0
      components/city-select/city-select.vue
  17. 2 0
      components/city-select/citySelect.js
  18. BIN
      components/colorui/.DS_Store
  19. 184 0
      components/colorui/animation.css
  20. 65 0
      components/colorui/components/cu-custom.vue
  21. 36 0
      components/colorui/icon.css
  22. 3921 0
      components/colorui/main.css
  23. 312 0
      components/com-input.vue
  24. 180 0
      components/companyListIndex/companyListIndex.vue
  25. 74 0
      components/empty.vue
  26. 74 0
      components/emptys.vue
  27. 141 0
      components/home-list/homeList.vue
  28. 114 0
      components/home-list/homeuserList.vue
  29. 19 0
      components/mescroll-uni/components/mescroll-body/mescroll-body.css
  30. 422 0
      components/mescroll-uni/components/mescroll-body/mescroll-body.vue
  31. 55 0
      components/mescroll-uni/components/mescroll-uni/components/mescroll-down.css
  32. 47 0
      components/mescroll-uni/components/mescroll-uni/components/mescroll-down.vue
  33. 83 0
      components/mescroll-uni/components/mescroll-uni/components/mescroll-top.vue
  34. 47 0
      components/mescroll-uni/components/mescroll-uni/components/mescroll-up.css
  35. 39 0
      components/mescroll-uni/components/mescroll-uni/components/mescroll-up.vue
  36. 15 0
      components/mescroll-uni/components/mescroll-uni/mescroll-i18n.js
  37. 57 0
      components/mescroll-uni/components/mescroll-uni/mescroll-mixins.js
  38. 64 0
      components/mescroll-uni/components/mescroll-uni/mescroll-uni-option.js
  39. 36 0
      components/mescroll-uni/components/mescroll-uni/mescroll-uni.css
  40. 799 0
      components/mescroll-uni/components/mescroll-uni/mescroll-uni.js
  41. 477 0
      components/mescroll-uni/components/mescroll-uni/mescroll-uni.vue
  42. 47 0
      components/mescroll-uni/components/mescroll-uni/mixins/mescroll-comp.js
  43. 66 0
      components/mescroll-uni/components/mescroll-uni/mixins/mescroll-more-item.js
  44. 74 0
      components/mescroll-uni/components/mescroll-uni/mixins/mescroll-more.js
  45. 109 0
      components/mescroll-uni/components/mescroll-uni/wxs/mixins.js
  46. 92 0
      components/mescroll-uni/components/mescroll-uni/wxs/renderjs.js
  47. 268 0
      components/mescroll-uni/components/mescroll-uni/wxs/wxs.wxs
  48. 78 0
      components/mescroll-uni/good-list/good-list.vue
  49. 66 0
      components/mescroll-uni/good-list/orderList.vue
  50. 49 0
      components/mescroll-uni/good-list/publishList.vue
  51. 208 0
      components/mescroll-uni/me-tabs/me-tabs.vue
  52. 80 0
      components/mescroll-uni/package.json
  53. 240 0
      components/phone-directory/README.md
  54. 34 0
      components/phone-directory/pages/phones/phone-search.vue
  55. 727 0
      components/phone-directory/pages/phones/phones.vue
  56. 120 0
      components/phone-directory/phone-alphabet.vue
  57. 126 0
      components/phone-directory/phone-directory.vue
  58. 152 0
      components/phone-directory/phone-list.vue
  59. 114 0
      components/phone-directory/phone-search-list.vue
  60. 278 0
      components/ren-dropdown-filter/ren-dropdown-filter.vue
  61. 260 0
      components/ren-dropdown-filter/ren-dropdown-filters.vue
  62. 274 0
      components/ren-dropdown-filters/ren-dropdown-filter.vue
  63. 131 0
      components/smh-timer/smh-timer.vue
  64. 1206 0
      components/tki-qrcode/qrcode.js
  65. 204 0
      components/tki-qrcode/tki-qrcode.vue
  66. 132 0
      components/uni-icons/icons.js
  67. 10 0
      components/uni-icons/uni-icons.vue
  68. 194 0
      components/uni-load-more/uni-load-more.vue
  69. 139 0
      components/uni-section/uni-section.vue
  70. 273 0
      components/wm-poster/wm-poster.vue
  71. 248 0
      components/wm-poster/wm-posterorders.vue
  72. 261 0
      components/wm-poster/wm-posters.vue
  73. BIN
      file/taobao.jks
  74. BIN
      file/测试证书/task.mobileprovision
  75. BIN
      file/测试证书/证书.p12
  76. 1009 0
      js_sdk/QuShe-SharerPoster/QS-SharePoster/QRCodeAlg.js
  77. 1322 0
      js_sdk/QuShe-SharerPoster/QS-SharePoster/QS-SharePoster.js
  78. 561 0
      js_sdk/QuShe-SharerPoster/QS-SharePoster/app.js
  79. 147 0
      js_sdk/QuShe-SharerPoster/QS-SharePoster/image-tools.js
  80. 1382 0
      js_sdk/Sansnn-uQRCode/uqrcode.js
  81. 461 0
      js_sdk/ican-H5Api/ican-H5Api.js
  82. 272 0
      js_sdk/wa-permission/permission.js
  83. 30 0
      main.js
  84. 209 0
      manifest.json
  85. BIN
      my/.DS_Store
  86. 307 0
      my/address/Endaddress.vue
  87. 260 0
      my/address/address.vue
  88. 56 0
      my/components/jc-record/jc-record.md
  89. 390 0
      my/components/jc-record/jc-record.vue
  90. 4933 0
      my/components/wangding-pickerAddress/data.js
  91. 103 0
      my/components/wangding-pickerAddress/wangding-pickerAddress.vue
  92. 36 0
      my/components/watch-login/css/icon.css
  93. 108 0
      my/components/watch-login/watch-button.vue
  94. 223 0
      my/components/watch-login/watch-input.vue
  95. 394 0
      my/enterpriseInfo/enterpriseInfo.vue
  96. 189 0
      my/feedback/index.vue
  97. 280 0
      my/feedback/jubao.vue
  98. 274 0
      my/gird/browse.vue
  99. 148 0
      my/gird/guanzhu.vue
  100. 109 0
      my/gird/visitor.vue

+ 569 - 0
App.vue

@@ -0,0 +1,569 @@
+<script>
+	export default {
+		onLaunch: function() {
+			let that = this
+			// #ifdef APP || H5
+			uni.setStorageSync('isDial', false)
+			setInterval(() => {
+				// #ifdef H5 || APP
+				let userId = uni.getStorageSync('userId')
+				if (uni.getStorageSync('token')) {
+					that.$Request.get('/app/chat/selectUserChatVideo').then(res => {
+						if (res.code == 0 && res.data) {
+							// videoStatus 状态  1是发起 2是通话中 3是已取消 4是已拒绝 5是已结束  不过他只会返回1和2
+							if (res.data.videoStatus) {
+								console.log(res.data)
+								uni.setStorageSync('videoStatus', res.data.videoStatus)
+								uni.setStorageSync('messageType', res.data.messageType) //4视频通话 5语音通话
+								if (res.data.videoStatus == 1) {
+									console.log(uni.getStorageSync('isDial'))
+									if (!uni.getStorageSync('isDial')) {
+										uni.setStorageSync('isDial',
+											true) //跳转通话页面  做标识  true 已跳转 false 未跳转
+										uni.navigateTo({
+											url: '/my/videoVoice/videoVoice?byUserId=' +
+												res.data.userId +
+												'&chatContentId=' + res.data.chatContentId +
+												'&isRol=2&messageType=' + res.data
+												.messageType + '&chatConversationId=' + res
+												.data.chatConversationId + '&postPushId=' + res
+												.data
+												.postPushId + '&resumesId=' + res.data
+												.resumesId + '&userType=' + res.data.userType
+										})
+									}
+								}
+							}
+
+						} else {
+							uni.setStorageSync('videoStatus', 4)
+						}
+					})
+				}
+				// #endif
+
+
+			}, 5000)
+			// #endif
+			// #ifdef MP-WEIXIN
+			if (!uni.getStorageSync('token')) {
+				uni.login({
+					provider: 'weixin',
+					success: function(loginRes) {
+						console.log(loginRes, '************')
+						let data = {
+							code: loginRes.code,
+						}
+						that.$Request.get('/app/Login/wxLogin', data).then(res => {
+							if (res.code == 0) {
+								uni.setStorageSync('openId', res.data.open_id)
+								uni.setStorageSync('unionId', res.data.unionId)
+								uni.setStorageSync('sessionkey', res.data.session_key)
+								let inviterCode = '';
+								if (uni.getStorageSync('inviterCode')) {
+									inviterCode = uni.getStorageSync('inviterCode')
+								}
+								let sendData = {
+									openId: uni.getStorageSync('openId'),
+									unionId: uni.getStorageSync('unionId'),
+									userName: '游客',
+									avatar: '',
+									sex: '1', //性别
+									inviterCode: inviterCode //别人登录进来携带你的邀请码
+								};
+								let phoneNum = false
+								uni.setStorageSync('sendDataList', sendData)
+								that.$Request.getT('/app/common/type/188').then(ret => {
+									if (ret.code == 0) {
+										if (ret.data && ret.data.value && ret.data.value ==
+											'是') {
+											phoneNum = true;
+											let flag = res.data.flag;
+											if (flag == '2' && phoneNum) { //需要授权手机号则走手机号授权
+												uni.setStorageSync('weixinPhone', true)
+											} else { //不需要手机号则则直接走授权登录
+												uni.setStorageSync('weixinPhone', false)
+											}
+										} else {
+											uni.setStorageSync('weixinPhone', false)
+										}
+									}
+								});
+
+							}
+						})
+
+					}
+				});
+			}
+			// #endif
+
+			setInterval(d => { //定时器,定时去调取聊天未读消息
+				let userId = uni.getStorageSync('userId')
+				if (userId) {
+					this.$Request.get('/app/chat/selectChatCount').then(res => {
+						if (res.code === 0) {
+							let chatCount = res.data.chatCount
+							let messageCount = res.data.messageCount
+
+							uni.setStorageSync('messageCount', messageCount)
+
+							let num = chatCount + messageCount
+							if (num == 0) {
+								uni.removeTabBarBadge({
+									index: 1
+								})
+								return;
+							}
+							uni.setTabBarBadge({
+								index: 1,
+								text: num + ""
+							})
+
+						}
+					});
+				}
+			}, 6000);
+
+			//#ifdef APP-PLUS
+			// APP检测更新 具体打包流程可以参考:https://ask.dcloud.net.cn/article/35667
+			plus.screen.lockOrientation('portrait-primary'); //竖屏正方向锁定
+			//获取是否热更新过
+			const updated = uni.getStorageSync('updated'); // 尝试读取storage
+
+			if (updated.completed === true) {
+				// 如果上次刚更新过
+				// 删除安装包及安装记录
+				console.log('安装记录被删除,更新成功');
+				uni.removeSavedFile({
+					filePath: updated.packgePath,
+					success: res => {
+						uni.removeStorageSync('updated');
+					}
+				});
+			} else if (updated.completed === false) {
+				uni.removeStorageSync('updated');
+				plus.runtime.install(updated.packgePath, {
+					force: true
+				});
+				uni.setStorage({
+					key: 'updated',
+					data: {
+						completed: true,
+						packgePath: updated.packgePath
+					},
+					success: res => {
+						console.log('成功安装上次的更新,应用需要重启才能继续完成');
+					}
+				});
+				uni.showModal({
+					title: '温馨提示',
+					content: '应用将重启以完成更新',
+					showCancel: false,
+					complete: () => {
+						plus.runtime.restart();
+					}
+				});
+			} else {
+				//获取当前系统版本信息
+				plus.runtime.getProperty(plus.runtime.appid, widgetInfo => {
+					//请求后台接口 解析数据 对比版本
+					that.$Request.getT('/app/user/selectNewApp').then(res => {
+						res = res.data[0];
+						if (res.wgtUrl && widgetInfo.version < res.version) {
+							let downloadLink = '';
+							let androidLink = res.androidWgtUrl;
+							let iosLink = res.iosWgtUrl;
+							let ready = false;
+							//校验是是不是热更新
+							if (res.wgtUrl.match(RegExp(/.wgt/))) {
+								// 判断系统类型
+								if (plus.os.name.toLowerCase() === 'android') {
+									console.log('安卓系统');
+									if (androidLink && androidLink !== '#') {
+										// 我这里默认#也是没有地址,请根据业务自行修改
+										console.log('发现下载地址');
+										// 安卓:创建下载任务
+										if (androidLink.match(RegExp(/.wgt/))) {
+											console.log('确认wgt热更新包');
+											downloadLink = androidLink;
+											ready = true;
+										} else {
+											console.log('安卓推荐.wgt强制更新,.apk的强制更新请您自行修改程序');
+										}
+									} else {
+										console.log('下载地址是空的,无法继续');
+									}
+								} else {
+									console.log('苹果系统');
+									if (iosLink && iosLink !== '#') {
+										// 我这里默认#也是没有地址,请根据业务自行修改
+										console.log('发现下载地址');
+										// 苹果(A):进行热更新(如果iosLink是wgt更新包的下载地址)判断文件名中是否含有.wgt
+										if (iosLink.match(RegExp(/.wgt/))) {
+											console.log('确认wgt热更新包');
+											downloadLink = iosLink;
+											ready = true;
+										} else {
+											console.log('苹果只支持.wgt强制更新');
+										}
+									} else {
+										console.log('下载地址是空的,无法继续');
+									}
+								}
+								if (ready) {
+									console.log('任务开始');
+									let downloadTask = uni.downloadFile({
+										url: downloadLink,
+										success: res => {
+											if (res.statusCode === 200) {
+												// 保存下载的安装包
+												console.log('保存安装包');
+												uni.saveFile({
+													tempFilePath: res.tempFilePath,
+													success: res => {
+														const packgePath = res
+															.savedFilePath;
+														// 保存更新记录到stroage,下次启动app时安装更新
+														uni.setStorage({
+															key: 'updated',
+															data: {
+																completed: false,
+																packgePath: packgePath
+															},
+															success: () => {
+																console
+																	.log(
+																		'成功保存记录'
+																	);
+															}
+														});
+														// 任务完成,关闭下载任务
+														console.log(
+															'任务完成,关闭下载任务,下一次启动应用时将安装更新'
+														);
+														downloadTask.abort();
+														downloadTask = null;
+													}
+												});
+											}
+										}
+									});
+								} else {
+									console.log('下载地址未准备,无法开启下载任务');
+								}
+							} else {
+								//不是热更新是在线更新 校验是否强制升级
+								if (res.method == 'true') {
+									uni.showModal({
+										showCancel: false,
+										confirmText: '立即更新',
+										title: '发现新版本',
+										content: res.des,
+										success: res => {
+											if (res.confirm) {
+												that.$queue.showLoading('下载中...');
+												if (uni.getSystemInfoSync().platform ==
+													'android') {
+													uni.downloadFile({
+														url: androidLink,
+														success: downloadResult => {
+															if (downloadResult
+																.statusCode ===
+																200) {
+																plus.runtime
+																	.install(
+																		downloadResult
+																		.tempFilePath, {
+																			force: false
+																		},
+																		d => {
+																			console
+																				.log(
+																					'install success...'
+																				);
+																			plus.runtime
+																				.restart();
+																		},
+																		e => {
+																			console
+																				.error(
+																					'install fail...'
+																				);
+																		}
+																	);
+															}
+														}
+													});
+												}
+												if (uni.getSystemInfoSync().platform ==
+													'ios') {
+													plus.runtime.openURL(iosLink, function(
+														res) {});
+												}
+											} else if (res.cancel) {
+												console.log('取消');
+											}
+										}
+									});
+								} else {
+									uni.showModal({
+										title: '发现新版本',
+										confirmText: '立即更新',
+										cancelText: '下次更新',
+										content: res.des,
+										success: res => {
+											if (res.confirm) {
+												that.$queue.showLoading('下载中...');
+												if (uni.getSystemInfoSync().platform ==
+													'android') {
+													uni.downloadFile({
+														url: androidLink,
+														success: downloadResult => {
+															if (downloadResult
+																.statusCode ===
+																200) {
+																plus.runtime
+																	.install(
+																		downloadResult
+																		.tempFilePath, {
+																			force: false
+																		},
+																		d => {
+																			console
+																				.log(
+																					'install success...'
+																				);
+																			plus.runtime
+																				.restart();
+																		},
+																		e => {
+																			console
+																				.error(
+																					'install fail...'
+																				);
+																		}
+																	);
+															}
+														}
+													});
+												}
+												if (uni.getSystemInfoSync().platform ==
+													'ios') {
+													plus.runtime.openURL(iosLink, function(
+														res) {});
+												}
+											} else if (res.cancel) {
+												console.log('取消');
+											}
+										}
+									});
+								}
+							}
+						}
+					});
+				});
+			}
+
+			//#endif
+		},
+		onShow: function() {
+			//关键词过滤  602
+			this.$Request.getT('/app/common/type/602').then(res => {
+				if (res.code == 0) {
+					if (res.data && res.data.value) {
+						this.$queue.setData('chatSearchKeys', res.data.value)
+					}
+				}
+			});
+			// 小程序是否上线
+			// this.$Request.get('/app/common/type/238').then(res => {
+			// 	if (res.code == 0) {
+			// 		// #ifdef MP-WEIXIN
+			// 		this.$queue.setData('XCXIsSelect', res.data.value);
+			// 		// #endif
+			// 		// #ifndef MP-WEIXIN
+			// 		this.$queue.setData('XCXIsSelect', '是');
+			// 		// #endif
+			// 	}
+			// });
+			this.$Request.get('/app/common/type/257').then(res => {
+				if (res.code == 0) {
+					// #ifdef MP-WEIXIN
+					this.$queue.setData('XCXIsSelect', res.data.value);
+					// #endif
+					// #ifndef MP-WEIXIN
+					this.$queue.setData('XCXIsSelect', '是');
+					// #endif
+				}
+			});
+			//用户客服
+			// 企业微信链接 274
+			this.$Request.get('/app/common/type/322').then(res => {
+				if (res.code == 0) {
+					this.$queue.setData('kefu', res.data.value);
+				}
+			});
+			// 企业微信客服APPID 275
+			this.$Request.get('/app/common/type/324').then(res => {
+				if (res.code == 0) {
+					this.$queue.setData('kefuAppid', res.data.value);
+				}
+			});
+			//企业端客服
+			this.$Request.get('/app/common/type/321').then(res => {
+				if (res.code == 0) {
+					this.$queue.setData('kefuq', res.data.value);
+				}
+			});
+			// 企业微信客服APPID 275
+			this.$Request.get('/app/common/type/323').then(res => {
+				if (res.code == 0) {
+					this.$queue.setData('kefuAppidq', res.data.value);
+				}
+			});
+			// 企业会员每天免费联系次数 421
+			this.$Request.get('/app/common/type/421').then(res => {
+				if (res.code == 0) {
+					this.$queue.setData('vipMsgNum', res.data.value);
+				}
+			});
+			// 企业非会员每天免费联系次数 422
+			this.$Request.get('/app/common/type/422').then(res => {
+				if (res.code == 0) {
+					this.$queue.setData('msgNum', res.data.value);
+				}
+			});
+			// 企业会员每次联系价格 419
+			this.$Request.get('/app/common/type/419').then(res => {
+				if (res.code == 0) {
+					this.$queue.setData('vipMsgPrice', res.data.value);
+				}
+			});
+			// 企业非会员每次联系价格 420
+			this.$Request.get('/app/common/type/420').then(res => {
+				if (res.code == 0) {
+					this.$queue.setData('msgPrice', res.data.value);
+				}
+			});
+			//腾讯云实时语音SDKAppID 
+			this.$Request.get('/app/common/type/814').then(res => {
+				if (res.code == 0) {
+					uni.setStorageSync('sdkAppId', res.data.value)
+				}
+			});
+			console.log('App Show')
+			// #ifdef H5
+			// let sysTem = uni.getSystemInfoSync()
+			// if (sysTem.model == 'PC') { //如果是pc访问移动端域名
+			// 	this.$Request.get('/app/common/type/432').then(res => {
+			// 		if (res.code == 0) {
+			// 			if (res.data.value == '是') {
+			// 				this.$Request.get('/app/common/type/433').then(ree => {
+			// 					if (ree.code == 0) {
+			// 						if (ree.data.value) {
+			// 							window.location.href = ree.data.value;
+			// 						}
+			// 					}
+			// 				})
+			// 			}
+			// 		}
+			// 	})
+			// }
+			// #endif
+			// #ifdef APP-PLUS
+
+			if (uni.getSystemInfoSync().platform == 'android') {
+				let clientid = plus.push.getClientInfo().clientid;
+
+				let userId = this.$queue.getData('userId');
+				if (userId) {
+					this.$Request.postT('/app/user/updateClientId?clientId=' + clientid + '&userId=' + userId).then(
+						res => {});
+				}
+			}
+
+			//#endif
+			//#ifdef H5
+			let isopen = false
+			this.$Request.get('/app/common/type/237').then(res => {
+				if (res.data.value == '是') {
+					isopen = true
+				}
+			});
+			if (isopen) {
+				let ua = navigator.userAgent.toLowerCase();
+				if (ua.indexOf('micromessenger') !== -1) {
+					let openid = uni.getStorageSync('openid');
+					let userId = uni.getStorageSync('userId');
+					let that = this;
+					if (!openid) {
+						if (window.location.href.indexOf('?code=') !== -1 || window.location.href.indexOf('&code=') !==
+							-1) {
+							let code;
+							if (window.location.href.indexOf('?code=') !== -1) {
+								code = window.location.href.split('?code=')[1].split('&')[0];
+							} else {
+								code = window.location.href.split('&code=')[1].split('&')[0];
+							}
+							this.$Request.get('/app/Login/getOpenId?code=' + code).then(ret => {
+								uni.setStorageSync('openId', ret.data)
+
+								this.$Request.get('/app/Login/openid/login?openId=' + ret.data).then(res => {
+
+									this.$queue.setData("userId", res.user.userId);
+									this.$queue.setData("token", res.token);
+									this.$queue.setData("phone", res.user.phone);
+									this.$queue.setData("userName", res.user.userName);
+									this.$queue.setData("avatar", res.user.avatar);
+									this.$queue.setData("invitationCode", res.user.invitationCode);
+									this.$queue.setData("inviterCode", res.user.inviterCode);
+								});
+							});
+						} else {
+							this.$Request.get('/app/common/type/108').then(res => {
+								if (res.data.value == '是') {
+									window.location.href =
+										'https://open.weixin.qq.com/connect/oauth2/authorize?appid=' +
+										that.$queue.getWxAppid() +
+										'&redirect_uri=' +
+										window.location.href.split('#')[0] +
+										'&response_type=code&scope=snsapi_userinfo#wechat_redirect';
+								}
+							});
+						}
+					}
+					if (userId && openid) {
+						this.$Request.get('/app/Login/bindOpenId?userId=' + userId + '&openId=' + openid).then(res => {
+							// 省钱兄陪玩 https://pw.xianmxkj.com
+						});
+					}
+				}
+			}
+
+
+			//#endif
+		},
+		onHide: function() {
+			console.log('App Hide')
+		}
+	}
+</script>
+
+<style lang="scss">
+	/*每个页面公共css */
+	@import "uview-ui/index.scss";
+	@import 'components/colorui/main.css';
+	@import 'components/colorui/icon.css';
+
+
+
+	page {
+		background-color: #FFFFFF;
+		color: #343546;
+	}
+
+	.bg {
+		background-color: #F7F7F7;
+	}
+</style>

+ 690 - 0
TrtcCloud/lib/TrtcCloudImpl.js

@@ -0,0 +1,690 @@
+var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
+    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
+    return new (P || (P = Promise))(function (resolve, reject) {
+        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
+        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
+        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
+        step((generator = generator.apply(thisArg, _arguments || [])).next());
+    });
+};
+import { NAME } from './constants';
+import { TRTCRoleType, TRTCAudioQuality, TRTCVideoRotation, TRTCVideoFillMode, TRTCVideoMirrorType, TRTCVideoStreamType, TRTCVideoEncParam, TRTCAppScene, TRTCAudioRoute, TRTCBeautyStyle, } from './TrtcDefines';
+import TrtcError, { TXLiteJSError, generateError_ } from './TrtcCode';
+const TrtcNativeTrtcCloudModule = uni.requireNativePlugin('TRTCCloudUniPlugin-TRTCCloudImpl');
+const TXAudioEffectManagerModule = uni.requireNativePlugin('TRTCCloudUniPlugin-TRTCCloudImpl-TXAudioEffectManagerModule');
+const TrtcEvent = uni.requireNativePlugin('globalEvent');
+let trtcCloud = null; // trtcCloud 单例
+export default class TrtcCloudImpl {
+    constructor() {
+        this.listenersMap_ = new Map();
+    }
+    static _createInstance() {
+        try {
+            if (trtcCloud) {
+                return trtcCloud;
+            }
+            TrtcNativeTrtcCloudModule.sharedInstance();
+            trtcCloud = new TrtcCloudImpl();
+            return trtcCloud;
+        }
+        catch (error) {
+            throw generateError_(error);
+        }
+    }
+    static _getInstance() {
+        if (trtcCloud) {
+            return trtcCloud;
+        }
+        throw new TrtcError({
+            code: TXLiteJSError.INVALID_OPERATION,
+            message: 'get trtcCloud failed, please create trtcCloud first',
+        });
+    }
+    static _destroyInstance() {
+        try {
+            trtcCloud = null;
+            TrtcNativeTrtcCloudModule.destroySharedInstance();
+        }
+        catch (error) {
+            throw new TrtcError({
+                code: error.code || TXLiteJSError.UNKNOWN,
+                message: error.message,
+                name: error.name,
+            });
+        }
+    }
+    // 截图保存
+    // async saveImage_(base64Data) {
+    //   return new Promise((resolve, reject) => {
+    //     let bitmap = new plus.nativeObj.Bitmap();
+    //     bitmap.loadBase64Data(base64Data, () => {
+    //       const url = "_doc/" + new Date().getTime() + ".png";  // url为时间戳命名方式
+    //       console.log('saveHeadImgFile', url);
+    //       bitmap.save(url, { overwrite: true }, (i) => {
+    //         uni.saveImageToPhotosAlbum({
+    //           filePath: url,
+    //           success: function() {
+    //             uni.showToast({
+    //               title: '图片保存成功',
+    //               icon: 'none'
+    //             })
+    //             bitmap.clear();
+    //             resolve({ code: 0, message: '图片保存成功' });
+    //           }
+    //         });
+    //       }, (e) => {
+    //         uni.showToast({
+    //           title: '图片保存失败, 请重新截图',
+    //           icon: 'none'
+    //         })
+    //         bitmap.clear();
+    //         resolve({ code: -1, message: '图片保存失败, 请重新截图' });
+    //       });
+    //     });
+    //   });
+    // }
+    on(event, callback) {
+        if (typeof event !== NAME.STRING || typeof callback !== NAME.FUNCTION) {
+            throw new TrtcError({
+                code: TXLiteJSError.INVALID_PARAMETER,
+                message: `${NAME.LOG_PREFIX} please check the on method parameter types. event type is a ${typeof event}; callback type is a ${typeof callback}`,
+            });
+        }
+        const nativeListener = (res) => __awaiter(this, void 0, void 0, function* () {
+            const { data = [] } = res;
+            const code = data[0];
+            const message = data[1] || '';
+            const extraInfo = data[2] || {};
+            switch (event) {
+                case 'onEnterRoom': {
+                    const result = code;
+                    callback(result);
+                    break;
+                }
+                case 'onExitRoom': {
+                    const reason = code;
+                    callback(reason);
+                    break;
+                }
+                case 'onFirstVideoFrame': {
+                    const userId = code;
+                    const streamType = data[1] || 0;
+                    const width = data[2] || 0;
+                    const height = data[3] || 0;
+                    callback({ userId, streamType, width, height });
+                    break;
+                }
+                case 'onFirstAudioFrame': {
+                    const userId = code || '';
+                    callback(userId);
+                    break;
+                }
+                case 'onMicDidReady': {
+                    callback();
+                    break;
+                }
+                case 'onCameraDidReady': {
+                    callback();
+                    break;
+                }
+                case 'onNetworkQuality': {
+                    const localQuality = data[0];
+                    const remoteQuality = data[1];
+                    callback({ localQuality, remoteQuality });
+                    break;
+                }
+                case 'onRemoteUserEnterRoom': {
+                    const userId = code || '';
+                    callback(userId);
+                    break;
+                }
+                case 'onRemoteUserLeaveRoom': {
+                    const userId = code || '';
+                    const reason = message;
+                    callback({ userId, reason });
+                    break;
+                }
+                case 'onSendFirstLocalAudioFrame': {
+                    callback();
+                    break;
+                }
+                case 'onSendFirstLocalVideoFrame': {
+                    const streamType = code;
+                    callback(streamType);
+                    break;
+                }
+                case 'onStatistics': {
+                    const statics = data[0] || {};
+                    callback(statics);
+                    break;
+                }
+                case 'onUserAudioAvailable': {
+                    const userId = code || '';
+                    const available = message;
+                    callback({ userId, available });
+                    break;
+                }
+                case 'onUserVideoAvailable': {
+                    const userId = code || '';
+                    const available = message;
+                    callback({ userId, available });
+                    break;
+                }
+                case 'onUserVoiceVolume': {
+                    const userVolumes = data[0];
+                    const totalVolume = data[1];
+                    callback({ userVolumes, totalVolume });
+                    break;
+                }
+                case 'onSwitchRole': {
+                    callback({ code, message });
+                    break;
+                }
+                case 'onScreenCaptureStarted': {
+                    callback({ code, message });
+                    break;
+                }
+                case 'onScreenCapturePaused': {
+                    callback({ code, message });
+                    break;
+                }
+                case 'onScreenCaptureResumed': {
+                    callback({ code, message });
+                    break;
+                }
+                case 'onScreenCaptureStopped': {
+                    callback({ code, message });
+                    break;
+                }
+                case 'onUserSubStreamAvailable': {
+                    const userId = code || '';
+                    const available = message;
+                    callback({ userId, available });
+                    break;
+                }
+                case 'onSnapshotComplete': {
+                    // base64 直接保存到本地图库
+                    // const { code: snapShotCode, message: msg } = await this.saveImage_(code);
+                    // callback({ snapShotCode, message: msg });
+                    callback({ base64Data: code, message });
+                    break;
+                }
+                case 'onUserVideoSizeChanged': {
+                    callback(data);
+                    break;
+                }
+                case 'onStart': {
+                    callback({ id: code, errCode: message });
+                    break;
+                }
+                case 'onPlayProgress': {
+                    callback({ id: code, curPtsMS: message, durationMS: extraInfo });
+                    break;
+                }
+                case 'onComplete': {
+                    callback({ id: code, errCode: message });
+                    break;
+                }
+                case 'onConnectOtherRoom': {
+                    // 拿不到 userid, 为了和 native 参数保持一致,所以空字符串代替
+                    callback({ userId: '', errCode: code, errMsg: message });
+                    break;
+                }
+                case 'onDisconnectOtherRoom': {
+                    callback({ errCode: code, errMsg: message });
+                    break;
+                }
+                case 'onError': {
+                    console.error(`onError: ${code}, ${message}, ${extraInfo}`);
+                    callback(generateError_({ message }, code, extraInfo));
+                    break;
+                }
+                default: {
+                    callback({ code, message, extraInfo });
+                }
+            }
+        });
+        this.listenersMap_.set(event, nativeListener); // 多次设置同一个事件时,后面的 callback 覆盖前面
+        TrtcEvent.addEventListener(event, nativeListener);
+    }
+    off(event) {
+        if (typeof event !== NAME.STRING) {
+            throw new TrtcError({
+                code: TXLiteJSError.INVALID_PARAMETER,
+                message: `${NAME.LOG_PREFIX} please check the off method parameter types. event type is a ${typeof event} not a ${NAME.STRING}`,
+            });
+        }
+        try {
+            if (event === '*') {
+                this.listenersMap_.forEach((value, key) => {
+                    TrtcEvent.removeEventListener(key, value);
+                });
+                this.listenersMap_.clear();
+            }
+            else {
+                TrtcEvent.removeEventListener(event, this.listenersMap_.get(event));
+                this.listenersMap_.delete(event);
+            }
+        }
+        catch (error) {
+            throw generateError_(error);
+        }
+    }
+    enterRoom(params, scene) {
+        if (scene !== TRTCAppScene.TRTCAppSceneVideoCall && scene !== TRTCAppScene.TRTCAppSceneLIVE && scene !== TRTCAppScene.TRTCAppSceneAudioCall && scene !== TRTCAppScene.TRTCAppSceneVoiceChatRoom) {
+            throw new TrtcError({
+                code: TXLiteJSError.INVALID_PARAMETER,
+                message: `${NAME.LOG_PREFIX} please check the enterRoom method parameters. scene is not of TRTCAppScene`,
+            });
+        }
+        try {
+            const enterRoomParams = Object.assign(Object.assign({}, params), { role: params.role || TRTCRoleType.TRTCRoleAnchor, appScene: scene });
+            TrtcNativeTrtcCloudModule.enterRoom(enterRoomParams);
+        }
+        catch (error) {
+            throw generateError_(error);
+        }
+    }
+    exitRoom() {
+        try {
+            TrtcNativeTrtcCloudModule.exitRoom();
+        }
+        catch (error) {
+            throw generateError_(error);
+        }
+    }
+    connectOtherRoom(params) {
+        try {
+            TrtcNativeTrtcCloudModule.connectOtherRoom(params);
+        }
+        catch (error) {
+            throw generateError_(error);
+        }
+    }
+    disconnectOtherRoom() {
+        try {
+            TrtcNativeTrtcCloudModule.disconnectOtherRoom();
+        }
+        catch (error) {
+            throw generateError_(error);
+        }
+    }
+    switchRole(role) {
+        if (role !== TRTCRoleType.TRTCRoleAnchor && role !== TRTCRoleType.TRTCRoleAudience) {
+            throw new TrtcError({
+                code: TXLiteJSError.INVALID_PARAMETER,
+                message: `${NAME.LOG_PREFIX} please check the switchRole method parameter. role is not of TRTCRoleType`,
+            });
+        }
+        try {
+            role && TrtcNativeTrtcCloudModule.switchRole(role);
+        }
+        catch (error) {
+            throw generateError_(error);
+        }
+    }
+    startLocalPreview(isFrontCamera = true, viewId) {
+        if (typeof isFrontCamera !== NAME.BOOLEAN || !viewId || typeof viewId !== NAME.STRING) {
+            throw new TrtcError({
+                code: TXLiteJSError.INVALID_PARAMETER,
+                message: `${NAME.LOG_PREFIX} please check the startLocalPreview method parameters`,
+            });
+        }
+        try {
+            let param = { isFrontCamera: !!isFrontCamera };
+            param = viewId ? Object.assign(Object.assign({}, param), { userId: viewId }) : param;
+            TrtcNativeTrtcCloudModule.startLocalPreview(param);
+        }
+        catch (error) {
+            throw generateError_(error);
+        }
+    }
+    setVideoEncoderParam(param) {
+        try {
+            TrtcNativeTrtcCloudModule.setVideoEncoderParam(param);
+        }
+        catch (error) {
+            throw generateError_(error);
+        }
+    }
+    stopLocalPreview() {
+        try {
+            TrtcNativeTrtcCloudModule.stopLocalPreview();
+        }
+        catch (error) {
+            throw generateError_(error);
+        }
+    }
+    switchCamera(isFrontCamera) {
+        if (typeof isFrontCamera !== NAME.BOOLEAN) {
+            throw new TrtcError({
+                code: TXLiteJSError.INVALID_PARAMETER,
+                message: `${NAME.LOG_PREFIX} please check the switchCamera method parameter`,
+            });
+        }
+        try {
+            TrtcNativeTrtcCloudModule.switchCamera(isFrontCamera);
+        }
+        catch (error) {
+            throw generateError_(error);
+        }
+    }
+    setLocalRenderParams(params) {
+        try {
+            const { rotation = TRTCVideoRotation.TRTCVideoRotation_0, fillMode = TRTCVideoFillMode.TRTCVideoFillMode_Fill, mirrorType = TRTCVideoMirrorType.TRTCVideoMirrorType_Auto } = params;
+            TrtcNativeTrtcCloudModule.setLocalRenderParams({
+                rotation,
+                fillMode,
+                mirrorType,
+            });
+        }
+        catch (error) {
+            throw generateError_(error);
+        }
+    }
+    muteLocalVideo(streamType, mute) {
+        if (streamType !== TRTCVideoStreamType.TRTCVideoStreamTypeBig && streamType !== TRTCVideoStreamType.TRTCVideoStreamTypeSub || typeof mute !== NAME.BOOLEAN) {
+            throw new TrtcError({
+                code: TXLiteJSError.INVALID_PARAMETER,
+                message: `${NAME.LOG_PREFIX} please check the muteLocalVideo method parameters`,
+            });
+        }
+        try {
+            TrtcNativeTrtcCloudModule.muteLocalVideo({ streamType, mute: !!mute });
+        }
+        catch (error) {
+            throw generateError_(error);
+        }
+    }
+    startRemoteView(userId, streamType, viewId) {
+        if (!userId || streamType !== TRTCVideoStreamType.TRTCVideoStreamTypeBig && streamType !== TRTCVideoStreamType.TRTCVideoStreamTypeSmall && streamType !== TRTCVideoStreamType.TRTCVideoStreamTypeSub || !viewId) {
+            throw new TrtcError({
+                code: TXLiteJSError.INVALID_PARAMETER,
+                message: `${NAME.LOG_PREFIX} please check the startRemoteView method parameters`,
+            });
+        }
+        try {
+            TrtcNativeTrtcCloudModule.startRemoteView({ userId, streamType, viewId });
+        }
+        catch (error) {
+            throw generateError_(error);
+        }
+    }
+    stopRemoteView(userId, streamType) {
+        if (!userId || streamType !== TRTCVideoStreamType.TRTCVideoStreamTypeBig && streamType !== TRTCVideoStreamType.TRTCVideoStreamTypeSmall && streamType !== TRTCVideoStreamType.TRTCVideoStreamTypeSub) {
+            throw new TrtcError({
+                code: TXLiteJSError.INVALID_PARAMETER,
+                message: `${NAME.LOG_PREFIX} please check the stopRemoteView method parameters`,
+            });
+        }
+        try {
+            TrtcNativeTrtcCloudModule.stopRemoteView({ userId, streamType });
+        }
+        catch (error) {
+            throw generateError_(error);
+        }
+    }
+    // 远端渲染设置
+    setRemoteRenderParams(userId, streamType, params) {
+        try {
+            if (!userId || (streamType !== TRTCVideoStreamType.TRTCVideoStreamTypeBig && streamType !== TRTCVideoStreamType.TRTCVideoStreamTypeSub)) {
+                throw new TrtcError({
+                    code: TXLiteJSError.INVALID_PARAMETER,
+                    message: `${NAME.LOG_PREFIX} please check the snapshotVideo method parameters`,
+                });
+            }
+            const { rotation = TRTCVideoRotation.TRTCVideoRotation_0, fillMode = TRTCVideoFillMode.TRTCVideoFillMode_Fill, mirrorType = TRTCVideoMirrorType.TRTCVideoMirrorType_Auto } = params;
+            TrtcNativeTrtcCloudModule.setRemoteRenderParams({
+                userId,
+                streamType,
+                rotation,
+                fillMode,
+                mirrorType
+            });
+        }
+        catch (error) {
+            throw generateError_(error);
+        }
+    }
+    // 截图
+    snapshotVideo(userId, streamType, sourceType) {
+        if (streamType !== TRTCVideoStreamType.TRTCVideoStreamTypeBig && streamType !== TRTCVideoStreamType.TRTCVideoStreamTypeSub) {
+            throw new TrtcError({
+                code: TXLiteJSError.INVALID_PARAMETER,
+                message: `${NAME.LOG_PREFIX} please check the snapshotVideo method parameters`,
+            });
+        }
+        try {
+            TrtcNativeTrtcCloudModule.snapshotVideo({ userId: userId || null, streamType, sourceType });
+        }
+        catch (error) {
+            throw generateError_(error);
+        }
+    }
+    startLocalAudio(quality = TRTCAudioQuality.TRTCAudioQualityDefault) {
+        if (quality !== TRTCAudioQuality.TRTCAudioQualitySpeech && quality !== TRTCAudioQuality.TRTCAudioQualityDefault && quality !== TRTCAudioQuality.TRTCAudioQualityMusic) {
+            throw new TrtcError({
+                code: TXLiteJSError.INVALID_PARAMETER,
+                message: `${NAME.LOG_PREFIX} please check the startLocalAudio method parameters`,
+            });
+        }
+        try {
+            TrtcNativeTrtcCloudModule.startLocalAudio(quality);
+        }
+        catch (error) {
+            throw generateError_(error);
+        }
+    }
+    stopLocalAudio() {
+        try {
+            TrtcNativeTrtcCloudModule.stopLocalAudio();
+        }
+        catch (error) {
+            throw generateError_(error);
+        }
+    }
+    muteLocalAudio(mute) {
+        if (typeof mute !== NAME.BOOLEAN) {
+            throw new TrtcError({
+                code: TXLiteJSError.INVALID_PARAMETER,
+                message: `${NAME.LOG_PREFIX} please check the muteLocalAudio method parameters, mute type is a ${typeof mute} not a ${NAME.BOOLEAN}`,
+            });
+        }
+        try {
+            TrtcNativeTrtcCloudModule.muteLocalAudio(!!mute);
+        }
+        catch (error) {
+            throw generateError_(error);
+        }
+    }
+    muteRemoteAudio(userId, mute) {
+        if (typeof mute !== NAME.BOOLEAN || !userId) {
+            throw new TrtcError({
+                code: TXLiteJSError.INVALID_PARAMETER,
+                message: `${NAME.LOG_PREFIX} please check the muteRemoteAudio method parameters`,
+            });
+        }
+        try {
+            TrtcNativeTrtcCloudModule.muteRemoteAudio({ userId, mute: !!mute });
+        }
+        catch (error) {
+            throw generateError_(error);
+        }
+    }
+    muteAllRemoteAudio(mute) {
+        if (typeof mute !== NAME.BOOLEAN) {
+            throw new TrtcError({
+                code: TXLiteJSError.INVALID_PARAMETER,
+                message: `${NAME.LOG_PREFIX} please check the muteAllRemoteAudio method parameters, mute type is a ${typeof mute} not a ${NAME.BOOLEAN}`,
+            });
+        }
+        try {
+            TrtcNativeTrtcCloudModule.muteAllRemoteAudio(!!mute);
+        }
+        catch (error) {
+            throw generateError_(error);
+        }
+    }
+    setAudioRoute(route) {
+        if (route !== TRTCAudioRoute.TRTCAudioRouteSpeaker && route !== TRTCAudioRoute.TRTCAudioRouteEarpiece) {
+            throw new TrtcError({
+                code: TXLiteJSError.INVALID_PARAMETER,
+                message: `${NAME.LOG_PREFIX} please check the setAudioRoute method parameter, route is not of TRTCAudioRoute`,
+            });
+        }
+        try {
+            TrtcNativeTrtcCloudModule.setAudioRoute(route);
+        }
+        catch (error) {
+            throw generateError_(error);
+        }
+    }
+    enableAudioVolumeEvaluation(interval) {
+        if (typeof interval !== NAME.NUMBER) {
+            throw new TrtcError({
+                code: TXLiteJSError.INVALID_PARAMETER,
+                message: `${NAME.LOG_PREFIX} please check the enableAudioVolumeEvaluation method parameter, interval type is a ${typeof interval} not a ${NAME.NUMBER}`,
+            });
+        }
+        try {
+            interval > 0 && TrtcNativeTrtcCloudModule.enableAudioVolumeEvaluation(interval);
+        }
+        catch (error) {
+            throw generateError_(error);
+        }
+    }
+    // ///////////////////////////////////////////////////////////////////////////////
+    //
+    //                      美颜 + 水印
+    //
+    // ///////////////////////////////////////////////////////////////////////////////
+    setBeautyStyle(beautyStyle) {
+        if (beautyStyle !== TRTCBeautyStyle.TRTCBeautyStyleSmooth && beautyStyle !== TRTCBeautyStyle.TRTCBeautyStyleNature && beautyStyle !== TRTCBeautyStyle.TRTCBeautyStylePitu) {
+            throw new TrtcError({
+                code: TXLiteJSError.INVALID_PARAMETER,
+                message: `${NAME.LOG_PREFIX} please check the setBeautyStyle method parameter, beautyStyle is not of TRTCBeautyStyle`,
+            });
+        }
+        try {
+            TrtcNativeTrtcCloudModule.setBeautyStyle(beautyStyle);
+        }
+        catch (error) {
+            throw generateError_(error);
+        }
+    }
+    setBeautyLevel(beautyLevel) {
+        if (typeof beautyLevel !== NAME.NUMBER || (beautyLevel < 0 || beautyLevel > 9)) {
+            throw new TrtcError({
+                code: TXLiteJSError.INVALID_PARAMETER,
+                message: `${NAME.LOG_PREFIX} please check the setBeautyLevel method parameter, beautyLevel should in the range 0-9`,
+            });
+        }
+        try {
+            TrtcNativeTrtcCloudModule.setBeautyLevel(beautyLevel);
+        }
+        catch (error) {
+            throw generateError_(error);
+        }
+    }
+    // ///////////////////////////////////////////////////////////////////////////////
+    //
+    //                      背景音效
+    //
+    // ///////////////////////////////////////////////////////////////////////////////
+    startPlayMusic(musicParam) {
+        try {
+            const { id = 0 } = musicParam || {};
+            TXAudioEffectManagerModule.startPlayMusic(Object.assign(Object.assign({}, musicParam), { ID: id })); // v1.2.0 的 iOS 解析的是 ID, v1.2.1 插件进行了修复
+        }
+        catch (error) {
+            throw generateError_(error);
+        }
+    }
+    stopPlayMusic(id) {
+        try {
+            TXAudioEffectManagerModule.stopPlayMusic(id);
+        }
+        catch (error) {
+            throw generateError_(error);
+        }
+    }
+    pausePlayMusic(id) {
+        try {
+            TXAudioEffectManagerModule.pausePlayMusic(id);
+        }
+        catch (error) {
+            throw generateError_(error);
+        }
+    }
+    resumePlayMusic(id) {
+        try {
+            TXAudioEffectManagerModule.resumePlayMusic(id);
+        }
+        catch (error) {
+            throw generateError_(error);
+        }
+    }
+    // ///////////////////////////////////////////////////////////////////////////////
+    //
+    //                      屏幕分享
+    //
+    // ///////////////////////////////////////////////////////////////////////////////
+    setSubStreamEncoderParam(param) {
+        try {
+            TrtcNativeTrtcCloudModule.setSubStreamEncoderParam(param);
+        }
+        catch (error) {
+            throw generateError_(error);
+        }
+    }
+    startScreenCapture(streamType = TRTCVideoStreamType.TRTCVideoStreamTypeSub, encParams = null) {
+        try {
+            let platform = uni.getSystemInfoSync().platform;
+            if ((streamType !== TRTCVideoStreamType.TRTCVideoStreamTypeSub && streamType !== TRTCVideoStreamType.TRTCVideoStreamTypeBig)) {
+                streamType = TRTCVideoStreamType.TRTCVideoStreamTypeSub;
+            }
+            const screenCaptureParams = Object.assign({ streamType }, encParams);
+            if (platform === NAME.ANDROID) {
+                TrtcNativeTrtcCloudModule.startScreenCapture(screenCaptureParams);
+            }
+            if (platform === NAME.IOS) {
+                // 开始应用内的屏幕分享(仅支持 iOS 13.0 及以上系统)
+                TrtcNativeTrtcCloudModule.startScreenCaptureInApp(screenCaptureParams);
+                // if (shareSource === TRTCShareSource.InApp) {
+                //   TrtcNativeTrtcCloudModule.startScreenCaptureInApp(screenCaptureParams);
+                // }
+                // // 开始全系统的屏幕分享(仅支持 iOS 11.0 及以上系统)
+                // if (shareSource === TRTCShareSource.ByReplaykit) {
+                //   TrtcNativeTrtcCloudModule.startScreenCaptureByReplaykit({ ...screenCaptureParams, appGroup: null });
+                // }
+            }
+        }
+        catch (error) {
+            throw generateError_(error);
+        }
+    }
+    stopScreenCapture() {
+        try {
+            TrtcNativeTrtcCloudModule.stopScreenCapture();
+        }
+        catch (error) {
+            throw generateError_(error);
+        }
+    }
+    pauseScreenCapture() {
+        try {
+            TrtcNativeTrtcCloudModule.pauseScreenCapture();
+        }
+        catch (error) {
+            throw generateError_(error);
+        }
+    }
+    resumeScreenCapture() {
+        try {
+            TrtcNativeTrtcCloudModule.resumeScreenCapture();
+        }
+        catch (error) {
+            throw generateError_(error);
+        }
+    }
+}

+ 612 - 0
TrtcCloud/lib/TrtcCode.js

@@ -0,0 +1,612 @@
+import { NAME, errorCodeUrl } from './constants';
+/**
+ * @namespace ErrorCode
+ *
+ * @description 错误码、警告码和事件列表
+ */
+/////////////////////////////////////////////////////////////////////////////////
+//
+//                     (一)错误码(严重)
+//
+/////////////////////////////////////////////////////////////////////////////////
+/**
+ * @memberof ErrorCode
+ * @typedef 错误码(严重)
+ * @description SDK 错误码(严重)对照表
+ * | 符号 | 值 | 含义 |
+ * |---|---|---|
+ * |ERR_NULL|0|无错误|
+ * |ERR_ROOM_ENTER_FAIL|-3301|进入房间失败|
+ * |ERR_ENTER_ROOM_PARAM_NULL|-3316|进房参数为空,请检查 enterRoom:appScene: 接口调用是否传入有效的 param|
+ * |ERR_SDK_APPID_INVALID|-3317|进房参数 sdkAppId 错误|
+ * |ERR_ROOM_ID_INVALID|-3318|进房参数 roomId 错误|
+ * |ERR_USER_ID_INVALID|-3319|进房参数 userID 不正确|
+ * |ERR_USER_SIG_INVALID|-3320|进房参数 userSig 不正确|
+ * |ERR_ROOM_REQUEST_ENTER_ROOM_TIMEOUT|-3308|请求进房超时,请检查网络|
+ * |ERR_SERVER_INFO_SERVICE_SUSPENDED|-100013|服务不可用。请检查:套餐包剩余分钟数是否大于0,腾讯云账号是否欠费|
+ * |ERR_ROOM_REQUEST_QUIT_ROOM_TIMEOUT|-3325|请求退房超时|
+ * |ERR_CAMERA_START_FAIL|-1301|打开摄像头失败,例如在 Windows 或 Mac 设备,摄像头的配置程序(驱动程序)异常,禁用后重新启用设备,或者重启机器,或者更新配置程序|
+ * |ERR_CAMERA_NOT_AUTHORIZED|-1314|摄像头设备未授权,通常在移动设备出现,可能是权限被用户拒绝了|
+ * |ERR_CAMERA_SET_PARAM_FAIL|-1315|摄像头参数设置出错(参数不支持或其它)|
+ * |ERR_CAMERA_OCCUPY|-1316|摄像头正在被占用中,可尝试打开其他摄像头|
+ * |ERR_MIC_START_FAIL|-1302|打开麦克风失败,例如在 Windows 或 Mac 设备,麦克风的配置程序(驱动程序)异常,禁用后重新启用设备,或者重启机器,或者更新配置程序|
+ * |ERR_MIC_NOT_AUTHORIZED|-1317|麦克风设备未授权,通常在移动设备出现,可能是权限被用户拒绝了|
+ * |ERR_MIC_SET_PARAM_FAIL|-1318|麦克风设置参数失败|
+ * |ERR_MIC_OCCUPY|-1319|麦克风正在被占用中,例如移动设备正在通话时,打开麦克风会失败|
+ * |ERR_MIC_STOP_FAIL|-1320|停止麦克风失败|
+ * |ERR_SPEAKER_START_FAIL|-1321|打开扬声器失败,例如在 Windows 或 Mac 设备,扬声器的配置程序(驱动程序)异常,禁用后重新启用设备,或者重启机器,或者更新配置程序|
+ * |ERR_SPEAKER_SET_PARAM_FAIL|-1322|扬声器设置参数失败|
+ * |ERR_SPEAKER_STOP_FAIL|-1323|停止扬声器失败|
+ * |ERR_SCREEN_CAPTURE_START_FAIL|-1308|开始录屏失败,如果在移动设备出现,可能是权限被用户拒绝了,如果在 Windows 或 Mac 系统的设备出现,请检查录屏接口的参数是否符合要求|
+ * |ERR_SCREEN_CAPTURE_UNSURPORT|-1309|录屏失败,在 Android 平台,需要5.0以上的系统|
+ * |ERR_SERVER_CENTER_NO_PRIVILEDGE_PUSH_SUB_VIDEO|-102015|没有权限上行辅路|
+ * |ERR_SERVER_CENTER_ANOTHER_USER_PUSH_SUB_VIDEO|-102016|其他用户正在上行辅路|
+ * |ERR_VIDEO_ENCODE_FAIL|-1303|视频帧编码失败,例如 iOS 设备切换到其他应用时,硬编码器可能被系统释放,再切换回来时,硬编码器重启前,可能会抛出|
+ * |ERR_UNSUPPORTED_RESOLUTION|-1305|不支持的视频分辨率|
+ * |ERR_AUDIO_ENCODE_FAIL|-1304|音频帧编码失败,例如传入自定义音频数据,SDK 无法处理|
+ * |ERR_UNSUPPORTED_SAMPLERATE|-1306|不支持的音频采样率|
+ * |ERR_PIXEL_FORMAT_UNSUPPORTED|-1327|设置的 pixel format 不支持|
+ * |ERR_BUFFER_TYPE_UNSUPPORTED|-1328|设置的 buffer type 不支持|
+ * |ERR_PUBLISH_CDN_STREAM_REQUEST_TIME_OUT|-3321|旁路转推请求超时|
+ * |ERR_CLOUD_MIX_TRANSCODING_REQUEST_TIME_OUT|-3322|云端混流请求超时|
+ * |ERR_PUBLISH_CDN_STREAM_SERVER_FAILED|-3323|旁路转推回包异常|
+ * |ERR_CLOUD_MIX_TRANSCODING_SERVER_FAILED|-3324|云端混流回包异常|
+ * |ERR_ROOM_REQUEST_START_PUBLISHING_TIMEOUT|-3333|开始向腾讯云的直播 CDN 推流信令超时|
+ * |ERR_ROOM_REQUEST_START_PUBLISHING_ERROR|-3334|开始向腾讯云的直播 CDN 推流信令异常|
+ * |ERR_ROOM_REQUEST_STOP_PUBLISHING_TIMEOUT|-3335|停止向腾讯云的直播 CDN 推流信令超时|
+ * |ERR_ROOM_REQUEST_STOP_PUBLISHING_ERROR|-3336|停止向腾讯云的直播 CDN 推流信令异常|
+ * |ERR_ROOM_REQUEST_CONN_ROOM_TIMEOUT|-3326|请求连麦超时|
+ * |ERR_ROOM_REQUEST_DISCONN_ROOM_TIMEOUT|-3327|请求退出连麦超时|
+ * |ERR_ROOM_REQUEST_CONN_ROOM_INVALID_PARAM|-3328|无效参数|
+ * |ERR_CONNECT_OTHER_ROOM_AS_AUDIENCE|-3330|当前是观众角色,不能请求或断开跨房连麦,需要先 switchRole() 到主播|
+ * |ERR_SERVER_CENTER_CONN_ROOM_NOT_SUPPORT|-102031|不支持跨房间连麦|
+ * |ERR_SERVER_CENTER_CONN_ROOM_REACH_MAX_NUM|-102032|达到跨房间连麦上限|
+ * |ERR_SERVER_CENTER_CONN_ROOM_REACH_MAX_RETRY_TIMES|-102033|跨房间连麦重试次数耗尽|
+ * |ERR_SERVER_CENTER_CONN_ROOM_REQ_TIMEOUT|-102034|跨房间连麦请求超时|
+ * |ERR_SERVER_CENTER_CONN_ROOM_REQ|-102035|跨房间连麦请求格式错误|
+ * |ERR_SERVER_CENTER_CONN_ROOM_NO_SIG|-102036|跨房间连麦无签名|
+ * |ERR_SERVER_CENTER_CONN_ROOM_DECRYPT_SIG|-102037|跨房间连麦签名解密失败|
+ * |ERR_SERVER_CENTER_CONN_ROOM_NO_KEY|-102038|未找到跨房间连麦签名解密密钥|
+ * |ERR_SERVER_CENTER_CONN_ROOM_PARSE_SIG|-102039|跨房间连麦签名解析错误|
+ * |ERR_SERVER_CENTER_CONN_ROOM_INVALID_SIG_TIME|-102040|跨房间连麦签名时间戳错误|
+ * |ERR_SERVER_CENTER_CONN_ROOM_SIG_GROUPID|-102041|跨房间连麦签名不匹配|
+ * |ERR_SERVER_CENTER_CONN_ROOM_NOT_CONNED|-102042|本房间无连麦|
+ * |ERR_SERVER_CENTER_CONN_ROOM_USER_NOT_CONNED|-102043|本用户未发起连麦|
+ * |ERR_SERVER_CENTER_CONN_ROOM_FAILED|-102044|跨房间连麦失败|
+ * |ERR_SERVER_CENTER_CONN_ROOM_CANCEL_FAILED|-102045|取消跨房间连麦失败|
+ * |ERR_SERVER_CENTER_CONN_ROOM_CONNED_ROOM_NOT_EXIST|-102046|被连麦房间不存在|
+ * |ERR_SERVER_CENTER_CONN_ROOM_CONNED_REACH_MAX_ROOM|-102047|被连麦房间达到连麦上限|
+ * |ERR_SERVER_CENTER_CONN_ROOM_CONNED_USER_NOT_EXIST|-102048|被连麦用户不存在|
+ * |ERR_SERVER_CENTER_CONN_ROOM_CONNED_USER_DELETED|-102049|被连麦用户已被删除|
+ * |ERR_SERVER_CENTER_CONN_ROOM_CONNED_USER_FULL|-102050|被连麦用户达到资源上限|
+ * |ERR_SERVER_CENTER_CONN_ROOM_INVALID_SEQ|-102051|连麦请求序号错乱|
+ */
+export const TXLiteAVError = {
+    /** 无错误 */
+    ERR_NULL: 0,
+    /** 进入房间失败 */
+    ERR_ROOM_ENTER_FAIL: -3301,
+    /** 进房参数为空,请检查 enterRoom:appScene: 接口调用是否传入有效的 param */
+    ERR_ENTER_ROOM_PARAM_NULL: -3316,
+    /** 进房参数 sdkAppId 错误 */
+    ERR_SDK_APPID_INVALID: -3317,
+    /** 进房参数 roomId 错误 */
+    ERR_ROOM_ID_INVALID: -3318,
+    /** 进房参数 userID 不正确 */
+    ERR_USER_ID_INVALID: -3319,
+    /** 进房参数 userSig 不正确 */
+    ERR_USER_SIG_INVALID: -3320,
+    /** 请求进房超时,请检查网络 */
+    ERR_ROOM_REQUEST_ENTER_ROOM_TIMEOUT: -3308,
+    /** 服务不可用。请检查:套餐包剩余分钟数是否大于0,腾讯云账号是否欠费 */
+    ERR_SERVER_INFO_SERVICE_SUSPENDED: -100013,
+    /** 请求退房超时 */
+    ERR_ROOM_REQUEST_QUIT_ROOM_TIMEOUT: -3325,
+    /** 打开摄像头失败,例如在 Windows 或 Mac 设备,摄像头的配置程序(驱动程序)异常,禁用后重新启用设备,或者重启机器,或者更新配置程序 */
+    ERR_CAMERA_START_FAIL: -1301,
+    /** 摄像头设备未授权,通常在移动设备出现,可能是权限被用户拒绝了 */
+    ERR_CAMERA_NOT_AUTHORIZED: -1314,
+    /** 摄像头参数设置出错(参数不支持或其它) */
+    ERR_CAMERA_SET_PARAM_FAIL: -1315,
+    /** 摄像头正在被占用中,可尝试打开其他摄像头 */
+    ERR_CAMERA_OCCUPY: -1316,
+    /** 打开麦克风失败,例如在 Windows 或 Mac 设备,麦克风的配置程序(驱动程序)异常,禁用后重新启用设备,或者重启机器,或者更新配置程序 */
+    ERR_MIC_START_FAIL: -1302,
+    /** 麦克风设备未授权,通常在移动设备出现,可能是权限被用户拒绝了 */
+    ERR_MIC_NOT_AUTHORIZED: -1317,
+    /** 麦克风设置参数失败 */
+    ERR_MIC_SET_PARAM_FAIL: -1318,
+    /** 麦克风正在被占用中,例如移动设备正在通话时,打开麦克风会失败 */
+    ERR_MIC_OCCUPY: -1319,
+    /** 停止麦克风失败 */
+    ERR_MIC_STOP_FAIL: -1320,
+    /** 打开扬声器失败,例如在 Windows 或 Mac 设备,扬声器的配置程序(驱动程序)异常,禁用后重新启用设备,或者重启机器,或者更新配置程序 */
+    ERR_SPEAKER_START_FAIL: -1321,
+    /** 扬声器设置参数失败 */
+    ERR_SPEAKER_SET_PARAM_FAIL: -1322,
+    /** 停止扬声器失败 */
+    ERR_SPEAKER_STOP_FAIL: -1323,
+    /** 开始录屏失败,如果在移动设备出现,可能是权限被用户拒绝了,如果在 Windows 或 Mac 系统的设备出现,请检查录屏接口的参数是否符合要求 */
+    ERR_SCREEN_CAPTURE_START_FAIL: -1308,
+    /** 录屏失败,在 Android 平台,需要5.0以上的系统 */
+    ERR_SCREEN_CAPTURE_UNSURPORT: -1309,
+    /** 没有权限上行辅路 */
+    ERR_SERVER_CENTER_NO_PRIVILEDGE_PUSH_SUB_VIDEO: -102015,
+    /** 其他用户正在上行辅路 */
+    ERR_SERVER_CENTER_ANOTHER_USER_PUSH_SUB_VIDEO: -102016,
+    /** 视频帧编码失败,例如 iOS 设备切换到其他应用时,硬编码器可能被系统释放,再切换回来时,硬编码器重启前,可能会抛出 */
+    ERR_VIDEO_ENCODE_FAIL: -1303,
+    /** 音频帧编码失败,例如传入自定义音频数据,SDK 无法处理 */
+    ERR_AUDIO_ENCODE_FAIL: -1304,
+    /** 不支持的视频分辨率 */
+    ERR_UNSUPPORTED_RESOLUTION: -1305,
+    /** 不支持的音频采样率 */
+    ERR_UNSUPPORTED_SAMPLERATE: -1306,
+    /** 设置的 pixel format 不支持 */
+    ERR_PIXEL_FORMAT_UNSUPPORTED: -1327,
+    /** 设置的 buffer type 不支持 */
+    ERR_BUFFER_TYPE_UNSUPPORTED: -1328,
+    /** 旁路转推请求超时 */
+    ERR_PUBLISH_CDN_STREAM_REQUEST_TIME_OUT: -3321,
+    /** 云端混流请求超时 */
+    ERR_CLOUD_MIX_TRANSCODING_REQUEST_TIME_OUT: -3322,
+    /** 旁路转推回包异常 */
+    ERR_PUBLISH_CDN_STREAM_SERVER_FAILED: -3323,
+    /** 云端混流回包异常 */
+    ERR_CLOUD_MIX_TRANSCODING_SERVER_FAILED: -3324,
+    /** 开始向腾讯云的直播 CDN 推流信令超时 */
+    ERR_ROOM_REQUEST_START_PUBLISHING_TIMEOUT: -3333,
+    /** 开始向腾讯云的直播 CDN 推流信令异常 */
+    ERR_ROOM_REQUEST_START_PUBLISHING_ERROR: -3334,
+    /** 停止向腾讯云的直播 CDN 推流信令超时 */
+    ERR_ROOM_REQUEST_STOP_PUBLISHING_TIMEOUT: -3335,
+    /** 停止向腾讯云的直播 CDN 推流信令异常 */
+    ERR_ROOM_REQUEST_STOP_PUBLISHING_ERROR: -3336,
+    /** 请求连麦超时 */
+    ERR_ROOM_REQUEST_CONN_ROOM_TIMEOUT: -3326,
+    /** 请求退出连麦超时 */
+    ERR_ROOM_REQUEST_DISCONN_ROOM_TIMEOUT: -3327,
+    /** 无效参数 */
+    ERR_ROOM_REQUEST_CONN_ROOM_INVALID_PARAM: -3328,
+    /** 当前是观众角色,不能请求或断开跨房连麦,需要先 switchRole() 到主播 */
+    ERR_CONNECT_OTHER_ROOM_AS_AUDIENCE: -3330,
+    /** 不支持跨房间连麦 */
+    ERR_SERVER_CENTER_CONN_ROOM_NOT_SUPPORT: -102031,
+    /** 达到跨房间连麦上限 */
+    ERR_SERVER_CENTER_CONN_ROOM_REACH_MAX_NUM: -102032,
+    /** 跨房间连麦重试次数耗尽 */
+    ERR_SERVER_CENTER_CONN_ROOM_REACH_MAX_RETRY_TIMES: -102033,
+    /** 跨房间连麦请求超时 */
+    ERR_SERVER_CENTER_CONN_ROOM_REQ_TIMEOUT: -102034,
+    /** 跨房间连麦请求格式错误 */
+    ERR_SERVER_CENTER_CONN_ROOM_REQ: -102035,
+    /** 跨房间连麦无签名 */
+    ERR_SERVER_CENTER_CONN_ROOM_NO_SIG: -102036,
+    /** 跨房间连麦签名解密失败 */
+    ERR_SERVER_CENTER_CONN_ROOM_DECRYPT_SIG: -102037,
+    /** 未找到跨房间连麦签名解密密钥 */
+    ERR_SERVER_CENTER_CONN_ROOM_NO_KEY: -102038,
+    /** 跨房间连麦签名解析错误 */
+    ERR_SERVER_CENTER_CONN_ROOM_PARSE_SIG: -102039,
+    /** 跨房间连麦签名时间戳错误 */
+    ERR_SERVER_CENTER_CONN_ROOM_INVALID_SIG_TIME: -102040,
+    /** 跨房间连麦签名不匹配 */
+    ERR_SERVER_CENTER_CONN_ROOM_SIG_GROUPID: -102041,
+    /** 本房间无连麦 */
+    ERR_SERVER_CENTER_CONN_ROOM_NOT_CONNED: -102042,
+    /** 本用户未发起连麦 */
+    ERR_SERVER_CENTER_CONN_ROOM_USER_NOT_CONNED: -102043,
+    /** 跨房间连麦失败 */
+    ERR_SERVER_CENTER_CONN_ROOM_FAILED: -102044,
+    /** 取消跨房间连麦失败 */
+    ERR_SERVER_CENTER_CONN_ROOM_CANCEL_FAILED: -102045,
+    /** 被连麦房间不存在 */
+    ERR_SERVER_CENTER_CONN_ROOM_CONNED_ROOM_NOT_EXIST: -102046,
+    /** 被连麦房间达到连麦上限 */
+    ERR_SERVER_CENTER_CONN_ROOM_CONNED_REACH_MAX_ROOM: -102047,
+    /** 被连麦用户不存在 */
+    ERR_SERVER_CENTER_CONN_ROOM_CONNED_USER_NOT_EXIST: -102048,
+    /** 被连麦用户已被删除 */
+    ERR_SERVER_CENTER_CONN_ROOM_CONNED_USER_DELETED: -102049,
+    /** 被连麦用户达到资源上限 */
+    ERR_SERVER_CENTER_CONN_ROOM_CONNED_USER_FULL: -102050,
+    /** 连麦请求序号错乱 */
+    ERR_SERVER_CENTER_CONN_ROOM_INVALID_SEQ: -102051,
+    /** 直播,推流出现网络断开,且经过多次重试无法恢复 */
+    ERR_RTMP_PUSH_NET_DISCONNECT: -1307,
+    /** 直播,推流地址非法,例如不是 RTMP 协议的地址 */
+    ERR_RTMP_PUSH_INVALID_ADDRESS: -1313,
+    /** 直播,连接推流服务器失败(若支持智能选路,IP 全部失败) */
+    ERR_RTMP_PUSH_NET_ALLADDRESS_FAIL: -1324,
+    /** 直播,网络不可用,请确认 WiFi、移动数据或者有线网络是否正常 */
+    ERR_RTMP_PUSH_NO_NETWORK: -1325,
+    /** 直播,服务器拒绝连接请求,可能是该推流地址已经被占用,或者 TXSecret 校验失败,或者是过期了,或者是欠费了 */
+    ERR_RTMP_PUSH_SERVER_REFUSE: -1326,
+    /** 直播,网络断连,且经多次重连抢救无效,可以放弃治疗,更多重试请自行重启播放 */
+    ERR_PLAY_LIVE_STREAM_NET_DISCONNECT: -2301,
+    /** 直播,获取加速拉流的地址失败 */
+    ERR_GET_RTMP_ACC_URL_FAIL: -2302,
+    /** 播放的文件不存在 */
+    ERR_FILE_NOT_FOUND: -2303,
+    /** H265 解码失败 */
+    ERR_HEVC_DECODE_FAIL: -2304,
+    /** 点播,音视频流解密失败 */
+    ERR_VOD_DECRYPT_FAIL: -2305,
+    /** 点播,获取点播文件信息失败 */
+    ERR_GET_VODFILE_MEDIAINFO_FAIL: -2306,
+    /** 直播,切流失败(切流可以播放不同画面大小的视频) */
+    ERR_PLAY_LIVE_STREAM_SWITCH_FAIL: -2307,
+    /** 直播,服务器拒绝连接请求 */
+    ERR_PLAY_LIVE_STREAM_SERVER_REFUSE: -2308,
+    /** 直播,RTMPACC 低延时拉流失败,且经过多次重试无法恢复 */
+    ERR_RTMP_ACC_FETCH_STREAM_FAIL: -2309,
+    /** 心跳失败,客户端定时向服务器发送数据包,告诉服务器自己活着,这个错误通常是发包超时 */
+    ERR_ROOM_HEARTBEAT_FAIL: -3302,
+    /** 拉取接口机服务器地址失败 */
+    ERR_ROOM_REQUEST_IP_FAIL: -3303,
+    /** 连接接口机服务器失败 */
+    ERR_ROOM_CONNECT_FAIL: -3304,
+    /** 请求视频位失败 */
+    ERR_ROOM_REQUEST_AVSEAT_FAIL: -3305,
+    /** 请求 token https 超时,请检查网络是否正常,或网络防火墙是否放行 https 访问 official.opensso.tencent-cloud.com:443 */
+    ERR_ROOM_REQUEST_TOKEN_HTTPS_TIMEOUT: -3306,
+    /** 请求 IP 和 sig 超时,请检查网络是否正常,或网络防火墙是否放行 UDP 访问下列 IP 和域名 query.tencent-cloud.com:8000 162.14.23.140:8000 162.14.7.49:8000 */
+    ERR_ROOM_REQUEST_IP_TIMEOUT: -3307,
+    /** 请求视频位超时 */
+    ERR_ROOM_REQUEST_VIDEO_FLAG_TIMEOUT: -3309,
+    /** 请求视频数据超时 */
+    ERR_ROOM_REQUEST_VIDEO_DATA_ROOM_TIMEOUT: -3310,
+    /** 请求修改视频能力项超时 */
+    ERR_ROOM_REQUEST_CHANGE_ABILITY_TIMEOUT: -3311,
+    /** 请求状态上报超时 */
+    ERR_ROOM_REQUEST_STATUS_REPORT_TIMEOUT: -3312,
+    /** 请求关闭视频超时 */
+    ERR_ROOM_REQUEST_CLOSE_VIDEO_TIMEOUT: -3313,
+    /** 请求接收视频项超时 */
+    ERR_ROOM_REQUEST_SET_RECEIVE_TIMEOUT: -3314,
+    /** 请求 token 无效参数,请检查 TRTCParams.userSig 是否填写正确 */
+    ERR_ROOM_REQUEST_TOKEN_INVALID_PARAMETER: -3315,
+    /** 请求 AES TOKEN 时,server 返回的内容是空的 */
+    ERR_ROOM_REQUEST_AES_TOKEN_RETURN_ERROR: -3329,
+    /** 请求接口机 IP 返回的列表为空的 */
+    ERR_ACCIP_LIST_EMPTY: -3331,
+    /** 请求发送 Json 信令超时 */
+    ERR_ROOM_REQUEST_SEND_JSON_CMD_TIMEOUT: -3332,
+    // Info 服务器(查询接口机 IP), 服务器错误码,数值范围[-100000, -110000]
+    /** server 解包错误,可能请求数据被篡改 */
+    ERR_SERVER_INFO_UNPACKING_ERROR: -100000,
+    /** TOKEN 错误 */
+    ERR_SERVER_INFO_TOKEN_ERROR: -100001,
+    /** 分配接口机错误 */
+    ERR_SERVER_INFO_ALLOCATE_ACCESS_FAILED: -100002,
+    /** 生成签名错误 */
+    ERR_SERVER_INFO_GENERATE_SIGN_FAILED: -100003,
+    /** https token 超时 */
+    ERR_SERVER_INFO_TOKEN_TIMEOUT: -100004,
+    /** 无效的命令字 */
+    ERR_SERVER_INFO_INVALID_COMMAND: -100005,
+    /** 权限位校验失败 */
+    ERR_SERVER_INFO_PRIVILEGE_FLAG_ERROR: -100006,
+    /** https 请求时,生成加密 key 错误 */
+    ERR_SERVER_INFO_GENERATE_KEN_ERROR: -100007,
+    /** https 请求时,生成 token 错误 */
+    ERR_SERVER_INFO_GENERATE_TOKEN_ERROR: -100008,
+    /** 数据库查询失败(房间相关存储信息) */
+    ERR_SERVER_INFO_DATABASE: -100009,
+    /** 房间号错误 */
+    ERR_SERVER_INFO_BAD_ROOMID: -100010,
+    /** 场景或角色错误 */
+    ERR_SERVER_INFO_BAD_SCENE_OR_ROLE: -100011,
+    /** 房间号转换出错 */
+    ERR_SERVER_INFO_ROOMID_EXCHANGE_FAILED: -100012,
+    /** 房间号非法 */
+    ERR_SERVER_INFO_STRGROUP_HAS_INVALID_CHARS: -100014,
+    /** 非法SDKAppid */
+    ERR_SERVER_INFO_LACK_SDKAPPID: -100015,
+    /** 无效请求, 旧版 0x1 要求带 Token; ECDH 要求带 ECDH Publich Key; 两个都没有就按报错 */
+    ERR_SERVER_INFO_INVALID: -100016,
+    /** 生成公钥失败 */
+    ERR_SERVER_INFO_ECDH_GET_KEY: -100017,
+    /** 获取tinyid失败 */
+    ERR_SERVER_INFO_ECDH_GET_TINYID: -100018,
+    // Access 接口机
+    /** token 过期 */
+    ERR_SERVER_ACC_TOKEN_TIMEOUT: -101000,
+    /** 签名错误 */
+    ERR_SERVER_ACC_SIGN_ERROR: -101001,
+    /** 签名超时 */
+    ERR_SERVER_ACC_SIGN_TIMEOUT: -101002,
+    /** 房间不存在 */
+    ERR_SERVER_ACC_ROOM_NOT_EXIST: -101003,
+    /** 后台房间标识 roomId 错误 */
+    ERR_SERVER_ACC_ROOMID: -101004,
+    /** 后台用户位置标识 locationId 错误 */
+    ERR_SERVER_ACC_LOCATIONID: -101005,
+    // center 服务器(信令和流控处理等任务)
+    /** 后台错误 */
+    ERR_SERVER_CENTER_SYSTEM_ERROR: -102000,
+    /** 无效的房间 Id */
+    ERR_SERVER_CENTER_INVALID_ROOMID: -102001,
+    /** 创建房间失败 */
+    ERR_SERVER_CENTER_CREATE_ROOM_FAILED: -102002,
+    /** 签名错误 */
+    ERR_SERVER_CENTER_SIGN_ERROR: -102003,
+    /** 签名过期 */
+    ERR_SERVER_CENTER_SIGN_TIMEOUT: -102004,
+    /** 房间不存在 */
+    ERR_SERVER_CENTER_ROOM_NOT_EXIST: -102005,
+    /** 房间添加用户失败 */
+    ERR_SERVER_CENTER_ADD_USER_FAILED: -102006,
+    /** 查找用户失败 */
+    ERR_SERVER_CENTER_FIND_USER_FAILED: -102007,
+    /** 频繁切换终端 */
+    ERR_SERVER_CENTER_SWITCH_TERMINATION_FREQUENTLY: -102008,
+    /** locationid 错误 */
+    ERR_SERVER_CENTER_LOCATION_NOT_EXIST: -102009,
+    /** 没有权限创建房间 */
+    ERR_SERVER_CENTER_NO_PRIVILEDGE_CREATE_ROOM: -102010,
+    /** 没有权限进入房间 */
+    ERR_SERVER_CENTER_NO_PRIVILEDGE_ENTER_ROOM: -102011,
+    /** 辅路抢视频位、申请辅路请求类型参数错误 */
+    ERR_SERVER_CENTER_INVALID_PARAMETER_SUB_VIDEO: -102012,
+    /** 没有权限上视频 */
+    ERR_SERVER_CENTER_NO_PRIVILEDGE_PUSH_VIDEO: -102013,
+    /** 没有空闲路由表 */
+    ERR_SERVER_CENTER_ROUTE_TABLE_ERROR: -102014,
+    /** 当前用户没有上行辅路 */
+    ERR_SERVER_CENTER_NOT_PUSH_SUB_VIDEO: -102017,
+    /** 用户被删除状态 */
+    ERR_SERVER_CENTER_USER_WAS_DELETED: -102018,
+    /** 没有权限请求视频 */
+    ERR_SERVER_CENTER_NO_PRIVILEDGE_REQUEST_VIDEO: -102019,
+    /** 进房参数 bussInfo 错误 */
+    ERR_SERVER_CENTER_INVALID_PARAMETER: -102023,
+    /** 请求 I 帧未知 opType */
+    ERR_SERVER_CENTER_I_FRAME_UNKNOW_TYPE: -102024,
+    /** 请求 I 帧包格式错误 */
+    ERR_SERVER_CENTER_I_FRAME_INVALID_PACKET: -102025,
+    /** 请求 I 帧目标用户不存在 */
+    ERR_SERVER_CENTER_I_FRAME_DEST_USER_NOT_EXIST: -102026,
+    /** 请求 I 帧房间用户太多 */
+    ERR_SERVER_CENTER_I_FRAME_ROOM_TOO_BIG: -102027,
+    /** 请求 I 帧参数错误 */
+    ERR_SERVER_CENTER_I_FRAME_RPS_INVALID_PARAMETER: -102028,
+    /** 房间号非法 */
+    ERR_SERVER_CENTER_INVALID_ROOM_ID: -102029,
+    /** 房间号超过限制 */
+    ERR_SERVER_CENTER_ROOM_ID_TOO_LONG: -102030,
+    /** 房间满员 */
+    ERR_SERVER_CENTER_ROOM_FULL: -102052,
+    /** json串解析失败 */
+    ERR_SERVER_CENTER_DECODE_JSON_FAIL: -102053,
+    /** 未定义命令字 */
+    ERR_SERVER_CENTER_UNKNOWN_SUB_CMD: -102054,
+    /** 未定义角色 */
+    ERR_SERVER_CENTER_INVALID_ROLE: -102055,
+    /** 代理机超出限制 */
+    ERR_SERVER_CENTER_REACH_PROXY_MAX: -102056,
+    //add by sunlitwang begin
+    /** 无法保存用户自定义recordId */
+    ERR_SERVER_CENTER_RECORDID_STORE: -102057,
+    /** Protobuf序列化错误 */
+    ERR_SERVER_CENTER_PB_SERIALIZE: -102058,
+    // https://cloud.tencent.com/document/product/269/1671#.E5.B8.90.E5.8F.B7.E7.B3.BB.E7.BB.9F , 帐号系统, 主要是70000 - 79999之间.
+    // 在请求 token 过程中,出现账号错误,SSO 返回的错误码,原为正数,现将其转换为负数。
+    /** sig 过期,请尝试重新生成。如果是刚生成,就过期,请检查有效期填写的是否过小,或者填的 0 */
+    ERR_SERVER_SSO_SIG_EXPIRED: -70001,
+    /** sig 校验失败,请确认下 sig 内容是否被截断,如缓冲区长度不够导致的内容截断 */
+    ERR_SERVER_SSO_SIG_VERIFICATION_FAILED_1: -70003,
+    /** sig 校验失败,请确认下 sig 内容是否被截断,如缓冲区长度不够导致的内容截断 */
+    ERR_SERVER_SSO_SIG_VERIFICATION_FAILED_2: -70004,
+    /** sig 校验失败,可用工具自行验证生成的 sig 是否正确 */
+    ERR_SERVER_SSO_SIG_VERIFICATION_FAILED_3: -70005,
+    /** sig 校验失败,可用工具自行验证生成的 sig 是否正确 */
+    ERR_SERVER_SSO_SIG_VERIFICATION_FAILED_4: -70006,
+    /** sig 校验失败,可用工具自行验证生成的 sig 是否正确 */
+    ERR_SERVER_SSO_SIG_VERIFICATION_FAILED_5: -70007,
+    /** sig 校验失败,可用工具自行验证生成的 sig 是否正确 */
+    ERR_SERVER_SSO_SIG_VERIFICATION_FAILED_6: -70008,
+    /** 用业务公钥验证 sig 失败,请确认生成的 usersig 使用的私钥和 sdkAppId 是否对应 */
+    ERR_SERVER_SSO_SIG_VERIFICATION_FAILED_7: -70009,
+    /** sig 校验失败,可用工具自行验证生成的 sig 是否正确 */
+    ERR_SERVER_SSO_SIG_VERIFICATION_FAILED_8: -70010,
+    /** sig 中 identifier 与请求时的 identifier 不匹配,请检查登录时填写的 identifier 与 sig 中的是否一致 */
+    ERR_SERVER_SSO_SIG_VERIFICATION_ID_NOT_MATCH: -70013,
+    /** sig 中 sdkAppId 与请求时的 sdkAppId 不匹配,请检查登录时填写的 sdkAppId 与 sig 中的是否一致 */
+    ERR_SERVER_SSO_APPID_NOT_MATCH: -70014,
+    /** 内部第三方票据验证超时,请重试,如多次重试不成功,请@TLS 帐号支持,QQ 3268519604 */
+    ERR_SERVER_SSO_VERIFICATION_EXPIRED: -70017,
+    /** 内部第三方票据验证超时,请重试,如多次重试不成功,请@TLS 帐号支持,QQ 3268519604 */
+    ERR_SERVER_SSO_VERIFICATION_FAILED: -70018,
+    /** sdkAppId 未找到,请确认是否已经在腾讯云上配置 */
+    ERR_SERVER_SSO_APPID_NOT_FOUND: -70020,
+    /** 帐号已被拉入黑名单,请联系 TLS 帐号支持 QQ 3268519604 */
+    ERR_SERVER_SSO_ACCOUNT_IN_BLACKLIST: -70051,
+    /** usersig 已经失效,请重新生成,再次尝试 */
+    ERR_SERVER_SSO_SIG_INVALID: -70052,
+    /** 安全原因被限制 */
+    ERR_SERVER_SSO_LIMITED_BY_SECURITY: -70114,
+    /** 登录状态无效,请使用 usersig 重新鉴权 */
+    ERR_SERVER_SSO_INVALID_LOGIN_STATUS: -70221,
+    /** sdkAppId 填写错误 */
+    ERR_SERVER_SSO_APPID_ERROR: -70252,
+    /** 票据校验失败,请检查各项参数是否正确 */
+    ERR_SERVER_SSO_TICKET_VERIFICATION_FAILED: -70346,
+    /** 票据因过期原因校验失败 */
+    ERR_SERVER_SSO_TICKET_EXPIRED: -70347,
+    /** 创建账号数量超过已购买预付费数量限制 */
+    ERR_SERVER_SSO_ACCOUNT_EXCEED_PURCHASES: -70398,
+    /** 服务器内部错误,请重试 */
+    ERR_SERVER_SSO_INTERNAL_ERROR: -70500,
+};
+/////////////////////////////////////////////////////////////////////////////////
+//
+//                     (二)错误码(警告)
+//
+/////////////////////////////////////////////////////////////////////////////////
+/**
+ * @memberof ErrorCode
+ * @typedef 错误码(警告)
+ * @description SDK 错误码(警告)对照表
+ * | 符号 | 值 | 含义 |
+ * |---|---|---|
+ * |WARNING_HW_ENCODER_START_FAIL|1103|硬编码启动出现问题,自动切换到软编码|
+ * |WARNING_VIDEO_ENCODER_SW_TO_HW|1107|当前 CPU 使用率太高,无法满足软件编码需求,自动切换到硬件编码|
+ * |WARNING_INSUFFICIENT_CAPTURE_FPS|1108|摄像头采集帧率不足,部分自带美颜算法的 Android 手机上会出现|
+ * |WARNING_SW_ENCODER_START_FAIL|1109|软编码启动失败|
+ * |WARNING_REDUCE_CAPTURE_RESOLUTION|1110|摄像头采集分辨率被降低,以满足当前帧率和性能最优解。|
+ * |WARNING_VIDEO_FRAME_DECODE_FAIL|2101|当前视频帧解码失败|
+ * |WARNING_AUDIO_FRAME_DECODE_FAIL|2102|当前音频帧解码失败|
+ * |WARNING_VIDEO_PLAY_LAG|2105|当前视频播放出现卡顿|
+ * |WARNING_HW_DECODER_START_FAIL|2106|硬解启动失败,采用软解码|
+ * |WARNING_VIDEO_DECODER_HW_TO_SW|2108|当前流硬解第一个 I 帧失败,SDK 自动切软解|
+ * |WARNING_SW_DECODER_START_FAIL|2109|软解码器启动失败|
+ * |WARNING_VIDEO_RENDER_FAIL|2110|视频渲染失败|
+ * |WARNING_AUDIO_RECORDING_WRITE_FAIL|7001|音频录制写入文件失败|
+ * |WARNING_ROOM_DISCONNECT|5101|网络断开连接|
+ * |WARNING_IGNORE_UPSTREAM_FOR_AUDIENCE|6001|当前是观众角色,忽略上行音视频数据|
+ */
+export const TXLiteAVWarning = {
+    /** 硬编码启动出现问题,自动切换到软编码 */
+    WARNING_HW_ENCODER_START_FAIL: 1103,
+    /** 当前 CPU 使用率太高,无法满足软件编码需求,自动切换到硬件编码 */
+    WARNING_VIDEO_ENCODER_SW_TO_HW: 1107,
+    /** 摄像头采集帧率不足,部分自带美颜算法的 Android 手机上会出现 */
+    WARNING_INSUFFICIENT_CAPTURE_FPS: 1108,
+    /** 软编码启动失败 */
+    WARNING_SW_ENCODER_START_FAIL: 1109,
+    /** 摄像头采集分辨率被降低,以满足当前帧率和性能最优解。 */
+    WARNING_REDUCE_CAPTURE_RESOLUTION: 1110,
+    /** 当前视频帧解码失败 */
+    WARNING_VIDEO_FRAME_DECODE_FAIL: 2101,
+    /** 当前音频帧解码失败 */
+    WARNING_AUDIO_FRAME_DECODE_FAIL: 2102,
+    /** 当前视频播放出现卡顿 */
+    WARNING_VIDEO_PLAY_LAG: 2105,
+    /** 硬解启动失败,采用软解码 */
+    WARNING_HW_DECODER_START_FAIL: 2106,
+    /** 当前流硬解第一个 I 帧失败,SDK 自动切软解 */
+    WARNING_VIDEO_DECODER_HW_TO_SW: 2108,
+    /** 软解码器启动失败 */
+    WARNING_SW_DECODER_START_FAIL: 2109,
+    /** 视频渲染失败 */
+    WARNING_VIDEO_RENDER_FAIL: 2110,
+    /** 音频录制写入文件失败 */
+    WARNING_AUDIO_RECORDING_WRITE_FAIL: 7001,
+    /** 网络断开连接 */
+    WARNING_ROOM_DISCONNECT: 5101,
+    /** 当前是观众角色,忽略上行音视频数据 */
+    WARNING_IGNORE_UPSTREAM_FOR_AUDIENCE: 6001,
+    /** 网络状况不佳:上行带宽太小,上传数据受阻 */
+    WARNING_NET_BUSY: 1101,
+    /** 直播,网络断连, 已启动自动重连(自动重连连续失败超过三次会放弃) */
+    WARNING_RTMP_SERVER_RECONNECT: 1102,
+    /** 直播,网络断连, 已启动自动重连(自动重连连续失败超过三次会放弃) */
+    WARNING_LIVE_STREAM_SERVER_RECONNECT: 2103,
+    /** 网络来包不稳:可能是下行带宽不足,或由于主播端出流不均匀 */
+    WARNING_RECV_DATA_LAG: 2104,
+    /** 直播,DNS 解析失败 */
+    WARNING_RTMP_DNS_FAIL: 3001,
+    /** 直播,服务器连接失败 */
+    WARNING_RTMP_SEVER_CONN_FAIL: 3002,
+    /** 直播,与 RTMP 服务器握手失败 */
+    WARNING_RTMP_SHAKE_FAIL: 3003,
+    /** 直播,服务器主动断开 */
+    WARNING_RTMP_SERVER_BREAK_CONNECT: 3004,
+    /** 直播,RTMP 读/写失败,将会断开连接 */
+    WARNING_RTMP_READ_WRITE_FAIL: 3005,
+    /** 直播,RTMP 写失败(SDK 内部错误码,不会对外抛出) */
+    WARNING_RTMP_WRITE_FAIL: 3006,
+    /** 直播,RTMP 读失败(SDK 内部错误码,不会对外抛出) */
+    WARNING_RTMP_READ_FAIL: 3007,
+    /** 直播,超过30s 没有数据发送,主动断开连接 */
+    WARNING_RTMP_NO_DATA: 3008,
+    /** 直播,connect 服务器调用失败(SDK 内部错误码,不会对外抛出) */
+    WARNING_PLAY_LIVE_STREAM_INFO_CONNECT_FAIL: 3009,
+    /** 直播,连接失败,该流地址无视频(SDK 内部错误码,不会对外抛出) */
+    WARNING_NO_STEAM_SOURCE_FAIL: 3010,
+    /** 网络断连,已启动自动重连 */
+    WARNING_ROOM_RECONNECT: 5102,
+    /** 网络状况不佳:上行带宽太小,上传数据受阻 */
+    WARNING_ROOM_NET_BUSY: 5103,
+};
+/////////////////////////////////////////////////////////////////////////////////
+//
+//                     (三)JS 封装层抛出的异常(严重)
+//
+/////////////////////////////////////////////////////////////////////////////////
+/**
+ * @namespace ErrorCode
+ * @description 错误码
+ */
+export const TXLiteJSError = {
+    /**
+     * 未知错误
+     * @default 0xFFFF
+     * @memberof module:ErrorCode
+     */
+    UNKNOWN: 0xffff,
+    /**
+     * 无效参数
+     *
+     * @default 0x1000
+     * @memberof module:ErrorCode
+     */
+    INVALID_PARAMETER: 0x1000,
+    /**
+     * 非法操作
+     *
+     * @default 0x1001
+     * @memberof module:ErrorCode
+     */
+    INVALID_OPERATION: 0x1001,
+};
+const getErrorName = function (code) {
+    for (let key in TXLiteJSError) {
+        if (TXLiteJSError[key] === code) {
+            return key;
+        }
+    }
+    return 'UNKNOWN';
+};
+/**
+ * TrtcError 错误对象<br>
+ * @extends Error
+ * @namespace ErrorCode
+ */
+class TrtcError extends Error {
+    constructor({ code = TXLiteJSError.UNKNOWN, message, extraInfo }) {
+        if (extraInfo) {
+            const tempError = {
+                errCode: code,
+                errMsg: message,
+                extraInfo: Object.assign(Object.assign({}, extraInfo), { errCodeUrl: errorCodeUrl }),
+            };
+            super(JSON.stringify(tempError));
+        }
+        else {
+            super(message +
+                ` <${getErrorName(code)} 0x${code.toString(16)}>. Refer to: ${errorCodeUrl}`);
+        }
+        this.errCode = code;
+        this.errMsg = message;
+        this.extraInfo = Object.assign(Object.assign({}, extraInfo), { errCodeUrl: errorCodeUrl });
+    }
+    /**
+     * 获取错误码<br>
+     * 详细错误码列表参见 {@link module:ErrorCode ErrorCode}
+     * @memberof TrtcError
+     */
+    getCode() {
+        return this.errCode;
+    }
+}
+export default TrtcError;
+export function generateError_(error, code = TXLiteJSError.UNKNOWN, extraInfo) {
+    return new TrtcError({
+        code: error.code || code,
+        message: `${NAME.LOG_PREFIX}${error.message}`,
+        extraInfo,
+    });
+}
+;

+ 454 - 0
TrtcCloud/lib/TrtcDefines.js

@@ -0,0 +1,454 @@
+/**
+ * TRTC 关键类型定义<br>
+ * @description 分辨率、质量等级等枚举和常量值的定义
+ */
+/////////////////////////////////////////////////////////////////////////////////
+//
+//                    【(一)视频相关枚举值定义】
+//
+/////////////////////////////////////////////////////////////////////////////////
+/**
+ * 视频分辨率<br>
+ * 此处仅定义横屏分辨率(如 640 × 360),如需使用竖屏分辨率(如 360 × 640),需要同时指定 VideoResolutionMode 为 Portrait
+ * @enum {Number}
+ */
+const TRTCVideoResolution_HACK_JSDOC = {
+    /** 宽高比 1:1;分辨率 120x120;建议码率(VideoCall)80kbps; 建议码率(LIVE)120kbps */
+    TRTCVideoResolution_120_120: 1,
+    /** 宽高比 1:1 分辨率 160x160;建议码率(VideoCall)100kbps; 建议码率(LIVE)150kbps */
+    TRTCVideoResolution_160_160: 3,
+    /** 宽高比 1:1;分辨率 270x270;建议码率(VideoCall)200kbps; 建议码率(LIVE)300kbps */
+    TRTCVideoResolution_270_270: 5,
+    /** 宽高比 1:1;分辨率 480x480;建议码率(VideoCall)350kbps; 建议码率(LIVE)500kbps */
+    TRTCVideoResolution_480_480: 7,
+    /** 宽高比4:3;分辨率 160x120;建议码率(VideoCall)100kbps; 建议码率(LIVE)150kbps */
+    TRTCVideoResolution_160_120: 50,
+    /** 宽高比 4:3;分辨率 240x180;建议码率(VideoCall)150kbps; 建议码率(LIVE)250kbps */
+    TRTCVideoResolution_240_180: 52,
+    /** 宽高比 4:3;分辨率 280x210;建议码率(VideoCall)200kbps; 建议码率(LIVE)300kbps */
+    TRTCVideoResolution_280_210: 54,
+    /** 宽高比 4:3;分辨率 320x240;建议码率(VideoCall)250kbps; 建议码率(LIVE)375kbps */
+    TRTCVideoResolution_320_240: 56,
+    /** 宽高比 4:3;分辨率 400x300;建议码率(VideoCall)300kbps; 建议码率(LIVE)450kbps */
+    TRTCVideoResolution_400_300: 58,
+    /** 宽高比 4:3;分辨率 480x360;建议码率(VideoCall)400kbps; 建议码率(LIVE)600kbps */
+    TRTCVideoResolution_480_360: 60,
+    /** 宽高比 4:3;分辨率 640x480;建议码率(VideoCall)600kbps; 建议码率(LIVE)900kbps */
+    TRTCVideoResolution_640_480: 62,
+    /** 宽高比 4:3;分辨率 960x720;建议码率(VideoCall)1000kbps; 建议码率(LIVE)1500kbps */
+    TRTCVideoResolution_960_720: 64,
+    /** 宽高比 16:9;分辨率 160x90;建议码率(VideoCall)150kbps; 建议码率(LIVE)250kbps */
+    TRTCVideoResolution_160_90: 100,
+    /** 宽高比 16:9;分辨率 256x144;建议码率(VideoCall)200kbps; 建议码率(LIVE)300kbps */
+    TRTCVideoResolution_256_144: 102,
+    /** 宽高比 16:9;分辨率 320x180;建议码率(VideoCall)250kbps; 建议码率(LIVE)400kbps */
+    TRTCVideoResolution_320_180: 104,
+    /** 宽高比 16:9;分辨率 480x270;建议码率(VideoCall)350kbps; 建议码率(LIVE)550kbps */
+    TRTCVideoResolution_480_270: 106,
+    /** 宽高比 16:9;分辨率 640x360;建议码率(VideoCall)500kbps; 建议码率(LIVE)900kbps */
+    TRTCVideoResolution_640_360: 108,
+    /** 宽高比 16:9;分辨率 960x540;建议码率(VideoCall)850kbps; 建议码率(LIVE)1300kbps */
+    TRTCVideoResolution_960_540: 110,
+    /** 宽高比 16:9;分辨率 1280x720;建议码率(VideoCall)1200kbps; 建议码率(LIVE)1800kbps */
+    TRTCVideoResolution_1280_720: 112,
+    /** 宽高比 16:9;分辨率 1920x1080;建议码率(VideoCall)2000kbps; 建议码率(LIVE)3000kbps */
+    TRTCVideoResolution_1920_1080: 114,
+};
+export var TRTCVideoResolution;
+(function (TRTCVideoResolution) {
+    TRTCVideoResolution[TRTCVideoResolution["TRTCVideoResolution_120_120"] = 1] = "TRTCVideoResolution_120_120";
+    TRTCVideoResolution[TRTCVideoResolution["TRTCVideoResolution_160_160"] = 3] = "TRTCVideoResolution_160_160";
+    TRTCVideoResolution[TRTCVideoResolution["TRTCVideoResolution_270_270"] = 5] = "TRTCVideoResolution_270_270";
+    TRTCVideoResolution[TRTCVideoResolution["TRTCVideoResolution_480_480"] = 7] = "TRTCVideoResolution_480_480";
+    TRTCVideoResolution[TRTCVideoResolution["TRTCVideoResolution_160_120"] = 50] = "TRTCVideoResolution_160_120";
+    TRTCVideoResolution[TRTCVideoResolution["TRTCVideoResolution_240_180"] = 52] = "TRTCVideoResolution_240_180";
+    TRTCVideoResolution[TRTCVideoResolution["TRTCVideoResolution_280_210"] = 54] = "TRTCVideoResolution_280_210";
+    TRTCVideoResolution[TRTCVideoResolution["TRTCVideoResolution_320_240"] = 56] = "TRTCVideoResolution_320_240";
+    TRTCVideoResolution[TRTCVideoResolution["TRTCVideoResolution_400_300"] = 58] = "TRTCVideoResolution_400_300";
+    TRTCVideoResolution[TRTCVideoResolution["TRTCVideoResolution_480_360"] = 60] = "TRTCVideoResolution_480_360";
+    TRTCVideoResolution[TRTCVideoResolution["TRTCVideoResolution_640_480"] = 62] = "TRTCVideoResolution_640_480";
+    TRTCVideoResolution[TRTCVideoResolution["TRTCVideoResolution_960_720"] = 64] = "TRTCVideoResolution_960_720";
+    TRTCVideoResolution[TRTCVideoResolution["TRTCVideoResolution_160_90"] = 100] = "TRTCVideoResolution_160_90";
+    TRTCVideoResolution[TRTCVideoResolution["TRTCVideoResolution_256_144"] = 102] = "TRTCVideoResolution_256_144";
+    TRTCVideoResolution[TRTCVideoResolution["TRTCVideoResolution_320_180"] = 104] = "TRTCVideoResolution_320_180";
+    TRTCVideoResolution[TRTCVideoResolution["TRTCVideoResolution_480_270"] = 106] = "TRTCVideoResolution_480_270";
+    TRTCVideoResolution[TRTCVideoResolution["TRTCVideoResolution_640_360"] = 108] = "TRTCVideoResolution_640_360";
+    TRTCVideoResolution[TRTCVideoResolution["TRTCVideoResolution_960_540"] = 110] = "TRTCVideoResolution_960_540";
+    TRTCVideoResolution[TRTCVideoResolution["TRTCVideoResolution_1280_720"] = 112] = "TRTCVideoResolution_1280_720";
+    TRTCVideoResolution[TRTCVideoResolution["TRTCVideoResolution_1920_1080"] = 114] = "TRTCVideoResolution_1920_1080";
+})(TRTCVideoResolution || (TRTCVideoResolution = {}));
+/**
+ * 视频分辨率模式<br>
+ * TRTCVideoResolution 中仅定义了横屏分辨率(如 640 × 360),如需使用竖屏分辨率(如 360 × 640),需要同时指定 TRTCVideoResolutionMode 为 Portrait
+ * @enum {Number}
+ */
+const TRTCVideoResolutionMode_HACK_JSDOC = {
+    /** 横屏分辨率 */
+    TRTCVideoResolutionModeLandscape: 0,
+    /** 竖屏分辨率 */
+    TRTCVideoResolutionModePortrait: 1,
+};
+export var TRTCVideoResolutionMode;
+(function (TRTCVideoResolutionMode) {
+    TRTCVideoResolutionMode[TRTCVideoResolutionMode["TRTCVideoResolutionModeLandscape"] = 0] = "TRTCVideoResolutionModeLandscape";
+    TRTCVideoResolutionMode[TRTCVideoResolutionMode["TRTCVideoResolutionModePortrait"] = 1] = "TRTCVideoResolutionModePortrait";
+})(TRTCVideoResolutionMode || (TRTCVideoResolutionMode = {}));
+;
+/**
+ * 视频流类型<br>
+ * TRTC 内部有三种不同的音视频流,分别是:
+ * - 高清大画面:一般用来传输摄像头的视频数据
+ * - 低清小画面:小画面和大画面的内容相互,但是分辨率和码率都比大画面低,因此清晰度也更低
+ * - 辅流画面:一般用于屏幕分享,同一时间在同一个房间中只允许一个用户发布辅流视频,其他用户必须要等该用户关闭之后才能发布自己的辅流
+ *
+ * **Note:**
+ * - 不支持单独开启低清小画面,小画面必须依附于大画面而存在,SDK 会自动设定低清小画面的分辨率和码率
+ * @enum {Number}
+ */
+const TRTCVideoStreamType_HACK_JSDOC = {
+    /** 大画面视频流 */
+    TRTCVideoStreamTypeBig: 0,
+    /** 小画面视频流 */
+    TRTCVideoStreamTypeSmall: 1,
+    /** 辅流(屏幕分享) */
+    TRTCVideoStreamTypeSub: 2,
+};
+export var TRTCVideoStreamType;
+(function (TRTCVideoStreamType) {
+    TRTCVideoStreamType[TRTCVideoStreamType["TRTCVideoStreamTypeBig"] = 0] = "TRTCVideoStreamTypeBig";
+    TRTCVideoStreamType[TRTCVideoStreamType["TRTCVideoStreamTypeSmall"] = 1] = "TRTCVideoStreamTypeSmall";
+    TRTCVideoStreamType[TRTCVideoStreamType["TRTCVideoStreamTypeSub"] = 2] = "TRTCVideoStreamTypeSub";
+})(TRTCVideoStreamType || (TRTCVideoStreamType = {}));
+/**
+ * 画面来源<br>
+ * TRTC 内部有两种不同的画面来源,分别是:
+ * - TRTCSnapshotSourceTypeStream: 视频流画面
+ * - TRTCSnapshotSourceTypeView: 视频渲染画面
+ *
+ * **Note:**
+ * - 截取视频流画面(TRTCSnapshotSourceTypeStream)一般更清晰。
+ * @enum {Number}
+ */
+const TRTCSnapshotSourceType_HACK_JSDOC = {
+    /** 视频流画面 */
+    TRTCSnapshotSourceTypeStream: 0,
+    /** 视频渲染画面 */
+    TRTCSnapshotSourceTypeView: 1,
+};
+export var TRTCSnapshotSourceType;
+(function (TRTCSnapshotSourceType) {
+    TRTCSnapshotSourceType[TRTCSnapshotSourceType["TRTCSnapshotSourceTypeStream"] = 0] = "TRTCSnapshotSourceTypeStream";
+    TRTCSnapshotSourceType[TRTCSnapshotSourceType["TRTCSnapshotSourceTypeView"] = 1] = "TRTCSnapshotSourceTypeView";
+})(TRTCSnapshotSourceType || (TRTCSnapshotSourceType = {}));
+/**
+ * 视频画面填充模式<br>
+ * 如果画面的显示分辨率不等于画面的原始分辨率,就需要您设置画面的填充模式:
+ * - TRTCVideoFillMode_Fill,图像铺满屏幕,超出显示视窗的视频部分将被截掉,所以画面显示可能不完整。
+ * - TRTCVideoFillMode_Fit,图像长边填满屏幕,短边区域会被填充黑色,但画面的内容肯定是完整的。
+ * @enum {Number}
+ */
+const TRTCVideoFillMode_HACK_JSDOC = {
+    /** 图像铺满屏幕,超出显示视窗的视频部分将被截掉 */
+    TRTCVideoFillMode_Fill: 0,
+    /** 图像长边填满屏幕,短边区域会被填充黑色 */
+    TRTCVideoFillMode_Fit: 1,
+};
+export var TRTCVideoFillMode;
+(function (TRTCVideoFillMode) {
+    TRTCVideoFillMode[TRTCVideoFillMode["TRTCVideoFillMode_Fill"] = 0] = "TRTCVideoFillMode_Fill";
+    TRTCVideoFillMode[TRTCVideoFillMode["TRTCVideoFillMode_Fit"] = 1] = "TRTCVideoFillMode_Fit";
+})(TRTCVideoFillMode || (TRTCVideoFillMode = {}));
+;
+/**
+ * 视频画面旋转方向<br>
+ * TRTC SDK 提供了对本地和远程画面的旋转角度设置 API,如下的旋转角度都是指顺时针方向的。
+ * @enum {Number}
+ */
+const TRTCVideoRotation_HACK_JSDOC = {
+    /** 顺时针旋转0度 */
+    TRTCVideoRotation_0: 0,
+    /** 顺时针旋转90度 */
+    TRTCVideoRotation_90: 1,
+    /** 顺时针旋转180度 */
+    TRTCVideoRotation_180: 2,
+    /** 顺时针旋转270度 */
+    TRTCVideoRotation_270: 3,
+};
+export var TRTCVideoRotation;
+(function (TRTCVideoRotation) {
+    TRTCVideoRotation[TRTCVideoRotation["TRTCVideoRotation_0"] = 0] = "TRTCVideoRotation_0";
+    TRTCVideoRotation[TRTCVideoRotation["TRTCVideoRotation_90"] = 1] = "TRTCVideoRotation_90";
+    TRTCVideoRotation[TRTCVideoRotation["TRTCVideoRotation_180"] = 2] = "TRTCVideoRotation_180";
+    TRTCVideoRotation[TRTCVideoRotation["TRTCVideoRotation_270"] = 3] = "TRTCVideoRotation_270";
+})(TRTCVideoRotation || (TRTCVideoRotation = {}));
+/**
+ * 画面渲染镜像类型<br>
+ * TRTC 的画面镜像提供下列设置模式
+ * @enum {Number}
+ */
+const TRTCVideoMirrorType_HACK_JSDOC = {
+    /** 只适用于移动端, 本地预览时,前置摄像头镜像,后置摄像头不镜像 */
+    TRTCVideoMirrorType_Auto: 0,
+    /** 所有画面均镜像 */
+    TRTCVideoMirrorType_Enable: 1,
+    /** 所有画面均不镜像 */
+    TRTCVideoMirrorType_Disable: 2
+};
+export var TRTCVideoMirrorType;
+(function (TRTCVideoMirrorType) {
+    TRTCVideoMirrorType[TRTCVideoMirrorType["TRTCVideoMirrorType_Auto"] = 0] = "TRTCVideoMirrorType_Auto";
+    TRTCVideoMirrorType[TRTCVideoMirrorType["TRTCVideoMirrorType_Enable"] = 1] = "TRTCVideoMirrorType_Enable";
+    TRTCVideoMirrorType[TRTCVideoMirrorType["TRTCVideoMirrorType_Disable"] = 2] = "TRTCVideoMirrorType_Disable";
+})(TRTCVideoMirrorType || (TRTCVideoMirrorType = {}));
+/**
+ * 美颜(磨皮)算法<br>
+ * TRTC SDK 内置了多种不同的磨皮算法,您可以选择最适合您产品定位的方案。
+ * @enum {Number}
+ */
+const TRTCBeautyStyle_HACK_JSDOC = {
+    /** 光滑,算法比较激进,磨皮效果比较明显,适用于秀场直播 */
+    TRTCBeautyStyleSmooth: 0,
+    /** 自然,算法更多地保留了面部细节,磨皮效果更加自然,适用于绝大多数直播场景 */
+    TRTCBeautyStyleNature: 1,
+    /** 优图,由优图实验室提供,磨皮效果介于光滑和自然之间,比光滑保留更多皮肤细节,比自然磨皮程度更高 */
+    TRTCBeautyStylePitu: 2,
+};
+export var TRTCBeautyStyle;
+(function (TRTCBeautyStyle) {
+    TRTCBeautyStyle[TRTCBeautyStyle["TRTCBeautyStyleSmooth"] = 0] = "TRTCBeautyStyleSmooth";
+    TRTCBeautyStyle[TRTCBeautyStyle["TRTCBeautyStyleNature"] = 1] = "TRTCBeautyStyleNature";
+    TRTCBeautyStyle[TRTCBeautyStyle["TRTCBeautyStylePitu"] = 2] = "TRTCBeautyStylePitu";
+})(TRTCBeautyStyle || (TRTCBeautyStyle = {}));
+/**
+ * 背景音效<br>
+ * @enum {Number}
+ */
+export class AudioMusicParam {
+    constructor(id, path, loopCount, publish, isShortFile, startTimeMS, endTimeMS) {
+        this.id = id;
+        this.path = path;
+        this.loopCount = loopCount;
+        this.publish = publish;
+        this.isShortFile = isShortFile;
+        this.startTimeMS = startTimeMS;
+        this.endTimeMS = endTimeMS;
+    }
+}
+/////////////////////////////////////////////////////////////////////////////////
+//
+//                    【(二)网络相关枚举值定义】
+//
+/////////////////////////////////////////////////////////////////////////////////
+/**
+ * 应用场景<br>
+ * TRTC 可用于视频会议和在线直播等多种应用场景,针对不同的应用场景,TRTC SDK 的内部会进行不同的优化配置:
+ * - TRTCAppSceneVideoCall    :视频通话场景,适合[1对1视频通话]、[300人视频会议]、[在线问诊]、[视频聊天]、[远程面试]等。
+ * - TRTCAppSceneLIVE         :视频互动直播,适合[视频低延时直播]、[十万人互动课堂]、[视频直播 PK]、[视频相亲房]、[互动课堂]、[远程培训]、[超大型会议]等。
+ * - TRTCAppSceneAudioCall    :语音通话场景,适合[1对1语音通话]、[300人语音会议]、[语音聊天]、[语音会议]、[在线狼人杀]等。
+ * - TRTCAppSceneVoiceChatRoom:语音互动直播,适合:[语音低延时直播]、[语音直播连麦]、[语聊房]、[K 歌房]、[FM 电台]等。
+ * @enum {Number}
+ */
+const TRTCAppScene_HACK_JSDOC = {
+    /**
+     * 视频通话场景,支持720P、1080P高清画质,单个房间最多支持300人同时在线,最高支持50人同时发言。<br>
+     * 适合:[视频低延时直播]、[十万人互动课堂]、[视频直播 PK]、[视频相亲房]、[互动课堂]、[远程培训]、[超大型会议]等。<br>
+     * 注意:此场景下,您必须通过 TRTCParams 中的 role 字段指定当前用户的角色。
+     */
+    TRTCAppSceneVideoCall: 0,
+    /**
+     * 视频互动直播,支持平滑上下麦,切换过程无需等待,主播延时小于300ms;支持十万级别观众同时播放,播放延时低至1000ms。<br>
+     * 在线直播场景,内部编码器和网络协议优化侧重性能和兼容性,性能和清晰度表现更佳。
+     */
+    TRTCAppSceneLIVE: 1,
+    /**
+     * 语音通话场景,支持 48kHz,支持双声道。单个房间最多支持300人同时在线,最高支持50人同时发言。<br>
+     * 适合:[1对1语音通话]、[300人语音会议]、[语音聊天]、[语音会议]、[在线狼人杀]等。
+     */
+    TRTCAppSceneAudioCall: 2,
+    /**
+     * 语音互动直播,支持平滑上下麦,切换过程无需等待,主播延时小于300ms;支持十万级别观众同时播放,播放延时低至1000ms。<br>
+     * 适合:[语音低延时直播]、[语音直播连麦]、[语聊房]、[K 歌房]、[FM 电台]等。<br>
+     * 注意:此场景下,您必须通过 TRTCParams 中的 role 字段指定当前用户的角色。
+     */
+    TRTCAppSceneVoiceChatRoom: 3,
+};
+export var TRTCAppScene;
+(function (TRTCAppScene) {
+    TRTCAppScene[TRTCAppScene["TRTCAppSceneVideoCall"] = 0] = "TRTCAppSceneVideoCall";
+    TRTCAppScene[TRTCAppScene["TRTCAppSceneLIVE"] = 1] = "TRTCAppSceneLIVE";
+    TRTCAppScene[TRTCAppScene["TRTCAppSceneAudioCall"] = 2] = "TRTCAppSceneAudioCall";
+    TRTCAppScene[TRTCAppScene["TRTCAppSceneVoiceChatRoom"] = 3] = "TRTCAppSceneVoiceChatRoom";
+})(TRTCAppScene || (TRTCAppScene = {}));
+/**
+ * 角色,仅适用于直播场景(TRTCAppSceneLIVE 和 TRTCAppSceneVoiceChatRoom)<br>
+ * 在直播场景中,多数用户只是观众,只有个别用户是主播,这种角色区分可以有利于 TRTC 进行更好的定向优化。
+ * - Anchor:主播,可以上行视频和音频,一个房间里最多支持50个主播同时上行音视频。
+ * - Audience:观众,只能观看,不能上行视频和音频,一个房间里的观众人数没有上限。
+ *
+ * @enum {Number}
+ */
+const TRTCRoleType_HACK_JSDOC = {
+    /** 主播 */
+    TRTCRoleAnchor: 20,
+    /** 观众 */
+    TRTCRoleAudience: 21,
+};
+export var TRTCRoleType;
+(function (TRTCRoleType) {
+    TRTCRoleType[TRTCRoleType["TRTCRoleAnchor"] = 20] = "TRTCRoleAnchor";
+    TRTCRoleType[TRTCRoleType["TRTCRoleAudience"] = 21] = "TRTCRoleAudience";
+})(TRTCRoleType || (TRTCRoleType = {}));
+/////////////////////////////////////////////////////////////////////////////////
+//
+//                    【(三)音频相关枚举值定义】
+//
+/////////////////////////////////////////////////////////////////////////////////
+/**
+ * 音频质量<br>
+ * @enum {Number}
+ */
+const TRTCAudioQuality_HACK_JSDOC = {
+    /** 人声模式:适用于以人声沟通为主的应用场景,该模式下音频传输的抗性较强,TRTC 会通过各种人声处理技术保障在弱网络环境下的流畅度最佳 */
+    TRTCAudioQualitySpeech: 1,
+    /** 标准模式(或者默认模式):介于 Speech 和 Music 之间的档位,对音乐的还原度比人声模式要好,但传输数据量比音乐模式要低很多,对各种场景均有不错的适应性,如无特殊需求推荐选择之。 */
+    TRTCAudioQualityDefault: 2,
+    /** 音乐模式:适用于对声乐要求很苛刻的场景,该模式下音频传输的数据量很大,TRTC 会通过各项技术确保音乐信号在各频段均能获得高保真的细节还原度 */
+    TRTCAudioQualityMusic: 3
+};
+export var TRTCAudioQuality;
+(function (TRTCAudioQuality) {
+    TRTCAudioQuality[TRTCAudioQuality["TRTCAudioQualitySpeech"] = 1] = "TRTCAudioQualitySpeech";
+    TRTCAudioQuality[TRTCAudioQuality["TRTCAudioQualityDefault"] = 2] = "TRTCAudioQualityDefault";
+    TRTCAudioQuality[TRTCAudioQuality["TRTCAudioQualityMusic"] = 3] = "TRTCAudioQualityMusic";
+})(TRTCAudioQuality || (TRTCAudioQuality = {}));
+/////////////////////////////////////////////////////////////////////////////////
+//
+//                      【(四)TRTC 核心类型定义】
+//
+/////////////////////////////////////////////////////////////////////////////////
+/**
+ * 进房相关参数<br>
+ * 只有该参数填写正确,才能顺利调用 enterRoom 进入 roomId 所指定的音视频房间。
+ * @param {Number}       sdkAppId      - 【字段含义】应用标识(必填),腾讯视频云基于 sdkAppId 完成计费统计。<br>
+ *                                       【推荐取值】在腾讯云 [TRTC 控制台](https://console.cloud.tencent.com/rav/) 中创建应用,之后可以在账号信息页面中得到该 ID。<br>
+ * @param {String}       userId        - 【字段含义】用户标识(必填)。当前用户的 userId,相当于用户名,UTF-8编码。<br>
+ *                                       【推荐取值】如果一个用户在您的账号系统中的 ID 为“abc”,则 userId 即可设置为“abc”。<br>
+ * @param {String}       userSig       - 【字段含义】用户签名(必填),当前 userId 对应的验证签名,相当于登录密码。<br>
+ *                                       【推荐取值】请参考 [如何计算UserSig](https://cloud.tencent.com/document/product/647/17275)。<br>
+ * @param {Number}       roomId        - 【字段含义】房间号码(必填),指定房间号,在同一个房间里的用户(userId)可以彼此看到对方并进行视频通话, roomId 和 strRoomId 必须填一个, 若您选用 strRoomId,则 roomId 需要填写为0。<br>
+ *                                       【推荐取值】您可以随意指定,但请不要重复,如果您的用户账号 ID 是数字类型的,可以直接用创建者的用户 ID 作为 roomId。<br>
+ * @param {String}       strRoomId     - 【字段含义】字符串房间号码(选填),roomId 和 strRoomId 必须填一个。若两者都填,则优先选择 roomId。<br>
+ *                                       【推荐取值】您可以随意指定,但请不要重复。<br>
+ * @param {TRTCRoleType} role          - 【字段含义】直播场景下的角色,仅适用于直播场景(TRTCAppSceneLIVE 和 TRTCAppSceneVoiceChatRoom),视频通话场景下指定无效。<br>
+ *                                       【推荐取值】默认值:主播(TRTCRoleAnchor)<br>
+ * @param {String}       privateMapKey - 【字段含义】房间签名(非必填),如果您希望某个房间只能让特定的某些 userId 进入,就需要使用 privateMapKey 进行权限保护。<br>
+ *                                       【推荐取值】仅建议有高级别安全需求的客户使用,参考文档:[进房权限保护](https://cloud.tencent.com/document/product/647/32240)<br>
+ * @param {String}       businessInfo  - 【字段含义】业务数据(非必填),某些非常用的高级特性才需要用到此字段。<br>
+ *                                       【推荐取值】不建议使用<br>
+ * @param {String}       streamId      - 【字段含义】绑定腾讯云直播 CDN 流 ID[非必填],设置之后,您就可以在腾讯云直播 CDN 上通过标准直播方案(FLV或HLS)播放该用户的音视频流。<br>
+ *                                       【推荐取值】限制长度为64字节,可以不填写,一种推荐的方案是使用 “sdkappid_roomid_userid_main” 作为 streamid,这样比较好辨认且不会在您的多个应用中发生冲突。<br>
+ *                                       【特殊说明】要使用腾讯云直播 CDN,您需要先在[控制台](https://console.cloud.tencent.com/trtc/) 中的功能配置页开启“启动自动旁路直播”开关。<br>
+ *                                       【参考文档】[CDN 旁路直播](https://cloud.tencent.com/document/product/647/16826)。
+ * @param {String}       userDefineRecordId - 【字段含义】设置云端录制完成后的回调消息中的 "userdefinerecordid"  字段内容,便于您更方便的识别录制回调。<br>
+ *                                            【推荐取值】限制长度为64字节,只允许包含大小写英文字母(a-zA-Z)、数字(0-9)及下划线和连词符。<br>
+ *                                            【参考文档】[云端录制](https://cloud.tencent.com/document/product/647/16823)。
+ */
+export class TRTCParams {
+    constructor(sdkAppId, userId, roomId, userSig, strRoomId, privateMapKey, role, businessInfo, streamId, userDefineRecordId) {
+        this.sdkAppId = sdkAppId;
+        this.userId = userId;
+        this.roomId = roomId;
+        this.userSig = userSig;
+        this.strRoomId = strRoomId;
+        this.privateMapKey = privateMapKey;
+        this.role = role;
+        this.businessInfo = businessInfo;
+        this.streamId = streamId;
+        this.userDefineRecordId = userDefineRecordId;
+    }
+}
+/**
+ * 视频编码参数<br>
+ * 该设置决定了远端用户看到的画面质量(同时也是云端录制出的视频文件的画面质量)。
+ * @param {TRTCVideoResolution}     videoResolution - 【字段含义】 视频分辨率<br>
+ *                                                    【推荐取值】 <br>
+ *                                                     - 视频通话建议选择360 × 640及以下分辨率,resMode 选择 Portrait。<br>
+ *                                                     - 手机直播建议选择 540 × 960,resMode 选择 Portrait。<br>
+ *                                                     - Window 和 iMac 建议选择 640 × 360 及以上分辨率,resMode 选择 Landscape。
+ *                                                    【特别说明】 TRTCVideoResolution 默认只能横屏模式的分辨率,例如640 × 360。<br>
+ *                                                                如需使用竖屏分辨率,请指定 resMode 为 Portrait,例如640 × 360结合 Portrait 则为360 × 640。<br>
+ * @param {TRTCVideoResolutionMode} resMode         - 【字段含义】分辨率模式(横屏分辨率 - 竖屏分辨率)<br>
+ *                                                    【推荐取值】手机直播建议选择 Portrait,Window 和 Mac 建议选择 Landscape。<br>
+ *                                                    【特别说明】如果 videoResolution 指定分辨率 640 × 360,resMode 指定模式为 Portrait,则最终编码出的分辨率为360 × 640。<br>
+ * @param {Number}                  videoFps        - 【字段含义】视频采集帧率<br>
+ *                                                    【推荐取值】15fps 或 20fps,10fps 以下会有轻微卡顿感,5fps 以下卡顿感明显,20fps 以上的帧率则过于浪费(电影的帧率也只有 24fps)。<br>
+ *                                                    【特别说明】很多 Android 手机的前置摄像头并不支持15fps以上的采集帧率,部分过于突出美颜功能的 Android 手机前置摄像头的采集帧率可能低于10fps。<br>
+ * @param {Number}                  videoBitrate    - 【字段含义】视频上行码率<br>
+ *                                                    【推荐取值】推荐设置请参考本文件前半部分 TRTCVideoResolution 定义处的注释说明<br>
+ *                                                    【特别说明】码率太低会导致视频中有很多的马赛克<br>
+ * @param {Number}                  minVideoBitrate  -【字段含义】最低视频码率,SDK 会在网络不佳的情况下主动降低视频码率,最低会降至 minVideoBitrate 所设定的数值。
+ *                                                    【推荐取值】<br>
+ *                                                      - 如果您追求“允许卡顿但要保持清晰”的效果,可以设置 minVideoBitrate 为 videoBitrate 的 60%;
+ *                                                      - 如果您追求“允许模糊但要保持流畅”的效果,可以设置 minVideoBitrate 为 200kbps;
+ *                                                      - 如果您将 videoBitrate 和 minVideoBitrate 设置为同一个值,等价于关闭 SDK 的自适应调节能力;
+ *                                                      - 默认值:0,此时最低码率由 SDK 根据分辨率情况,自动设置合适的数值。<br>
+ *                                                    【特别说明】<br>
+ *                                                     - 当您把分辨率设置的比较高时,minVideoBitrate 不适合设置的太低,否则会出现画面模糊和大范围的马赛克宏块。
+ *                                                       比如把分辨率设置为 720p,把码率设置为 200kbps,那么编码出的画面将会出现大范围区域性马赛克。
+ * @param {Boolean}                 enableAdjustRes - 【字段含义】是否允许调整分辨率<br>
+ *                                                    【推荐取值】 <br>
+ *                                                     - 手机直播建议选择 NO。<br>
+ *                                                     - 视频通话模式,若更关注流畅性,建议选择 YES,此时若遇到带宽有限的弱网,SDK 会自动降低分辨率以保障更好的流畅度(仅针对 TRTCVideoStreamTypeBig 生效)。
+ *                                                     - 默认值:NO。<br>
+ *                                                    【特别说明】若有录制需求,选择 YES 时,请确保通话过程中,调整分辨率不会影响您的录制效果。<br>
+ */
+export class TRTCVideoEncParam {
+    constructor(videoResolution = TRTCVideoResolution.TRTCVideoResolution_640_360, resMode = TRTCVideoResolutionMode.TRTCVideoResolutionModePortrait, videoFps = 15, videoBitrate = 550, minVideoBitrate = 0, enableAdjustRes = false) {
+        this.videoResolution = videoResolution;
+        this.videoResolutionMode = resMode;
+        this.videoFps = videoFps;
+        this.videoBitrate = videoBitrate;
+        this.minVideoBitrate = minVideoBitrate;
+        this.enableAdjustRes = enableAdjustRes;
+    }
+}
+;
+/**
+ * 画面渲染参数<br>
+ * 您可以通过设置此参数来控制画面的旋转、填充、镜像模式
+ * @param {TRTCVideoRotation} rotation  - 【字段含义】视频画面旋转方向
+ * @param {TRTCVideoFillMode} fillMode  - 【字段含义】视频画面填充模式
+ * @param {TRTCVideoMirrorType} mirrorType  - 【字段含义】画面渲染镜像类型
+ */
+export class TRTCRenderParams {
+    constructor(rotation = TRTCVideoRotation.TRTCVideoRotation_0, fillMode = TRTCVideoFillMode.TRTCVideoFillMode_Fit, mirrorType = TRTCVideoMirrorType.TRTCVideoMirrorType_Disable) {
+        this.rotation = rotation;
+        this.fillMode = fillMode;
+        this.mirrorType = mirrorType;
+    }
+}
+/**
+ * 音频路由(即声音的播放模式)<br>
+ * @enum {Number}
+ */
+const TRTCAudioRoute_HACK_JSDOC = {
+    /** 使用扬声器播放(即“免提”),扬声器位于手机底部,声音偏大,适合外放音乐 */
+    TRTCAudioRouteSpeaker: 0,
+    /** 使用听筒播放,听筒位于手机顶部,声音偏小,适合需要保护隐私的通话场景 */
+    TRTCAudioRouteEarpiece: 1,
+};
+export var TRTCAudioRoute;
+(function (TRTCAudioRoute) {
+    TRTCAudioRoute[TRTCAudioRoute["TRTCAudioRouteSpeaker"] = 0] = "TRTCAudioRouteSpeaker";
+    TRTCAudioRoute[TRTCAudioRoute["TRTCAudioRouteEarpiece"] = 1] = "TRTCAudioRouteEarpiece";
+})(TRTCAudioRoute || (TRTCAudioRoute = {}));
+/////////////////////////////////////////////////////////////////////////////////
+//
+//                    【其它参数】
+//
+/////////////////////////////////////////////////////////////////////////////////
+export var TRTCShareSource;
+(function (TRTCShareSource) {
+    TRTCShareSource["InApp"] = "InApp";
+    TRTCShareSource["ByReplaykit"] = "ByReplaykit";
+})(TRTCShareSource || (TRTCShareSource = {}));

+ 10 - 0
TrtcCloud/lib/constants.js

@@ -0,0 +1,10 @@
+export const NAME = {
+    ANDROID: 'android',
+    IOS: 'ios',
+    STRING: 'string',
+    FUNCTION: 'function',
+    BOOLEAN: 'boolean',
+    NUMBER: 'number',
+    LOG_PREFIX: '【UniApp-JS】',
+};
+export const errorCodeUrl = 'https://web.sdk.qcloud.com/trtc/uniapp/doc/zh-cn/ErrorCode.html';

+ 944 - 0
TrtcCloud/lib/index.js

@@ -0,0 +1,944 @@
+import TrtcCloudImpl from './TrtcCloudImpl';
+import { TRTCVideoStreamType } from './TrtcDefines';
+const version = '1.3.1';
+export * from './TrtcDefines';
+/**
+ * TrtcCloud
+ *
+ * @class TrtcCloud
+ */
+export default class TrtcCloud {
+    /**
+     * 创建 TrtcCloud 单例
+     *
+     * @static
+     * @memberof TrtcCloud
+     * @example
+     * TrtcCloud.createInstance();
+     */
+    static createInstance() {
+        console.log('----------------------------------------------------------------');
+        console.log(`                        SDK ${version}                    `);
+        console.log('----------------------------------------------------------------');
+        return TrtcCloudImpl._createInstance();
+    }
+    /**
+     * 销毁 TrtcCloud 单例
+     *
+     * @static
+     * @memberof TrtcCloud
+     * @example
+     * TrtcCloud.destroyInstance();
+     */
+    static destroyInstance() {
+        return TrtcCloudImpl._destroyInstance();
+    }
+    /**
+     * 设置 TrtcCloud 事件监听
+     *
+     * @param {String} event 事件名称
+     * @param {Function} callback 事件回调
+     * @memberof TrtcCloud
+     *
+     * @example
+     * this.trtcCloud = TrtcCloud.createInstance(); // 创建 trtcCloud 实例
+     * this.trtcCloud.on('onEnterRoom', (res) => {});
+     */
+    on(event, callback) {
+        return TrtcCloudImpl._getInstance().on(event, callback);
+    }
+    /**
+     * 取消事件绑定<br>
+     *
+     * @param {String} event 事件名称,传入通配符 '*' 会解除所有事件绑定。
+     * @memberof TrtcCloud
+     * @example
+     * this.trtcCloud.off('onEnterRoom');
+     *
+     * this.trtcCloud.off('*'); // 取消所有绑定的事件
+     */
+    off(event) {
+        return TrtcCloudImpl._getInstance().off(event);
+    }
+    /**
+     * 进房<br>
+     * 调用接口后,您会收到来自 TRTCCallback 中的 [onEnterRoom(result)]{@link TRTCCallback#onEnterRoom} 回调
+     * 如果加入成功,result 会是一个正数(result > 0),表示加入房间所消耗的时间,单位是毫秒(ms)。<br>
+     * 如果加入失败,result 会是一个负数(result < 0),表示进房失败的错误码。
+     *
+     * * 参数 scene 的枚举值如下:
+     * - {@link TRTCAppSceneVideoCall}:<br>
+     *          视频通话场景,支持720P、1080P高清画质,单个房间最多支持300人同时在线,最高支持50人同时发言。<br>
+     *          适合:[1对1视频通话]、[300人视频会议]、[在线问诊]、[视频聊天]、[远程面试]等。<br>
+     * - {@link TRTCAppSceneAudioCall}:<br>
+     *          语音通话场景,支持 48kHz,支持双声道。单个房间最多支持300人同时在线,最高支持50人同时发言。<br>
+     *          适合:[1对1语音通话]、[300人语音会议]、[语音聊天]、[语音会议]、[在线狼人杀]等。<br>
+     * - {@link TRTCAppSceneLIVE}:<br>
+     *          视频互动直播,支持平滑上下麦,切换过程无需等待,主播延时小于300ms;支持十万级别观众同时播放,播放延时低至1000ms。<br>
+     *          适合:[视频低延时直播]、[十万人互动课堂]、[视频直播 PK]、[视频相亲房]、[互动课堂]、[远程培训]、[超大型会议]等。<br>
+     * - {@link TRTCAppSceneVoiceChatRoom}:<br>
+     *          语音互动直播,支持平滑上下麦,切换过程无需等待,主播延时小于300ms;支持十万级别观众同时播放,播放延时低至1000ms。<br>
+     *          适合:[语音低延时直播]、[语音直播连麦]、[语聊房]、[K 歌房]、[FM 电台]等。<br>
+     *
+     * **Note:**
+     * 1. 当 scene 选择为 TRTCAppSceneLIVE 或 TRTCAppSceneVoiceChatRoom 时,您必须通过 TRTCParams 中的 role 字段指定当前用户的角色。
+     * 2. 不管进房是否成功,enterRoom 都必须与 exitRoom 配对使用,在调用 `exitRoom` 前再次调用 `enterRoom` 函数会导致不可预期的错误问题。
+     *
+     * @param {TRTCParams} params - 进房参数
+     * @param {Number} params.sdkAppId      - 应用标识(必填)
+     * @param {String} params.userId        - 用户标识(必填)
+     * @param {String} params.userSig       - 用户签名(必填)
+     * @param {Number} params.roomId        - 房间号码, roomId 和 strRoomId 必须填一个, 若您选用 strRoomId,则 roomId 需要填写为0。
+     * @param {String} params.strRoomId     - 字符串房间号码 [选填],在同一个房间内的用户可以看到彼此并进行视频通话, roomId 和 strRoomId 必须填一个。若两者都填,则优先选择 roomId
+     * @param {TRTCRoleType} params.role    - 直播场景下的角色,默认值:主播
+     * - TRTCRoleAnchor: 主播,可以上行视频和音频,一个房间里最多支持50个主播同时上行音视频。
+     * - TRTCRoleAudience: 观众,只能观看,不能上行视频和音频,一个房间里的观众人数没有上限。
+     * @param {String=} params.privateMapKey - 房间签名(非必填)
+     * @param {String=} params.businessInfo  - 业务数据(非必填)
+     * @param {String=} params.streamId      - 自定义 CDN 播放地址(非必填)
+     * @param {String=} params.userDefineRecordId - 设置云端录制完成后的回调消息中的 "userdefinerecordid" 字段内容,便于您更方便的识别录制回调(非必填)
+     * @param {TRTCAppScene} scene 应用场景,目前支持视频通话(TRTCAppSceneVideoCall)、语音通话(TRTCAppSceneAudioCall)、在线直播(TRTCAppSceneLIVE)、语音聊天室(VTRTCAppSceneVoiceChatRoom)四种场景,
+     * 详见 [TrtcDefines] 中 TRTCAppScene 参数定义
+     *
+     * @memberof TrtcCloud
+     * @example
+     * import { TRTCAppScene } from '@/TrtcCloud/lib/TrtcDefines';
+     * this.trtcCloud = TrtcCloud.createInstance(); // 创建实例,只需创建一次
+     * const params = {
+     *   sdkAppId: 0,
+     *   userId: 'xxx',
+     *   roomId: 12345,
+     *   userSig: 'xxx'
+     * };
+     * this.trtcCloud.enterRoom(params, TRTCAppScene.TRTCAppSceneVideoCall);
+     */
+    enterRoom(params, scene) {
+        return TrtcCloudImpl._getInstance().enterRoom(params, scene);
+    }
+    /**
+     * 退房<br>
+     * 执行退出房间的相关逻辑释放资源后,SDK 会通过 `onExitRoom()` 回调通知到您
+     *
+     * **Note:**
+     * 1. 如果您要再次调用 `enterRoom()` 或者切换到其它的音视频 SDK,请等待 `onExitRoom()` 回调到来后再执行相关操作,否则可能会遇到如摄像头、麦克风设备被强占等各种异常问题。
+     *
+     * @memberof TrtcCloud
+     * @example
+     * this.trtcCloud.exitRoom();
+     */
+    exitRoom() {
+        return TrtcCloudImpl._getInstance().exitRoom();
+    }
+    /**
+     * 切换角色,仅适用于直播场景(TRTCAppSceneLIVE 和 TRTCAppSceneVoiceChatRoom)
+     *
+     * 在直播场景下,一个用户可能需要在“观众”和“主播”之间来回切换。
+     * 您可以在进房前通过 TRTCParams 中的 role 字段确定角色,也可以通过 switchRole 在进房后切换角色。
+     *
+     * @param {TRTCRoleType} role - 目标角色,默认为主播
+     * - TRTCRoleAnchor: 主播,可以上行视频和音频,一个房间里最多支持50个主播同时上行音视频。
+     * - TRTCRoleAudience: 观众,只能观看,不能上行视频和音频,一个房间里的观众人数没有上限。
+     *
+     * @memberof TrtcCloud
+     * @example
+     * import { TRTCRoleType } from '@/TrtcCloud/lib/TrtcDefines';
+     * this.trtcCloud.switchRole(TRTCRoleType.TRTCRoleAudience);
+     */
+    switchRole(role) {
+        return TrtcCloudImpl._getInstance().switchRole(role);
+    }
+    /**
+     * 请求跨房通话
+     *
+     * 默认情况下,只有同一个房间中的用户之间可以进行音视频通话,不同的房间之间的音视频流是相互隔离的。
+     * 使用该接口让身处两个不同房间中的主播进行跨房间的音视频流分享,从而让每个房间中的观众都能观看到这两个主播的音视频。
+     * 跨房通话的请求结果会通过监听 [onConnectOtherRoom](https://web.sdk.qcloud.com/trtc/uniapp/doc/zh-cn/TRTCCallback.html#event:onConnectOtherRoom) 事件通知给您。
+     *
+     * @param {Object} params - 跨房通话参数
+     * - 如果对端的房间号为数字,那么传入的参数为 roomId。
+     * - 如果对端的房间号为字符串,那么传入的参数为 strRoomId。
+     * - 针对对端的房间号类型传递对应参数,不需要两个同时传递。具体请看 example 的使用。
+     * @param {Number} params.roomId 跨房通话时对端的数字房间号 roomId(与 strRoomId 选填其中一个,不可同时传递)
+     * @param {String} params.strRoomId 跨房通话时对端的字符串房间号 strRoomId(与 roomId 选填其中一个,不可同时传递)
+     * @param {String} params.userId 跨房通话时对端的 userId(必填)
+     *
+     *
+     * @memberof TrtcCloud
+     * @example
+     * this.trtcCloud.connectOtherRoom({"roomId": 1233, "userId": "user_11"});
+     * this.trtcCloud.connectOtherRoom({"strRoomId": "1233", "userId": "user_22"});
+     */
+    connectOtherRoom(params) {
+        return TrtcCloudImpl._getInstance().connectOtherRoom(params);
+    }
+    /**
+     * 退出跨房通话
+     *
+     * 退出跨房通话的请求结果会通过监听 [onDisconnectOtherRoom](https://web.sdk.qcloud.com/trtc/uniapp/doc/zh-cn/TRTCCallback.html#event:onDisconnectOtherRoom) 事件通知给您。
+     *
+     * @memberof TrtcCloud
+     * @example
+     * this.trtcCloud.disconnectOtherRoom();
+     */
+    disconnectOtherRoom() {
+        return TrtcCloudImpl._getInstance().disconnectOtherRoom();
+    }
+    /**
+     * 开启本地视频的预览画面<br>
+     * 当开始渲染首帧摄像头画面时,您会收到 `onFirstVideoFrame(null)` 回调
+     *
+     * @param {Boolean} isFrontCamera 前置、后置摄像头,true:前置摄像头;false:后置摄像头,**默认为 true**
+     * @param {String=} viewId 用于承载视频画面的渲染控件,使用原生插件中的 TRTCCloudUniPlugin-TXLocalViewComponent component,需要提供 viewId 属性值,例如 viewId=userId
+     * @memberof TrtcCloud
+     * @example
+     * // 预览本地画面
+     * const viewId = this.userId;
+     * this.trtcCloud.startLocalPreview(true, viewId);
+     */
+    startLocalPreview(isFrontCamera = true, viewId) {
+        return TrtcCloudImpl._getInstance().startLocalPreview(isFrontCamera, viewId);
+    }
+    /**
+     * 设置视频编码器的编码参数
+     * - 该设置能够决定远端用户看到的画面质量,同时也能决定云端录制出的视频文件的画面质量。
+     * @param {TRTCVideoEncParam} param 用于设置视频编码器的相关参数
+     * @memberof TrtcCloud
+     * @example
+     *
+     * import { TRTCVideoResolution, TRTCVideoResolutionMode, TRTCVideoEncParam } from '@/TrtcCloud/lib/TrtcDefines';
+     * const videoResolution = TRTCVideoResolution.TRTCVideoResolution_480_360;
+     * const videoResolutionMode = TRTCVideoResolutionMode.TRTCVideoResolutionModeLandscape; // 横屏采集
+     * const videoFps = 15;
+     * const videoBitrate = 900;
+     * const minVideoBitrate = 200;
+     * const enableAdjustRes = false;
+     * // const param = new TRTCVideoEncParam(videoResolution, videoResolutionMode, videoFps, videoBitrate, minVideoBitrate, enableAdjustRes); // v1.1.0 方式
+     *
+     * const param = { // v1.2.0 以上版本支持的方式
+     *  videoResolution,
+     *  videoResolutionMode,
+     *  videoFps,
+     *  videoBitrate,
+     *  minVideoBitrate,
+     *  enableAdjustRes,
+     * };
+     *
+     * this.trtcCloud.setVideoEncoderParam(param);
+     */
+    setVideoEncoderParam(param) {
+        return TrtcCloudImpl._getInstance().setVideoEncoderParam(param);
+    }
+    /**
+     * 切换前置或后置摄像头
+     *
+     * @param {Boolean} isFrontCamera 前置、后置摄像头,true:前置摄像头;false:后置摄像头
+     * @memberof TrtcCloud
+     * @example
+     * // 切换前置或后置摄像头
+     * const isFrontCamera = true;
+     * this.trtcCloud.switchCamera(isFrontCamera);
+     */
+    switchCamera(isFrontCamera) {
+        return TrtcCloudImpl._getInstance().switchCamera(isFrontCamera);
+    }
+    /**
+     * 停止本地视频采集及预览
+     *
+     * @memberof TrtcCloud
+     * @example
+     * this.trtcCloud.stopLocalPreview();
+     */
+    stopLocalPreview() {
+        return TrtcCloudImpl._getInstance().stopLocalPreview();
+    }
+    /**
+     * 设置本地画面的渲染参数,可设置的参数包括有:画面的旋转角度、填充模式以及左右镜像等。
+     * @param {TRTCRenderParams} params - 本地图像的参数
+     * @param {TRTCVideoRotation} params.rotation - 图像的顺时针旋转角度,支持90、180以及270旋转角度,默认值:TRTCVideoRotation.TRTCVideoRotation_0
+     * @param {TRTCVideoFillMode} params.fillMode - 视频画面填充模式,填充(画面可能会被拉伸裁剪)或适应(画面可能会有黑边),默认值:TRTCVideoFillMode.TRTCVideoFillMode_Fill
+     * @param {TRTCVideoMirrorType} params.mirrorType - 画面镜像模式,默认值:TRTCVideoMirrorType.TRTCVideoMirrorType_Auto
+     *
+     * @memberof TrtcCloud
+     * @example
+     * import { TRTCVideoRotation, TRTCVideoFillMode, TRTCVideoMirrorType } from '@/TrtcCloud/lib/TrtcDefines';
+     * const renderParams = {
+     *  rotation: TRTCVideoRotation.TRTCVideoRotation_0,
+     *  fillMode: TRTCVideoFillMode.TRTCVideoFillMode_Fill,
+     *  mirrorType: TRTCVideoMirrorType.TRTCVideoMirrorType_Auto
+     * };
+     * this.trtcCloud.setLocalRenderParams(renderParams);
+     */
+    setLocalRenderParams(params) {
+        return TrtcCloudImpl._getInstance().setLocalRenderParams(params);
+    }
+    /**
+     * 暂停/恢复发布本地的视频流
+     *
+     * 该接口可以暂停(或恢复)发布本地的视频画面,暂停之后,同一房间中的其他用户将无法继续看到自己画面。 该接口在指定 TRTCVideoStreamTypeBig 时等效于 start/stopLocalPreview 这两个接口,但具有更好的响应速度。 因为 start/stopLocalPreview 需要打开和关闭摄像头,而打开和关闭摄像头都是硬件设备相关的操作,非常耗时。 相比之下,muteLocalVideo 只需要在软件层面对数据流进行暂停或者放行即可,因此效率更高,也更适合需要频繁打开关闭的场景。 当暂停/恢复发布指定 TRTCVideoStreamTypeBig 后,同一房间中的其他用户将会收到 onUserVideoAvailable 回调通知。 当暂停/恢复发布指定 TRTCVideoStreamTypeSub 后,同一房间中的其他用户将会收到 onUserSubStreamAvailable 回调通知。
+     * @param {TRTCVideoStreamType} streamType 要暂停/恢复的视频流类型(仅支持 TRTCVideoStreamTypeBig 和 TRTCVideoStreamTypeSub)
+     * @param {Boolean} mute - true:屏蔽;false:开启,默认值:false
+     *
+     * @memberof TrtcCloud
+     * @example
+     * this.trtcCloud.muteLocalVideo(TRTCVideoStreamType.TRTCVideoStreamTypeBig, true);
+     */
+    muteLocalVideo(streamType, mute) {
+        return TrtcCloudImpl._getInstance().muteLocalVideo(streamType, mute);
+    }
+    /**
+     * 显示远端视频或辅流<br>
+     *
+     * @param {String} userId 指定远端用户的 userId
+     * @param {TRTCVideoStreamType} streamType 指定要观看 userId 的视频流类型
+     * - 高清大画面:TRTCVideoStreamType.TRTCVideoStreamTypeBig
+     * - 低清小画面:TRTCVideoStreamType.TRTCVideoStreamTypeSmall
+     * - 辅流(屏幕分享):TRTCVideoStreamType.TRTCVideoStreamTypeSub
+     * @param {String} viewId 用于承载视频画面的渲染控件,使用原生插件中的 TRTCCloudUniPlugin-TXRemoteViewComponent component,需要提供 viewId 属性值,例如 viewId=userId
+     * @memberof TrtcCloud
+     * @example
+     * import { TRTCVideoStreamType } from '@/TrtcCloud/lib/TrtcDefines';
+     * const viewId = this.remoteUserId;
+     * this.trtcCloud.startRemoteView(userId, TRTCVideoStreamType.TRTCVideoStreamTypeBig, viewId);
+     */
+    startRemoteView(userId, streamType, viewId) {
+        return TrtcCloudImpl._getInstance().startRemoteView(userId, streamType, viewId);
+    }
+    /**
+     * 停止显示远端视频画面,同时不再拉取该远端用户的视频数据流<br>
+     * 指定要停止观看的 userId 的视频流类型
+     *
+     * @param {String} userId 指定的远端用户 ID
+     * @param {TRTCVideoStreamType} streamType
+     * - 高清大画面:TRTCVideoStreamType.TRTCVideoStreamTypeBig
+     * - 低清小画面:TRTCVideoStreamType.TRTCVideoStreamTypeSmall
+     * - 辅流(屏幕分享):TRTCVideoStreamType.TRTCVideoStreamTypeSub
+     * @memberof TrtcCloud
+     * @example
+     * import { TRTCVideoStreamType } from '@/TrtcCloud/lib/TrtcDefines';
+     * this.trtcCloud.stopRemoteView(remoteUserId, TRTCVideoStreamType.TRTCVideoStreamTypeBig);
+     */
+    stopRemoteView(userId, streamType) {
+        return TrtcCloudImpl._getInstance().stopRemoteView(userId, streamType);
+    }
+    /**
+     * 设置远端画面的渲染参数,可设置的参数包括有:画面的旋转角度、填充模式以及左右镜像等。
+     * @param {String} userId 远端用户 ID
+     * @param {TRTCVideoStreamType} streamType 可以设置为主路画面(TRTCVideoStreamTypeBig)或辅路画面(TRTCVideoStreamTypeSub)
+     * @param {TRTCRenderParams} params - 图像的参数
+     * @param {TRTCVideoRotation} params.rotation - 图像的顺时针旋转角度,支持90、180以及270旋转角度,默认值:TRTCVideoRotation.TRTCVideoRotation_0
+     * @param {TRTCVideoFillMode} params.fillMode - 视频画面填充模式,填充(画面可能会被拉伸裁剪)或适应(画面可能会有黑边),默认值:TRTCVideoFillMode.TRTCVideoFillMode_Fill
+     * @param {TRTCVideoMirrorType} params.mirrorType - 画面镜像模式,默认值:TRTCVideoMirrorType.TRTCVideoMirrorType_Auto
+     * @memberof TrtcCloud
+     * @example
+     * import { TRTCVideoRotation, TRTCVideoFillMode, TRTCVideoMirrorType } from '@/TrtcCloud/lib/TrtcDefines';
+     * const renderParams = {
+     *  rotation: TRTCVideoRotation.TRTCVideoRotation_0,
+     *  fillMode: TRTCVideoFillMode.TRTCVideoFillMode_Fill,
+     *  mirrorType: TRTCVideoMirrorType.TRTCVideoMirrorType_Auto
+     * };
+     * this.trtcCloud.setRemoteRenderParams(userId, TRTCVideoStreamType.TRTCVideoStreamTypeBig, renderParams);
+     */
+    setRemoteRenderParams(userId, streamType, params) { }
+    /**
+     * 视频画面截图
+     *
+     * 您可以通过本接口截取本地的视频画面,远端用户的主路画面以及远端用户的辅路(屏幕分享)画面。
+     *
+     * @param {String | null} userId 用户 ID,如指定 null 表示截取本地的视频画面
+     * @param {TRTCVideoStreamType} streamType 视频流类型,可选择截取主路画面(TRTCVideoStreamTypeBig,常用于摄像头)或辅路画面(TRTCVideoStreamTypeSub,常用于屏幕分享)
+     * @param {TRTCSnapshotSourceType} sourceType 画面来源,可选择截取视频流画面(TRTCSnapshotSourceTypeStream)或视频渲染画面(TRTCSnapshotSourceTypeView),前者一般更清晰
+     *
+     * @memberof TrtcCloud
+     * @example
+     * import { TRTCVideoStreamType } from '@/TrtcCloud/lib/TrtcDefines';
+     * this.trtcCloud.snapshotVideo(null, TRTCVideoStreamType.TRTCVideoStreamTypeBig, TRTCSnapshotSourceType.TRTCSnapshotSourceTypeStream); // 截取本地视频流画面
+     * this.trtcCloud.snapshotVideo(this.remoteUserId, TRTCVideoStreamType.TRTCVideoStreamTypeBig, TRTCSnapshotSourceType.TRTCSnapshotSourceTypeView); // 截取远端指定用户视频渲染画面
+     */
+    snapshotVideo(userId, streamType, sourceType) {
+        return TrtcCloudImpl._getInstance().snapshotVideo(userId, streamType, sourceType);
+    }
+    /**
+     * 开启本地音频的采集和上行, 并设置音频质量<br>
+     * 该函数会启动麦克风采集,并将音频数据传输给房间里的其他用户。 SDK 不会默认开启本地音频采集和上行,您需要调用该函数开启,否则房间里的其他用户将无法听到您的声音<br>
+     * 主播端的音质越高,观众端的听感越好,但传输所依赖的带宽也就越高,在带宽有限的场景下也更容易出现卡顿
+     *
+     * @param {TRTCAudioQuality} quality 声音音质
+     * - TRTCAudioQualitySpeech,流畅:采样率:16k;单声道;音频裸码率:16kbps;适合语音通话为主的场景,比如在线会议,语音通话。
+     * - TRTCAudioQualityDefault,默认:采样率:48k;单声道;音频裸码率:50kbps;SDK 默认的音频质量,如无特殊需求推荐选择之。
+     * - TRTCAudioQualityMusic,高音质:采样率:48k;双声道 + 全频带;音频裸码率:128kbps;适合需要高保真传输音乐的场景,比如在线K歌、音乐直播等
+     * @memberof TrtcCloud
+     * @example
+     * import { TRTCAudioQuality } from '@/TrtcCloud/lib/TrtcDefines';
+     * this.trtcCloud.startLocalAudio(TRTCAudioQuality.TRTCAudioQualityDefault);
+     */
+    startLocalAudio(quality) {
+        return TrtcCloudImpl._getInstance().startLocalAudio(quality);
+    }
+    /**
+     * 关闭本地音频的采集和上行<br>
+     * 当关闭本地音频的采集和上行,房间里的其它成员会收到 `onUserAudioAvailable(false)` 回调通知
+     *
+     * @memberof TrtcCloud
+     * @example
+     * this.trtcCloud.stopLocalAudio();
+     */
+    stopLocalAudio() {
+        return TrtcCloudImpl._getInstance().stopLocalAudio();
+    }
+    /**
+     * 静音本地的音频
+     *
+     * 当静音本地音频后,房间里的其它成员会收到 onUserAudioAvailable(false) 回调通知。
+     * 与 stopLocalAudio 不同之处在于,muteLocalAudio 并不会停止发送音视频数据,而是会继续发送码率极低的静音包。
+     * 在对录制质量要求很高的场景中,选择 muteLocalAudio 是更好的选择,能录制出兼容性更好的 MP4 文件。
+     * 这是由于 MP4 等视频文件格式,对于音频的连续性是要求很高的,简单粗暴地 stopLocalAudio 会导致录制出的 MP4 不易播放。
+     *
+     * @param {Boolean} mute - true:屏蔽;false:开启,默认值:false
+     *
+     * @memberof TrtcCloud
+     * @example
+     * this.trtcCloud.muteLocalAudio(true);
+     */
+    muteLocalAudio(mute) {
+        return TrtcCloudImpl._getInstance().muteLocalAudio(mute);
+    }
+    /**
+     * 静音掉某一个用户的声音,同时不再拉取该远端用户的音频数据流
+     *
+     * @param {String}  userId - 用户 ID
+     * @param {Boolean} mute   - true:静音;false:非静音
+     *
+     * @memberof TrtcCloud
+     * @example
+     * this.trtcCloud.muteRemoteAudio('denny', true);
+     */
+    muteRemoteAudio(userId, mute) {
+        return TrtcCloudImpl._getInstance().muteRemoteAudio(userId, mute);
+    }
+    /**
+     * 静音掉所有用户的声音,同时不再拉取该远端用户的音频数据流
+     *
+     * @param {Boolean} mute - true:静音;false:非静音
+     *
+     * @memberof TrtcCloud
+     * @example
+     * this.trtcCloud.muteAllRemoteAudio(true);
+     */
+    muteAllRemoteAudio(mute) {
+        return TrtcCloudImpl._getInstance().muteAllRemoteAudio(mute);
+    }
+    /**
+     * 设置音频路由
+     *
+     * 设置“音频路由”,即设置声音是从手机的扬声器还是从听筒中播放出来,因此该接口仅适用于手机等移动端设备。 手机有两个扬声器:一个是位于手机顶部的听筒,一个是位于手机底部的立体声扬声器。
+     * 设置音频路由为听筒时,声音比较小,只有将耳朵凑近才能听清楚,隐私性较好,适合用于接听电话。 设置音频路由为扬声器时,声音比较大,不用将手机贴脸也能听清,因此可以实现“免提”的功能。
+     *
+     * @param {TRTCAudioRoute} route 音频路由,即声音由哪里输出(扬声器、听筒), 默认值:TRTCAudioRoute.TRTCAudioRouteSpeaker(扬声器),
+     * @memberof TrtcCloud
+     * @example
+     * import { TRTCAudioRoute } from '@/TrtcCloud/lib/TrtcDefines';
+     * this.trtcCloud.setAudioRoute(TRTCAudioRoute.TRTCAudioRouteSpeaker); // TRTCAudioRoute.TRTCAudioRouteEarpiece (听筒)
+     */
+    setAudioRoute(route) {
+        return TrtcCloudImpl._getInstance().setAudioRoute(route);
+    }
+    /**
+     * 启用或关闭音量大小提示
+     *
+     * 开启此功能后,SDK 会在 onUserVoiceVolume() 中反馈对每一路声音音量大小值的评估。
+     *
+     * **Note:**
+     * - 如需打开此功能,请在 startLocalAudio 之前调用才可以生效。
+     *
+     * @param {Number} interval - 设置 onUserVoiceVolume 回调的触发间隔,单位为ms,最小间隔为100ms,如果小于等于0则会关闭回调,建议设置为300ms
+     * @memberof TrtcCloud
+     * @example
+     * this.trtcCloud.enableAudioVolumeEvaluation(300);
+     */
+    enableAudioVolumeEvaluation(interval) {
+        return TrtcCloudImpl._getInstance().enableAudioVolumeEvaluation(interval);
+    }
+    /////////////////////////////////////////////////////////////////////////////////
+    //
+    //                      屏幕分享
+    //
+    /////////////////////////////////////////////////////////////////////////////////
+    /**
+     * 设置屏幕分享(即辅路)的视频编码参数
+     *
+     * 该接口可以设定远端用户所看到的屏幕分享(即辅路)的画面质量,同时也能决定云端录制出的视频文件中屏幕分享的画面质量。 请注意如下两个接口的差异:
+     *  - setVideoEncoderParam 用于设置主路画面(TRTCVideoStreamTypeBig,一般用于摄像头)的视频编码参数。
+     *  - setSubStreamEncoderParam 用于设置辅路画面(TRTCVideoStreamTypeSub,一般用于屏幕分享)的视频编码参数。
+     *
+     * **Note:**
+     *  - 即使您使用主路传输屏幕分享(在调用 startScreenCapture 时设置 type=TRTCVideoStreamTypeBig),依然要使用 setSubStreamEncoderParam 设定屏幕分享的编码参数,而不要使用 setVideoEncoderParam
+     * @param {TRTCVideoEncParam} param	辅流编码参数,详情请参考 TRTCVideoEncParam。
+     * @memberof TrtcCloud
+     * @example
+     * const params = {
+     *   videoResolution: TRTCVideoResolution.TRTCVideoResolution_640_360,
+     *   videoResolutionMode: TRTCVideoResolutionMode.TRTCVideoResolutionModePortrait,
+     *   videoFps: 15,
+     *   videoBitrate: 900,
+     *   minVideoBitrate: 200,
+     *   enableAdjustRes: false,
+     * };
+     * this.trtcCloud.setSubStreamEncoderParam(params);
+     */
+    setSubStreamEncoderParam(param) {
+        return TrtcCloudImpl._getInstance().setSubStreamEncoderParam(param);
+    }
+    /**
+     * 启动屏幕分享
+     *
+     * **Note:**
+     *  - 一个用户同时最多只能上传一条主路(TRTCVideoStreamTypeBig)画面和一条辅路(TRTCVideoStreamTypeSub)画面,
+     * 默认情况下,屏幕分享使用辅路画面,如果使用主路画面,建议您提前停止摄像头采集(stopLocalPreview)避免相互冲突。
+     *  - **仅支持 iOS 13.0 及以上系统,进行应用内的屏幕分享**
+     *
+     * @param {TRTCVideoStreamType} streamType 屏幕分享使用的线路,可以设置为主路(TRTCVideoStreamTypeBig)或者辅路(TRTCVideoStreamTypeSub),推荐使用
+     * @param {TRTCVideoEncParam} encParams 屏幕分享的画面编码参数,可以设置为 null,表示让 SDK 选择最佳的编码参数(分辨率、码率等)。即使在调用 startScreenCapture 时设置 type=TRTCVideoStreamTypeBig,依然可以使用此接口更新屏幕分享的编码参数。
+     * @memberof TrtcCloud
+     * @example
+     * import { TRTCVideoResolution, TRTCVideoResolutionMode, TRTCVideoStreamType} from '@/TrtcCloud/lib/TrtcDefines';
+     * const encParams = {
+     *   videoResolution: TRTCVideoResolution.TRTCVideoResolution_640_360,
+     *   videoResolutionMode: TRTCVideoResolutionMode.TRTCVideoResolutionModePortrait,
+     *   videoFps: 15,
+     *   videoBitrate: 900,
+     *   minVideoBitrate: 200,
+     *   enableAdjustRes: false,
+     * };
+     * this.trtcCloud.startScreenCapture(TRTCVideoStreamType.TRTCVideoStreamTypeSub, encParams);
+     */
+    startScreenCapture(streamType = TRTCVideoStreamType.TRTCVideoStreamTypeSub, encParams = null) {
+        return TrtcCloudImpl._getInstance().startScreenCapture(streamType, encParams);
+    }
+    /**
+     * 停止屏幕分享
+     * @memberof TrtcCloud
+     * @example
+     * this.trtcCloud.stopScreenCapture();
+     */
+    stopScreenCapture() {
+        return TrtcCloudImpl._getInstance().stopScreenCapture();
+    }
+    /**
+     * 暂停屏幕分享
+     * @memberof TrtcCloud
+     * @example
+     * this.trtcCloud.pauseScreenCapture();
+     */
+    pauseScreenCapture() {
+        return TrtcCloudImpl._getInstance().pauseScreenCapture();
+    }
+    /**
+     * 恢复屏幕分享
+     * @memberof TrtcCloud
+     * @example
+     * this.trtcCloud.resumeScreenCapture();
+     */
+    resumeScreenCapture() {
+        return TrtcCloudImpl._getInstance().resumeScreenCapture();
+    }
+    /////////////////////////////////////////////////////////////////////////////////
+    //
+    //                      美颜 + 水印
+    //
+    /////////////////////////////////////////////////////////////////////////////////
+    /**
+     * 设置美颜(磨皮)算法
+     * TRTC 内置多种不同的磨皮算法,您可以选择最适合您产品定位的方案
+     *
+     * **Note:**
+     * - 设置美颜前,先调用 `setBeautyLevel` 设置美颜级别。否则美颜级别为 0 表示关闭美颜
+     *
+     * @param {TRTCBeautyStyle} beautyStyle 美颜风格,TRTCBeautyStyleSmooth:光滑;TRTCBeautyStyleNature:自然;TRTCBeautyStylePitu:优图
+     * @memberof TrtcCloud
+     * @example
+     * import { TRTCBeautyStyle } from '@/TrtcCloud/lib/TrtcDefines';
+     * const beautyLevel = 5; // 美颜级别,取值范围0 - 9; 0表示关闭,9表示效果最明显。
+     * this.trtcCloud.setBeautyLevel(beautyLevel);
+     * this.trtcCloud.setBeautyStyle(TRTCBeautyStyle.TRTCBeautyStyleSmooth);
+     */
+    setBeautyStyle(beautyStyle) {
+        return TrtcCloudImpl._getInstance().setBeautyStyle(beautyStyle);
+    }
+    /**
+     * 设置美颜级别
+     * @param {Number} beautyLevel	美颜级别,取值范围0 - 9; 0表示关闭,9表示效果最明显。
+     *
+     * @memberof TrtcCloud
+     * @example
+     * const beautyLevel = 5; // 美颜级别,取值范围0 - 9; 0表示关闭,9表示效果最明显。
+     * this.trtcCloud.setBeautyLevel(beautyLevel);
+     */
+    setBeautyLevel(beautyLevel) {
+        return TrtcCloudImpl._getInstance().setBeautyLevel(beautyLevel);
+    }
+    /////////////////////////////////////////////////////////////////////////////////
+    //
+    //                      背景音效
+    //
+    /////////////////////////////////////////////////////////////////////////////////
+    /**
+     * 开始播放背景音乐
+     * 每个音乐都需要您指定具体的 ID,您可以通过该 ID 对音乐的开始、停止、音量等进行设置。<br>
+     * **Note:**
+     * - 如果要多次播放同一首背景音乐,请不要每次播放都分配一个新的 ID,我们推荐使用相同的 ID。
+     * - 若您希望同时播放多首不同的音乐,请为不同的音乐分配不同的 ID 进行播放。
+     * - 如果使用同一个 ID 播放不同音乐,SDK 会先停止播放旧的音乐,再播放新的音乐。
+     *
+     * **Note:**<br>
+     * 在 uni-app 中 path 如何获取。
+     * - 使用 cdn 地址,例如:`path = https://web.sdk.qcloud.com/component/TUIKit/assets/uni-app/calling-bell-1.mp3;`
+     * - 使用本地绝对路径。
+     *     1. 通过 [uni.saveFile](https://zh.uniapp.dcloud.io/api/file/file.html#savefile) 获取保存后的相对路径(建议这种路径)。
+     *     2. 将上一步的相对路径转成绝对路径,[plus.io.convertLocalFileSystemURL](https://www.html5plus.org/doc/zh_cn/io.html#plus.io.convertLocalFileSystemURL)。
+     *
+     * @param {AudioMusicParam} musicParam 音乐参数
+     * @param {Number} musicParam.id 音乐 ID
+     * @param {String} musicParam.path 音效文件的完整路径或 URL 地址。支持的音频格式包括 MP3、AAC、M4A、WAV
+     * @param {Number} musicParam.loopCount 音乐循环播放的次数。取值范围为0 - 任意正整数,默认值:0。0表示播放音乐一次;1表示播放音乐两次;以此类推
+     * @param {Boolean} musicParam.publish 是否将音乐传到远端。true:音乐在本地播放的同时,远端用户也能听到该音乐;false:主播只能在本地听到该音乐,远端观众听不到。默认值:false。
+     * @param {Boolean} musicParam.isShortFile 播放的是否为短音乐文件。true:需要重复播放的短音乐文件;false:正常的音乐文件。默认值:false
+     * @param {Number} musicParam.startTimeMS 音乐开始播放时间点,单位: 毫秒。
+     * @param {Number} musicParam.endTimeMS 音乐结束播放时间点,单位: 毫秒,0 表示播放至文件结尾。
+     * @memberof TrtcCloud
+     * @example
+     * import { AudioMusicParam } from '@/TrtcCloud/lib/TrtcDefines';
+     * const musicParam = {
+     *  id: 1,
+     *  path: '',
+     *  loopCount: 1,
+     *  publish: true,
+     *  isShortFile: false,
+     *  startTimeMS: 0,
+     *  endTimeMS: 0,
+     * };
+     * this.trtcCloud.startPlayMusic(musicParam);
+     */
+    startPlayMusic(musicParam) {
+        return TrtcCloudImpl._getInstance().startPlayMusic(musicParam);
+    }
+    /**
+     * 停止播放背景音乐
+     * @param {Number} id	音乐 ID
+     *
+     * @memberof TrtcCloud
+     * @example
+     * const musicId = 5;
+     * this.trtcCloud.stopPlayMusic(musicId);
+     */
+    stopPlayMusic(id) {
+        return TrtcCloudImpl._getInstance().stopPlayMusic(id);
+    }
+    /**
+     * 暂停播放背景音乐
+     * @param {Number} id	音乐 ID
+     * @memberof TrtcCloud
+     * @example
+     * const musicId = 5;
+     * this.trtcCloud.pausePlayMusic(musicId);
+     */
+    pausePlayMusic(id) {
+        return TrtcCloudImpl._getInstance().pausePlayMusic(id);
+    }
+    /**
+     * 恢复播放背景音乐
+     * @param {Number} id	音乐 ID
+     * @memberof TrtcCloud
+     * @example
+     * const musicId = 5;
+     * this.trtcCloud.resumePlayMusic(musicId);
+     */
+    resumePlayMusic(id) {
+        return TrtcCloudImpl._getInstance().resumePlayMusic(id);
+    }
+    /////////////////////////////////////////////////////////////////////////////////
+    //
+    //                       设置 TRTCCallback 回调
+    //
+    /////////////////////////////////////////////////////////////////////////////////
+    /**
+     * 设置 TrtcCloud 回调
+     *
+     * @example
+     * // 创建/使用/销毁 TrtcCloud 对象的示例代码:
+     * import TrtcCloud from '@/TrtcCloud/lib/index';
+     * this.trtcCloud = new TrtcCloud();
+     *
+     * // 添加事件监听的方法,事件关键字详见下方”通用事件回调“
+     * this.trtcCloud.on('onEnterRoom', (result) => {
+     *   if (result > 0) {
+     *     console.log(`enter room success, spend ${result}ms`);
+     *   } else {
+     *     console.log(`enter room failed, error code = ${result}`);
+     *   }
+     * });
+     *
+     * @namespace TRTCCallback
+     */
+    /////////////////////////////////////////////////////////////////////////////////
+    //
+    //                      (一)事件回调
+    //
+    /////////////////////////////////////////////////////////////////////////////////
+    /**
+     * 错误回调,表示 SDK 不可恢复的错误,一定要监听并分情况给用户适当的界面提示<br>
+     * @event TRTCCallback#onError
+     * @param {Number} code 错误码,[详见](https://cloud.tencent.com/document/product/647/38308#.E9.94.99.E8.AF.AF.E7.A0.81.E8.A1.A8)
+     * @param {String} message 错误信息
+     * @param {Object} extraInfo 扩展信息字段,个别错误码可能会带额外的信息帮助定位问题
+     */
+    onError(code, message, extraInfo) { }
+    /**
+     * 警告回调,用于告知您一些非严重性问题,例如出现卡顿或者可恢复的解码失败<br>
+     * @event TRTCCallback#onWarning
+     * @param {Number} code 警告码,[详见](https://cloud.tencent.com/document/product/647/38308#.E8.AD.A6.E5.91.8A.E7.A0.81.E8.A1.A8)
+     * @param {String} message 警告信息
+     * @param {Object} extraInfo 扩展信息字段,个别警告码可能会带额外的信息帮助定位问题
+     */
+    onWarning(code, message, extraInfo) { }
+    /**
+     * 进房后的回调<br>
+     * 调用 `enterRoom()` 接口执行进房操作后,会收到 `onEnterRoom(result)` 回调<br>
+     * 如果加入成功,result 会是一个正数(result > 0),代表加入房间的时间消耗,单位是毫秒(ms)。<br>
+     * 如果加入失败,result 会是一个负数(result < 0),代表进房失败的错误码。
+     *
+     * @event TRTCCallback#onEnterRoom
+     * @param {Number} result 进房耗时
+     */
+    onEnterRoom(result) { }
+    /**
+     * 离开房间的事件回调<br>
+     * 调用 `exitRoom()` 接口会执行退出房间的相关逻辑,例如释放音视频设备资源和编解码器资源等。待资源释放完毕,会通过 `onExitRoom()` 回调通知到您<br>
+     *
+     * **Note:**
+     * - 如果您要再次调用 `enterRoom()` 或者切换到其他的音视频 SDK,请等待 `onExitRoom()` 回调到来之后再执行相关操作。 否则可能会遇到音频设备被占用等各种异常问题
+     *
+     * @event TRTCCallback#onExitRoom
+     * @param {Number} reason 离开房间原因,0:主动调用 exitRoom 退房;1:被服务器踢出当前房间;2:当前房间整个被解散
+     */
+    onExitRoom(reason) { }
+    /**
+     * 跨房通话事件回调<br>
+     * 调用 TRTCCloud 中的 [connectOtherRoom()](https://web.sdk.qcloud.com/trtc/uniapp/doc/zh-cn/TrtcCloud.html#connectOtherRoom) 接口会将两个不同房间中的主播拉通视频通话,也就是所谓的“主播PK”功能。
+     * 调用者会收到 onConnectOtherRoom() 事件回调来获知跨房通话是否成功, 如果成功,两个房间中的所有用户都会收到来自另一个房间中的 PK 主播的 [onUserVideoAvailable()](http://127.0.0.1:5500/UniApp-TRTC-SDK/packages/TrtcCloud/docs/zh-cn/api/TRTCCallback.html#event:onUserVideoAvailable) 回调。
+     *
+     * @event TRTCCallback#onConnectOtherRoom
+     * @param {Object} params 调用 [connectOtherRoom()](https://web.sdk.qcloud.com/trtc/uniapp/doc/zh-cn/TrtcCloud.html#connectOtherRoom) 接口返回值数据。
+     * - userId:跨房通话时对端 userId
+     * - errCode: [错误状态码](https://cloud.tencent.com/document/product/647/38308#.E8.AD.A6.E5.91.8A.E7.A0.81.E8.A1.A8),返回0表示跨房通话成功。
+     * - errMsg: 状态信息,跨房通话成功返回 OK。
+     */
+    onConnectOtherRoom(params) { }
+    /**
+     * 结束跨房通话的结果回调<br>
+     * 调用 TRTCCloud 中的 [disconnectOtherRoom()](https://web.sdk.qcloud.com/trtc/uniapp/doc/zh-cn/TrtcCloud.html#disconnectOtherRoom) 接口会将两个不同房间中的主播拉通视频通话,也就是所谓的“主播PK”功能。
+     * 调用者会收到 onDisconnectOtherRoom() 事件回调来获知结束跨房通话是否成功。
+     *
+     * @event TRTCCallback#onDisconnectOtherRoom
+     * @param {Object} params 调用 [disconnectOtherRoom()](https://web.sdk.qcloud.com/trtc/uniapp/doc/zh-cn/TrtcCloud.html#disconnectOtherRoom) 失败时返回的错误数据。
+     * - errCode: [错误状态码](https://cloud.tencent.com/document/product/647/38308#.E8.AD.A6.E5.91.8A.E7.A0.81.E8.A1.A8)。
+     * - errMsg: 错误信息。
+     */
+    onDisconnectOtherRoom(params) { }
+    /**
+     * 切换角色的事件回调<br>
+     * 调用 TRTCCloud 中的 switchRole() 接口会切换主播和观众的角色,该操作会伴随一个线路切换的过程, 待 SDK 切换完成后,会抛出 onSwitchRole() 事件回调
+     *
+     * @event TRTCCallback#onSwitchRole
+     * @param {Number} code 错误码,[详见](https://cloud.tencent.com/document/product/647/38308#.E8.AD.A6.E5.91.8A.E7.A0.81.E8.A1.A8)
+     * @param {String} message 错误信息
+     */
+    onSwitchRole(code, message) { }
+    /**
+     * 开始渲染本地或远程用户的首帧画面<br>
+     * 如果 userId 为 null,代表开始渲染本地采集的摄像头画面,需要您先调用 `startLocalPreview` 触发。 如果 userId 不为 null,代表开始渲染远程用户的首帧画面,需要您先调用 `startRemoteView` 触发<br>
+     * 只有当您调用 `startLocalPreview()、startRemoteView() 或 startRemoteSubStreamView()` 之后,才会触发该回调
+     *
+     * @event TRTCCallback#onFirstVideoFrame
+     * @param {String} userId 本地或远程用户 ID,如果 userId === null 代表本地,userId !== null 代表远程
+     * @param {TRTCVideoStreamType} streamType 视频流类型:摄像头或屏幕分享
+     * @param {Number} width 画面宽度
+     * @param {Number} height 画面高度
+     */
+    onFirstVideoFrame(userId, streamType, width, height) { }
+    /**
+     * 开始播放远程用户的首帧音频(本地声音暂不通知)<br>
+     * 如果 userId 为 null,代表开始渲染本地采集的摄像头画面,需要您先调用 `startLocalPreview` 触发。 如果 userId 不为 null,代表开始渲染远程用户的首帧画面,需要您先调用 `startRemoteView` 触发<br>
+     * 只有当您调用 `startLocalPreview()、startRemoteView() 或 startRemoteSubStreamView()` 之后,才会触发该回调
+     *
+     * @event TRTCCallback#onFirstAudioFrame
+     * @param {String} userId 远程用户 ID
+     */
+    onFirstAudioFrame(userId) { }
+    /**
+     * 截图完成时回调<br>
+     * @event TRTCCallback#onSnapshotComplete
+     * @param {String} base64Data 截图对应的 base64 数据
+     * @param {String} message 错误信息
+     */
+    onSnapshotComplete(base64Data, message) { }
+    /**
+     * 麦克风准备就绪
+     */
+    onMicDidReady() { }
+    /**
+     * 摄像头准备就绪
+     */
+    onCameraDidReady() { }
+    /**
+     * 网络质量:该回调每2秒触发一次,统计当前网络的上行和下行质量<br>
+     * userId 为本地用户 ID 代表自己当前的视频质量
+     *
+     * @param {String} localQuality 上行网络质量
+     * @param {String} remoteQuality 下行网络质量
+     */
+    onNetworkQuality(localQuality, remoteList) { }
+    /**
+     * 有用户加入当前房间<br>
+     * 出于性能方面的考虑,在两种不同的应用场景下,该通知的行为会有差别:<br>
+     * 通话场景(TRTCAppScene.TRTCAppSceneVideoCall 和 TRTCAppScene.TRTCAppSceneAudioCall):该场景下用户没有角色的区别,任何用户进入房间都会触发该通知。<br>
+     * 直播场景(TRTCAppScene.TRTCAppSceneLIVE 和 TRTCAppScene.TRTCAppSceneVoiceChatRoom ):该场景不限制观众的数量,如果任何用户进出都抛出回调会引起很大的性能损耗,所以该场景下只有主播进入房间时才会触发该通知,观众进入房间不会触发该通知
+     *
+     * @event TRTCCallback#onRemoteUserEnterRoom
+     * @param {String} userId 用户标识 ID
+     */
+    onRemoteUserEnterRoom(userId) { }
+    /**
+     * 有用户离开当前房间<br>
+     * 与 onRemoteUserEnterRoom 相对应,在两种不同的应用场景下,该通知的行为会有差别:<br>
+     * 通话场景(TRTCAppScene.TRTCAppSceneVideoCall 和 TRTCAppScene.TRTCAppSceneAudioCall):该场景下用户没有角色的区别,任何用户进入房间都会触发该通知。<br>
+     * 直播场景(TRTCAppScene.TRTCAppSceneLIVE 和 TRTCAppScene.TRTCAppSceneVoiceChatRoom ):该场景不限制观众的数量,如果任何用户进出都抛出回调会引起很大的性能损耗,所以该场景下只有主播进入房间时才会触发该通知,观众进入房间不会触发该通知
+     *
+     * @event TRTCCallback#onRemoteUserLeaveRoom
+     * @param {String} userId 用户标识 ID
+     * @param {Number} reason 离开原因,0 表示用户主动退出房间,1 表示用户超时退出,2 表示被踢出房间
+     */
+    onRemoteUserLeaveRoom(userId, reason) { }
+    /**
+     * 首帧本地音频数据已经被送出<br>
+     * 在 `enterRoom()` 并 `startLocalAudio()` 成功后开始麦克风采集,并将采集到的声音进行编码。 当 SDK 成功向云端送出第一帧音频数据后,会抛出这个回调事件
+     *
+     * @event TRTCCallback#onSendFirstLocalAudioFrame
+     */
+    onSendFirstLocalAudioFrame() { }
+    /**
+     * 首帧本地视频数据已经被送出<br>
+     * SDK 会在 `enterRoom()` 并 `startLocalPreview()` 成功后开始摄像头采集,并将采集到的画面进行编码。 当 SDK 成功向云端送出第一帧视频数据后,会抛出这个回调事件
+     *
+     * @event TRTCCallback#onSendFirstLocalVideoFrame
+     * @param {TRTCVideoStreamType} streamType 视频流类型,大画面、小画面或辅流画面(屏幕分享)
+     */
+    onSendFirstLocalVideoFrame(streamType) { }
+    /**
+     * 技术指标统计回调<br>
+     * 如果您是熟悉音视频领域相关术语,可以通过这个回调获取 SDK 的所有技术指标。 如果您是首次开发音视频相关项目,可以只关注 `onNetworkQuality` 回调
+     *
+     * **Note:**
+     * - 每 2 秒回调一次
+     *
+     * @param {Object} statics 状态数据
+     */
+    onStatistics(statics) { }
+    /**
+     * 远端用户是否存在可播放的音频数据<br>
+     * @event TRTCCallback#onUserAudioAvailable
+     * @param {String} userId 用户标识 ID
+     * @param {Boolean} available 声音是否开启
+     */
+    onUserAudioAvailable(userId, available) { }
+    /**
+     * 远端用户是否存在可播放的主路画面(一般用于摄像头)<br>
+     * 当您收到 `onUserVideoAvailable(userId, true)` 通知时,表示该路画面已经有可用的视频数据帧到达。 此时,您需要调用 `startRemoteView(userId)` 接口加载该用户的远程画面。 然后,您会收到名为 onFirstVideoFrame(userid) 的首帧画面渲染回调。<br>
+     * 当您收到 `onUserVideoAvailable(userId, false)` 通知时,表示该路远程画面已经被关闭,可能由于该用户调用了 `muteLocalVideo()` 或 `stopLocalPreview()`。
+     *
+     * @event TRTCCallback#onUserVideoAvailable
+     * @param {String} userId 用户标识 ID
+     * @param {Boolean} available 画面是否开启
+     */
+    onUserVideoAvailable(userId, available) { }
+    /**
+     * 用于提示音量大小的回调,包括每个 userId 的音量和远端总音量<br>
+     * SDK 可以评估每一路音频的音量大小,并每隔一段时间抛出该事件回调,您可以根据音量大小在 UI 上做出相应的提示,比如“波形图”或“音量槽”。 要完成这个功能, 您需要先调用 enableAudioVolumeEvaluation 开启这个能力并设定事件抛出的时间间隔。 需要补充说明的是,无论当前房间中是否有人说话,SDK 都会按照您设定的时间间隔定时抛出此事件回调,只不过当没有人说话时,userVolumes 为空,totalVolume 为 0。
+     *
+     * **Note:**
+     * - userVolumes 为一个数组,对于数组中的每一个元素,当 userId 为空时表示本地麦克风采集的音量大小,当 userId 不为空时代表远端用户的音量大小
+     *
+     * @event TRTCCallback#onUserVoiceVolume
+     * @param {Array} userVolumes 是一个数组,用于承载所有正在说话的用户的音量大小,取值范围 0 - 100
+     * @param {Number} totalVolume 所有远端用户的总音量大小, 取值范围 0 - 100
+     */
+    onUserVoiceVolume(userVolumes, totalVolume) { }
+    /**
+     * 屏幕分享开启的事件回调<br>
+     * 当您通过 startScreenCapture 等相关接口启动屏幕分享时,SDK 便会抛出此事件回调
+     * @event TRTCCallback#onScreenCaptureStarted
+     */
+    onScreenCaptureStarted() { }
+    /**
+     * 屏幕分享停止的事件回调<br>
+     * 当您通过 stopScreenCapture 停止屏幕分享时,SDK 便会抛出此事件回调
+     * @event TRTCCallback#onScreenCaptureStopped
+     * @param {Number} reason 停止原因,0:用户主动停止;1:屏幕窗口关闭导致停止;2:表示屏幕分享的显示屏状态变更(如接口被拔出、投影模式变更等)
+     */
+    onScreenCaptureStopped(reason) { }
+    /**
+     * 屏幕分享停止的事件回调<br>
+     * 当您通过 pauseScreenCapture 停止屏幕分享时,SDK 便会抛出此事件回调
+     * @event TRTCCallback#onScreenCapturePaused
+     * @param {Number} reason 停止原因,0:用户主动停止;1:屏幕窗口关闭导致停止;2:表示屏幕分享的显示屏状态变更(如接口被拔出、投影模式变更等)
+     */
+    onScreenCapturePaused(reason) { }
+    /**
+     * 屏幕分享恢复的事件回调<br>
+     * 当您通过 resumeScreenCapture 恢复屏幕分享时,SDK 便会抛出此事件回调
+     * @event TRTCCallback#onScreenCaptureResumed
+     */
+    onScreenCaptureResumed() { }
+    /**
+     * 某远端用户发布/取消了辅路视频画面<br>
+     * “辅路画面”一般被用于承载屏幕分享的画面。当您收到 onUserSubStreamAvailable(userId, true) 通知时,表示该路画面已经有可播放的视频帧到达。 此时,您需要调用 startRemoteView 接口订阅该用户的远程画面,订阅成功后,您会继续收到该用户的首帧画面渲染回调 onFirstVideoFrame(userId)
+     *
+     * **Note:**
+     * - 拉取 Web 端(用 [WebRTC](https://web.sdk.qcloud.com/trtc/webrtc/doc/zh-cn/index.html) 实现屏幕分享)的屏幕分享,收不到 onUserSubStreamAvailable 事件。因为 [WebRTC](https://web.sdk.qcloud.com/trtc/webrtc/doc/zh-cn/index.html) 推的屏幕分享也是主流
+     * @param {String} userId 用户 ID
+     * @param {Boolean} available 是否可用,true 表示辅流可用
+     * @event TRTCCallback#onUserSubStreamAvailable
+     */
+    onUserSubStreamAvailable(userId, available) { }
+    /**
+     * 用户视频大小发生改变回调。<br>
+     * 当您收到 onUserVideoSizeChanged(userId, streamtype, newWidth, newHeight) 通知时,表示该路画面大小发生了调整,调整的原因可能是该用户调用了 setVideoEncoderParam 或者 setSubStreamEncoderParam 重新设置了画面尺寸。
+     * @param {String} userId 用户 ID
+     * @param {TRTCVideoStreamType} streamType 视频流类型,仅支持 TRTCVideoStreamTypeBig 和 TRTCVideoStreamTypeSub
+     * @param {Number} newWidth 视频流的宽度(像素)
+     * @param {Number} newHeight 视频流的高度(像素)
+     * @event TRTCCallback#onUserVideoSizeChanged
+     */
+    onUserVideoSizeChanged(userId, streamType, newWidth, newHeight) { }
+    /**
+     * 背景音乐开始播放
+     * @param {Number} id 播放的 id
+     * @param {Number} errCode 播放的状态码
+     * @event TRTCCallback#onStart
+     */
+    onStart(id, errCode) { }
+    /**
+     * 背景音乐的播放进度
+     * @param {Number} id 播放的 id
+     * @param {Number} curPtsMS 当前播放的位置
+     * @param {Number} durationMS 当前音频总时长
+     * @event TRTCCallback#onPlayProgress
+     */
+    onPlayProgress(id, curPtsMS, durationMS) { }
+    /**
+     * 背景音乐已经播放完毕
+     * @param {Number} id 播放的 id
+     * @param {Number} errCode 播放结束的状态码
+     * @event TRTCCallback#onComplete
+     */
+    onComplete(id, errCode) { }
+}

+ 279 - 0
TrtcCloud/permission.js

@@ -0,0 +1,279 @@
+/**
+ * 本模块封装了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
+//  }
+
+// HBuilder 选择 vue3 时, 上面的打包无法通过 import 进行引入
+export default {
+  judgeIosPermission: judgeIosPermission,
+  requestAndroidPermission: requestAndroidPermission,
+  checkSystemEnableLocation: checkSystemEnableLocation,
+  gotoAppPermissionSetting: gotoAppPermissionSetting
+};
+ 

+ 22 - 0
TrtcCloud/view/TrtcLocalView.nvue

@@ -0,0 +1,22 @@
+<template>
+	<TRTCCloudUniPlugin-TXLocalViewComponent :viewId="viewId"></TRTCCloudUniPlugin-TXLocalViewComponent>
+</template>
+
+<script>
+	export default {
+		name: 'TrtcLocalView',
+		props: {
+			viewId: {
+				type: String,
+				default: ''
+			}
+		},
+		created() {
+			console.log(this.viewId, '1111111')
+		}
+	}
+</script>
+
+<style>
+
+</style>

+ 28 - 0
TrtcCloud/view/TrtcRemoteView.nvue

@@ -0,0 +1,28 @@
+<template>
+	<TRTCCloudUniPlugin-TXRemoteViewComponent :userId="userId"
+		:viewId="viewId"></TRTCCloudUniPlugin-TXRemoteViewComponent>
+</template>
+
+<script>
+	export default {
+		name: 'TrtcRemoteView',
+		props: {
+			userId: {
+				type: String,
+				default: ''
+			},
+			viewId: {
+				type: String,
+				default: ''
+			}
+		},
+		created() {
+			console.log(this.userId, 'userId')
+			console.log(this.viewId, 'viewId')
+		}
+	}
+</script>
+
+<style>
+
+</style>

+ 87 - 0
common/cache.js

@@ -0,0 +1,87 @@
+/**
+ * 缓存数据优化
+ * var cache = require('utils/cache.js');
+ * import cache from '../cache'
+ * 使用方法 【
+ *     一、设置缓存
+ *         string    cache.put('k', 'string你好啊');
+ *         json      cache.put('k', { "b": "3" }, 2);
+ *         array     cache.put('k', [1, 2, 3]);
+ *         boolean   cache.put('k', true);
+ *     二、读取缓存
+ *         默认值    cache.get('k')
+ *         string    cache.get('k', '你好')
+ *         json      cache.get('k', { "a": "1" })
+ *     三、移除/清理  
+ *         移除: cache.remove('k');
+ *         清理:cache.clear(); 
+ * 】
+ * @type {String}
+ */
+var postfix = '_mallStore'; // 缓存前缀 
+/**
+ * 设置缓存 
+ * @param  {[type]} k [键名]
+ * @param  {[type]} v [键值]
+ * @param  {[type]} t [时间、单位秒]
+ */
+function put(k, v, t) {
+    uni.setStorageSync(k, v) 
+    var seconds = parseInt(t);
+    if (seconds > 0) {
+        var timestamp = Date.parse(new Date());
+        timestamp = timestamp / 1000 + seconds;
+        uni.setStorageSync(k + postfix, timestamp + "")
+    } else {
+        uni.removeStorageSync(k + postfix)
+    }
+}
+
+
+/**
+ * 获取缓存 
+ * @param  {[type]} k   [键名]
+ * @param  {[type]} def [获取为空时默认]
+ */
+function get(k, def) {
+    var deadtime = parseInt(uni.getStorageSync(k + postfix)) 
+    if (deadtime) {
+        if (parseInt(deadtime) < Date.parse(new Date()) / 1000) {
+            if (def) {
+                return def;
+            } else {
+                return false;
+            }
+        }
+    }
+    var res = uni.getStorageSync(k);
+    if (res) {
+        return res;
+    } else {
+        if (def == undefined  || def == "") {
+            def = false; 
+        }
+        return def;
+    }
+}
+
+function remove(k) {
+    uni.removeStorageSync(k);
+    uni.removeStorageSync(k + postfix);
+}
+
+/**
+ * 清理所有缓存
+ * @return {[type]} [description]
+ */
+function clear() {
+    uni.clearStorageSync();
+}
+
+
+module.exports = {
+    put: put,
+    get: get,
+    remove: remove,
+    clear: clear,
+}

+ 29 - 0
common/config.js

@@ -0,0 +1,29 @@
+const ROOTPATH = "https://zp.xianmaxiong.com/sqx_fast";
+const ROOTPATH1 = "https://zp.xianmaxiong.com/sqx_fast";
+const ROOTPATH2 = "wss://zp.xianmaxiong.com/wss/websocket/"; //联系客服
+const ROOTPATH3 = "wss://zp.xianmaxiong.com/wss/chatSocket/"; //聊天
+const ROOTPATH4 = "https://zp.xianmaxiong.com";
+
+// const ROOTPATH = "https://wap.aidezp.com/sqx_fast";
+// const ROOTPATH1 = "https://wap.aidezp.com/sqx_fast";
+// const ROOTPATH2 = "wss://wap.aidezp.com/wss/websocket/"; //联系客服
+// const ROOTPATH3 = "wss://wap.aidezp.com/wss/chatSocket/"; //聊天
+
+// const ROOTPATH = location.origin + "/sqx_fast";
+// const ROOTPATH1 = location.origin + "/sqx_fast";
+// const ROOTPATH2 = "wss://" + location.hostname + "/wss/websocket/"; //联系客服
+// const ROOTPATH3 = "wss://" + location.hostname + "/wss/chatSocket/"; //聊天
+
+// const ROOTPATH = "http://192.168.0.254:7155/sqx_fast";
+// const ROOTPATH1 = "http://192.168.0.254:7155/sqx_fast";
+// const ROOTPATH2 = "ws://192.168.0.254:7155/sqx_fast/websocket/"; //联系客服
+// const ROOTPATH3 = "ws://192.168.0.254:7155/sqx_fast/chatSocket/"; //聊天
+// const ROOTPATH4 = "https://zp.xianmaxiong.com";
+module.exports = {
+	APIHOST: ROOTPATH,
+	APIHOST1: ROOTPATH1,
+	WSHOST: ROOTPATH2,
+	WSHOST1: ROOTPATH3,
+	WSHOST4: ROOTPATH4
+
+};

+ 313 - 0
common/httpRequest.js

@@ -0,0 +1,313 @@
+import configdata from './config'
+import cache from './cache'
+
+module.exports = {
+	config: function(name) {
+		var info = null;
+		if (name) {
+			var name2 = name.split("."); //字符分割
+			if (name2.length > 1) {
+				info = configdata[name2[0]][name2[1]] || null;
+			} else {
+				info = configdata[name] || null;
+			}
+			if (info == null) {
+				let web_config = cache.get("web_config");
+				if (web_config) {
+					if (name2.length > 1) {
+						info = web_config[name2[0]][name2[1]] || null;
+					} else {
+						info = web_config[name] || null;
+					}
+				}
+			}
+		}
+		return info;
+	},
+	post: function(url, data, header) {
+		header = header || "application/x-www-form-urlencoded";
+		url = this.config("APIHOST") + url;
+		let token = uni.getStorageSync("token");
+		return new Promise((succ, error) => {
+			uni.request({
+				url: url,
+				data: data,
+				method: "POST",
+				header: {
+					"content-type": header,
+					"token": token
+				},
+				success: function(result) {
+					if (result.data.code == 401) {
+						// uni.clearStorage();
+						uni.removeStorageSync("token")
+						uni.removeStorageSync("userId")
+						uni.removeStorageSync("phone")
+						uni.removeStorageSync("openid")
+						uni.removeStorageSync("userName")
+						uni.removeStorageSync("relation")
+						uni.removeStorageSync("relation_id")
+						uni.removeStorageSync("isInvitation")
+						uni.removeStorageSync("zhiFuBao")
+						uni.removeStorageSync("zhiFuBaoName")
+						uni.showToast({
+							title: '用户信息失效,请重新登录!',
+							icon: 'none'
+						})
+					}
+					succ.call(self, result.data)
+				},
+				fail: function(e) {
+					error.call(self, e)
+				}
+			})
+		})
+	},
+	postT: function(url, data, header) {
+		header = header || "application/x-www-form-urlencoded";
+		url = this.config("APIHOST1") + url;
+		let token = uni.getStorageSync("token");
+		if (token) {
+			return new Promise((succ, error) => {
+				uni.request({
+					url: url,
+					data: data,
+					method: "POST",
+					header: {
+						"content-type": header,
+						"token": token
+					},
+					success: function(result) {
+						if (result.data.code == 401) {
+							uni.removeStorageSync("token")
+							uni.removeStorageSync("userId")
+							uni.removeStorageSync("phone")
+							uni.removeStorageSync("openid")
+							uni.removeStorageSync("userName")
+							uni.removeStorageSync("relation")
+							uni.removeStorageSync("relation_id")
+							uni.removeStorageSync("isInvitation")
+							uni.removeStorageSync("zhiFuBao")
+							uni.removeStorageSync("zhiFuBaoName")
+							uni.showToast({
+								title: '用户信息失效,请重新登录!',
+								icon: 'none'
+							})
+						}
+						succ.call(self, result.data)
+					},
+					fail: function(e) {
+						error.call(self, e)
+					}
+				})
+			})
+		} else {
+			return new Promise((succ, error) => {
+				uni.request({
+					url: url,
+					data: data,
+					method: "POST",
+					header: {
+						"content-type": header,
+					},
+					success: function(result) {
+						succ.call(self, result.data)
+					},
+					fail: function(e) {
+						error.call(self, e)
+					}
+				})
+			})
+		}
+	},
+	postJson: function(url, data, header) {
+		header = header || "application/json";
+		url = this.config("APIHOST1") + url;
+		let token = uni.getStorageSync("token");
+		if (token) {
+			return new Promise((succ, error) => {
+				uni.request({
+					url: url,
+					data: data,
+					method: "POST",
+					header: {
+						"content-type": header,
+						"token": token
+					},
+					success: function(result) {
+						if (result.data.code == 401) {
+							uni.removeStorageSync("token")
+							uni.removeStorageSync("userId")
+							uni.removeStorageSync("phone")
+							uni.removeStorageSync("openid")
+							uni.removeStorageSync("userName")
+							uni.removeStorageSync("relation")
+							uni.removeStorageSync("relation_id")
+							uni.removeStorageSync("isInvitation")
+							uni.removeStorageSync("zhiFuBao")
+							uni.removeStorageSync("zhiFuBaoName")
+							uni.showToast({
+								title: '用户信息失效,请重新登录!',
+								icon: 'none'
+							})
+						}
+						succ.call(self, result.data)
+					},
+					fail: function(e) {
+						error.call(self, e)
+					}
+				})
+			})
+		} else {
+			return new Promise((succ, error) => {
+				uni.request({
+					url: url,
+					data: data,
+					method: "POST",
+					header: {
+						"content-type": header,
+					},
+					success: function(result) {
+						succ.call(self, result.data)
+					},
+					fail: function(e) {
+						error.call(self, e)
+					}
+				})
+			})
+		}
+	},
+	getT: function(url, data, header) {
+		header = header || "application/x-www-form-urlencoded";
+		url = this.config("APIHOST1") + url;
+		let token = uni.getStorageSync("token");
+		if (token) {
+			return new Promise((succ, error) => {
+				uni.request({
+					url: url,
+					data: data,
+					method: "GET",
+					header: {
+						"content-type": header,
+						"token": token
+					},
+					success: function(result) {
+						if (result.data.code == 401) {
+							uni.removeStorageSync("token")
+							uni.removeStorageSync("userId")
+							uni.removeStorageSync("phone")
+							uni.removeStorageSync("openid")
+							uni.removeStorageSync("userName")
+							uni.removeStorageSync("relation")
+							uni.removeStorageSync("relation_id")
+							uni.removeStorageSync("isInvitation")
+							uni.removeStorageSync("zhiFuBao")
+							uni.removeStorageSync("zhiFuBaoName")
+							uni.showToast({
+								title: '用户信息失效,请重新登录!',
+								icon: 'none'
+							})
+						}
+						succ.call(self, result.data)
+					},
+					fail: function(e) {
+						error.call(self, e)
+					}
+				})
+			})
+		} else {
+			return new Promise((succ, error) => {
+				uni.request({
+					url: url,
+					data: data,
+					method: "GET",
+					header: {
+						"content-type": header
+					},
+					success: function(result) {
+						succ.call(self, result.data)
+					},
+					fail: function(e) {
+						error.call(self, e)
+					}
+				})
+			})
+		}
+	},
+	get: function(url, data, header) {
+		header = header || "application/x-www-form-urlencoded";
+		url = this.config("APIHOST") + url;
+		let token = uni.getStorageSync("token");
+		return new Promise((succ, error) => {
+			uni.request({
+				url: url,
+				data: data,
+				method: "GET",
+				header: {
+					"content-type": header,
+					"token": token
+				},
+				success: function(result) {
+					if (result.data.code == 401) {
+						uni.removeStorageSync("token")
+						uni.removeStorageSync("userId")
+						uni.removeStorageSync("phone")
+						uni.removeStorageSync("openid")
+						uni.removeStorageSync("userName")
+						uni.removeStorageSync("relation")
+						uni.removeStorageSync("relation_id")
+						uni.removeStorageSync("isInvitation")
+						uni.removeStorageSync("zhiFuBao")
+						uni.removeStorageSync("zhiFuBaoName")
+						uni.showToast({
+							title: '用户信息失效,请重新登录!',
+							icon: 'none'
+						})
+					}
+					succ.call(self, result.data)
+				},
+				fail: function(e) {
+					error.call(self, e)
+				}
+			})
+		})
+	},
+	getMsg: function(url, data, header) {
+		header = header || "application/x-www-form-urlencoded";
+		url = this.config("APIHOST2") + url;
+		let token = uni.getStorageSync("token");
+		return new Promise((succ, error) => {
+			uni.request({
+				url: url,
+				data: data,
+				method: "GET",
+				header: {
+					"content-type": header,
+					"token": token
+				},
+				success: function(result) {
+					if (result.data.code == 401) {
+						uni.removeStorageSync("token")
+						uni.removeStorageSync("userId")
+						uni.removeStorageSync("phone")
+						uni.removeStorageSync("openid")
+						uni.removeStorageSync("userName")
+						uni.removeStorageSync("relation")
+						uni.removeStorageSync("relation_id")
+						uni.removeStorageSync("isInvitation")
+						uni.removeStorageSync("zhiFuBao")
+						uni.removeStorageSync("zhiFuBaoName")
+						uni.showToast({
+							title: '用户信息失效,请重新登录!',
+							icon: 'none'
+						})
+					}
+					succ.call(self, result.data)
+				},
+				fail: function(e) {
+					error.call(self, e)
+				}
+			})
+		})
+	}
+}

+ 200 - 0
common/queue.js

@@ -0,0 +1,200 @@
+/**
+ * 
+ * @author maxd
+ * @date 2019.8.1
+ */
+module.exports = {
+	//微信的appId
+	getWxAppid() {
+		return 'wxd0bec3a917085e78'
+	},
+	//全局邀请码
+	getInvitation() {
+		return uni.getStorageSync("publicRelation")
+	},
+	//获取APP下载地址
+	getAppDownUrl() {
+		return uni.getStorageSync("appurl")
+	},
+	//全局域名 部分html中需要单独替换 需要修改config中的网络请求域名
+	publicYuMing() {
+		return 'https://zp.xianmaxiong.com'
+	},
+	logout() {
+		this.remove("token");
+		this.remove("userId");
+		this.remove("mobile");
+		this.remove("openid");
+		this.remove("nickName");
+		this.remove("relation");
+		this.remove("image_url");
+		this.remove("relation_id");
+		this.remove("isInvitation");
+		this.remove("member");
+	},
+	loginClear() {
+		this.remove("token");
+		this.remove("userId");
+		this.remove("mobile");
+		this.remove("nickName");
+		this.remove("image_url");
+		this.remove("relation_id");
+		this.remove("isInvitation");
+		this.remove("member");
+	},
+	showLoading(title) {
+		uni.showLoading({
+			title: title
+		});
+	},
+	showToast(title) {
+		uni.showToast({
+			title: title,
+			mask: false,
+			duration: 2000,
+			icon: "none"
+		});
+	},
+	getChatSearchKeys: function(key) {
+		let list = uni.getStorageSync("chatSearchKeys");
+		let keys = key.replace(/\s*/g, "")
+		let over = false
+		for (var i = 0; i < keys.length; i++) {
+			// console.error(keys[i])
+			if (list.indexOf(keys[i]) != -1) {
+				over = true;
+				// return over;
+			}
+
+		}
+		return over;
+
+	},
+	setJson: function(key, value) {
+		let jsonString = JSON.stringify(value);
+		try {
+			uni.setStorageSync(key, jsonString);
+		} catch (e) {
+			// error
+		}
+	},
+	setData: function(key, value) {
+		try {
+			uni.setStorageSync(key, value);
+		} catch (e) {
+			// error
+		}
+	},
+	getData: function(key) {
+		try {
+			const value = uni.getStorageSync(key);
+			if (value) {
+				return value;
+			}
+		} catch (e) {
+			// error
+		}
+
+	},
+	getJson: function(key) {
+		try {
+			const value = uni.getStorageSync(key);
+			if (value) {
+				return JSON.parse(value);
+			}
+		} catch (e) {
+			// error
+		}
+
+	},
+	clear: function() {
+		uni.clearStorage();
+	},
+	get: function(key) { //获取队列里面全部的数据
+		let data = this.getJson(key);
+		if (data instanceof Array) {
+			return data;
+		}
+		return [];
+	},
+	insert: function(param) { //队列插入数据
+		param.capacityNum = param.capacityNum || 100; //队列容量 默认队列中超过100条数据,自动删除尾部
+		let data = this.getJson(param.key);
+		if (data instanceof Array) {
+			if (data.length > param.capacityNum) {
+				let total = data.length - param.capacityNum;
+				for (let i = 0; i < total; i++) {
+					data.pop();
+				}
+			}
+			data.unshift(param.value);
+		} else {
+			data = [];
+			data.push(param.value);
+		}
+		this.setJson(param.key, data);
+	},
+	removeItem: function(key, itemIds) { //提供itemIds数组 批量删除队列中的某项数据
+		let data = this.getJson(key);
+		if (data instanceof Array) {
+			for (let i = 0; i < itemIds.length; i++) {
+				for (let p = 0; p < data.length; p++) {
+					if (itemIds[i] === data[p].itemid) {
+						data.splice(p, 1);
+						break;
+					}
+				}
+			}
+			this.setJson(key, data);
+		}
+	},
+	isExist: function(key, itemId) { //检测某条数据在队列中是否存在
+		let data = this.getJson(key);
+		if (data instanceof Array) {
+			for (let p = 0; p < data.length; p++) {
+				if (itemId === data[p].itemid) {
+					return true;
+				}
+			}
+		}
+		return false;
+	},
+	isExistPdd: function(key, itemId) { //检测某条数据在队列中是否存在
+		let data = this.getJson(key);
+		if (data instanceof Array) {
+			for (let p = 0; p < data.length; p++) {
+				if (itemId === data[p].goodsId) {
+					return true;
+				}
+			}
+		}
+		return false;
+	},
+	isExistJd: function(key, itemId) { //检测某条数据在队列中是否存在
+		let data = this.getJson(key);
+		if (data instanceof Array) {
+			for (let p = 0; p < data.length; p++) {
+				if (itemId === data[p].skuId) {
+					return true;
+				}
+			}
+		}
+		return false;
+	},
+	remove: function(key) { //删除某条队列
+		try {
+			uni.removeStorageSync(key);
+			//localStorage.removeItem(key)
+		} catch (e) {
+			// error
+		}
+	},
+	getCount: function(key) { //获取队列中全部数据数量
+
+		let data = this.getJson(key);
+		if (data instanceof Array) {
+			return data.length;
+		}
+		return 0;
+	},
+};

BIN
components/.DS_Store


+ 350 - 0
components/btnPopous/btnPopous.vue

@@ -0,0 +1,350 @@
+<template>
+	<view>
+		<view class="jobAll" :class="topY==true?'end':'start'" @touchstart="start" @touchend="end"
+			@touchmove.stop="move">
+			<view class="jobAll-search flex justify-center">
+				<view class="jobAll-search-box">
+					<u-dropdown :mask='false' active-color="#00B88F">
+						<u-dropdown-item v-model="value1" title="岗位" @change="change1"
+							:options="options1"></u-dropdown-item>
+						<u-dropdown-item v-model="value2" title="经验" @change="change2"
+							:options="options2"></u-dropdown-item>
+						<u-dropdown-item v-model="value3" title="薪资" @change="change3"
+							:options="options3"></u-dropdown-item>
+						<u-dropdown-item v-model="value4" title="城市" @change="change4"
+							:options="options4"></u-dropdown-item>
+					</u-dropdown>
+				</view>
+			</view>
+			<view class="jobAll-item flex justify-center">
+				<view class="jobAll-item-box">
+					<scroll-view scroll-y="true" v-if="list.length>0" style="width: 100%;height: 100%;"
+						:refresher-enabled="true" :refresher-triggered="refresher" @refresherpulling="scrolltoupper"
+						@refresherrefresh="refresherrefresh" @scrolltolower="scrolltolower">
+						<view class="jobAll-item-box-item" v-for="(item,index) in list" :key="index"
+							@tap.native="gotos(item.postPushId)">
+							<view class="jobAll-item-box-item-title flex align-center justify-between">
+								<text>{{item.ruleClassifyName}}</text>
+								<text>{{item.salaryRange}}</text>
+							</view>
+							<view class="jobAll-item-box-item-label flex align-center flex-wrap">
+								<view class="jobAll-item-box-item-labels">
+									{{item.education}}
+								</view>
+								<view class="jobAll-item-box-item-labels">
+									{{item.experience}}
+								</view>
+								<view class="jobAll-item-box-item-labels">
+									{{item.industry}}
+								</view>
+							</view>
+							<view class="jobAll-item-box-item-line" v-if="(index + 1) != list.length">
+
+							</view>
+						</view>
+					</scroll-view>
+					<empty v-else />
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	import empty from '../empty.vue'
+	export default {
+		name: "btnPopous",
+		components: {
+			empty
+		},
+		props: {
+			//公司id
+			companyId: {
+				type: String,
+				default: ''
+			},
+			//城市数组
+			cittArr: {
+				type: Array,
+				default: []
+			},
+			//薪资数组
+			moneyArr: {
+				type: Array,
+				default: []
+			},
+			//经验数组
+			jyArr: {
+				type: Array,
+				default: []
+			},
+			//岗位分类
+			classify: {
+				type: Array,
+				default: []
+			}
+		},
+		data() {
+			return {
+				refresher: false,
+				list: [],
+				clientX: '',
+				clientY: '',
+				topY: false,
+				value1: 1,
+				value2: 1,
+				value3: 1,
+				value4: 1,
+				options1: [],
+				options2: [],
+				options3: [],
+				options4: [],
+				page: 1,
+				pages: 1,
+				limit: 10,
+				projectName: '', //岗位名称
+				address: '', //城市
+				salaryRange: '', //薪资
+				experience: '', //经验
+			};
+		},
+		watch: {
+			//城市
+			cittArr(newArr, oldArr) {
+				//拿到最新传递的城市数据,处理成需要的格式
+				let arr = [{
+					label: '全部',
+					value: '',
+				}]
+				this.cittArr.map(item => {
+					let obj = {
+						label: item,
+						value: item,
+					}
+					arr.push(obj)
+				})
+				this.options4 = arr
+			},
+			//岗位分类列表
+			classify(newArr, oldArr) {
+				//拿到最新传递的岗位数据,处理成需要的格式
+				let arr = [{
+					label: '全部',
+					value: '',
+				}]
+				this.classify.map(item => {
+					let obj = {
+						label: item,
+						value: item,
+					}
+					arr.push(obj)
+				})
+				this.options1 = arr
+			},
+			//经验
+			jyArr(newArr, oldArr) {
+				//拿到最新传递的经验数据,处理成需要的格式
+				let arr = [{
+					label: '全部',
+					value: '',
+				}]
+				this.jyArr.map(item => {
+					let obj = {
+						label: item.value,
+						value: item.value,
+					}
+					arr.push(obj)
+				})
+				this.options2 = arr
+			},
+			//薪资
+			moneyArr(newArr, oldArr) {
+				//拿到最新传递的薪资数据,处理成需要的格式
+				let arr = [{
+					label: '全部',
+					value: '',
+				}]
+				this.moneyArr.map(item => {
+					let obj = {
+						label: item.value,
+						value: item.value,
+					}
+					arr.push(obj)
+				})
+				this.options3 = arr
+			},
+		},
+		created() {
+			this.userGetPostPushList()
+		},
+		methods: {
+			change1(e) {
+				this.projectName = e
+				this.userGetPostPushList()
+			},
+			change2(e) {
+				this.experience = e
+				this.userGetPostPushList()
+			},
+			change3(e) {
+				this.salaryRange = e
+				this.userGetPostPushList()
+			},
+			change4(e) {
+				this.address = e
+				this.userGetPostPushList()
+			},
+			/**
+			 * 上拉加载更多
+			 */
+			scrolltolower() {
+				if (this.page < this.pages) {
+					this.page += 1
+					this.userGetPostPushList()
+				}
+			},
+			/**
+			 * 获取岗位列表
+			 */
+			userGetPostPushList() {
+				let data = {
+					companyId: this.companyId,
+					page: this.page,
+					limit: this.limit,
+					ruleClassifyName: this.projectName, //岗位名称
+					city: this.address, //城市
+					salaryRange: this.salaryRange, //薪资
+					experience: this.experience, //经验
+				}
+				this.$Request.getT("/app/postPush/userGetPostPushList", data).then(res => {
+					if (res.code == 0) {
+						this.pages = res.data.pages
+						if (this.page == 1) {
+							this.list = res.data.records
+						} else {
+							this.list = [...this.list, ...res.data.records]
+						}
+					}
+				})
+			},
+			gotos(postPushId) {
+				console.log('rrrrrrrrrrrrrrrrrrrrrrrr')
+				uni.navigateTo({
+					url: '/pages/index/game/order?postPushId=' + postPushId
+				})
+			},
+			refresherrefresh() {
+				this.refresher = false
+			},
+			scrolltoupper(e) {
+				// this.refresher = true
+				this.refresher = true
+				this.topY = false
+
+			},
+			start(e) {
+				this.clientX = e.changedTouches[0].clientX
+				this.clientY = e.changedTouches[0].clientY
+			},
+			end(e) {
+				let subX = e.changedTouches[0].clientX - this.clientX
+				let subY = e.changedTouches[0].clientY - this.clientY
+				if (subY < -50) {
+					this.topY = true
+				} else if (subY > 200) {
+					this.topY = false
+				}
+			},
+			move(e) {
+
+			},
+		}
+	}
+</script>
+
+<style lang="scss">
+	.jobAll {
+		width: 100%;
+		height: 100vh;
+		background-color: #FFFFFF;
+		border-radius: 30px 30px 0px 0px;
+		position: fixed;
+		z-index: 99999;
+
+		.jobAll-search {
+			width: 100%;
+			margin-top: 20rpx;
+
+			.jobAll-search-box {
+				width: 686rpx;
+				height: 100%;
+			}
+		}
+
+		.jobAll-item {
+			width: 100%;
+			height: 80vh;
+			margin-top: 60rpx;
+
+			.jobAll-item-box {
+				width: 686rpx;
+				height: 100%;
+
+				.jobAll-item-box-item {
+					width: 100%;
+					margin-bottom: 20rpx;
+
+					.jobAll-item-box-item-title {
+						width: 100%;
+
+						text:nth-of-type(1) {
+							color: #1F1F1F;
+							font-size: 38rpx;
+							font-weight: 800;
+						}
+
+						text:nth-of-type(2) {
+							color: #00B88F;
+							font-size: 38rpx;
+							font-weight: bold;
+						}
+					}
+
+					.jobAll-item-box-item-label {
+						width: 100%;
+						margin-top: 30rpx;
+
+						.jobAll-item-box-item-labels {
+							color: #666666;
+							font-size: 26rpx;
+							font-weight: 500;
+							padding: 10rpx 15rpx;
+							border-radius: 8rpx;
+							background-color: #F6F6F6;
+							margin-right: 20rpx;
+							margin-bottom: 20rpx;
+						}
+					}
+
+					.jobAll-item-box-item-line {
+						width: 100%;
+						border-bottom: 1rpx solid #E6E6E6;
+						margin-top: 40rpx;
+					}
+				}
+			}
+		}
+	}
+
+	.start {
+		border-radius: 30px 30px 0px 0px !important;
+		bottom: -80vh;
+		transition-duration: .5s
+	}
+
+	.end {
+		border-radius: 0 !important;
+		bottom: 0;
+		transition-duration: .5s
+	}
+</style>

+ 409 - 0
components/city-select/city-select.vue

@@ -0,0 +1,409 @@
+<template>
+	<!-- 城市选择-->
+	<view class="city-select">
+		<scroll-view :scroll-top="scrollTop" scroll-y="true" class="city-select-main" id="city-select-main"
+			:scroll-into-view="toView">
+			<!-- 预留搜索-->
+			<view class="city-serach" v-if="isSearch"><input @input="keyInput" :placeholder="placeholder"
+					class="city-serach-input" /></view>
+			<!-- 当前定位城市 -->
+			<view class="hot-title" v-if="activeCity && !serachCity">当前定位城市</view>
+			<view class="hot-city" v-if="activeCity && !serachCity">
+				<view class="hot-item" @click="cityTrigger(activeCity)">{{ activeCity[formatName] }}</view>
+			</view>
+			<!-- 热门城市 -->
+			<view class="hot-title" v-if="hotCity.length > 0 && !serachCity">热门城市</view>
+			<view class="hot-city" v-if="hotCity.length > 0 && !serachCity">
+				<template v-for="(item, index) in hotCity">
+					<view :key="index" @click="cityTrigger(item, 'hot')" class="hot-item">{{ item[formatName] }}</view>
+				</template>
+			</view>
+			<!-- 城市列表(搜索前) -->
+			<view class="citys" v-if="!serachCity">
+				<view v-for="(city, index) in sortItems" :key="index" v-show="city.isCity" class="citys-row">
+					<view class="citys-item-letter" :id="'city-letter-' + (city.name === '#' ? '0' : city.name)">
+						{{ city.name }}</view>
+					<view class="citys-item" v-for="(item, inx) in city.citys" :key="inx" @click="cityTrigger(item)">
+						{{ item.cityName }}</view>
+				</view>
+			</view>
+			<!-- 城市列表(搜索后)  -->
+			<view class="citys" v-if="serachCity">
+				<view v-for="(item, index) in searchDatas" :key="index" class="citys-row">
+					<view class="citys-item" :key="index" @click="cityTrigger(item)">{{ item.name }}</view>
+				</view>
+			</view>
+		</scroll-view>
+		<!-- 城市选择索引-->
+		<view class="city-indexs-view" v-if="!serachCity">
+			<view class="city-indexs">
+				<view v-for="(cityIns, index) in handleCity" class="city-indexs-text" v-show="cityIns.isCity"
+					:key="index" @click="cityindex(cityIns.forName)">
+					{{ cityIns.name }}
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	import citySelect from './citySelect.js';
+	export default {
+		props: {
+			//查询提示文字
+			placeholder: {
+				type: String,
+				default: '请输入城市名称'
+			},
+			//传入要排序的名称
+			formatName: {
+				type: String,
+				default: 'cityName'
+			},
+			//当前定位城市
+			activeCity: {
+				type: Object,
+				default: () => null
+			},
+			//热门城市
+			hotCity: {
+				type: Array,
+				default: () => []
+			},
+			//城市数据
+			obtainCitys: {
+				type: Array,
+				default: () => []
+			},
+			//是否有搜索
+			isSearch: {
+				type: Boolean,
+				default: true
+			}
+		},
+		data() {
+			return {
+				toView: 'city-letter-Find', //锚链接 初始值
+				scrollTop: 0, //scroll-view 滑动的距离
+				cityindexs: [], // 城市索引
+				activeCityIndex: '', // 当前所在的城市索引
+				handleCity: [], // 处理后的城市数据
+				serachCity: '', // 搜索的城市
+				cityData: []
+			};
+		},
+		computed: {
+			/**
+			 * @desc 城市列表排序
+			 * @return  Array
+			 */
+			sortItems() {
+				for (let index = 0; index < this.handleCity.length; index++) {
+					if (this.handleCity[index].isCity) {
+						let cityArr = this.handleCity[index].citys;
+						cityArr = cityArr.sort(function(a, b) {
+							var value1 = a.unicode;
+							var value2 = b.unicode;
+							return value1 - value2;
+						});
+					}
+				}
+				return this.handleCity;
+			},
+			/**
+			 * @desc 搜索后的城市列表
+			 * @return Array
+			 */
+			searchDatas() {
+				var searchData = [];
+				for (let i = 0; i < this.cityData.length; i++) {
+					if (this.cityData[i][this.formatName].indexOf(this.serachCity) !== -1) {
+						searchData.push({
+							oldData: this.cityData[i],
+							name: this.cityData[i][this.formatName]
+						});
+					}
+				}
+				return searchData;
+			}
+		},
+		created() {
+			// 初始化城市数据
+			this.cityData = this.obtainCitys;
+			this.initializationCity();
+			this.buildCityindexs();
+		},
+		watch: {
+			obtainCitys(newData) {
+				this.updateCitys(newData);
+			}
+		},
+		methods: {
+			/**
+			 * @desc 初始化
+			 */
+			updateCitys(data) {
+				if (data && data.length) {
+					this.cityData = data;
+					this.initializationCity();
+					this.buildCityindexs();
+				}
+			},
+			/**
+			 * @desc 监听输入框的值
+			 */
+			keyInput(event) {
+				this.serachCity = event.detail.value;
+			},
+			/**
+			 * @desc 初始化城市数据
+			 * @return undefind
+			 */
+			initializationCity() {
+				this.handleCity = [];
+				const cityLetterArr = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q',
+					'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '#'
+				];
+				for (let index = 0; index < cityLetterArr.length; index++) {
+					this.handleCity.push({
+						name: cityLetterArr[index],
+						isCity: false, // 用于区分是否含有当前字母开头的城市
+						citys: [], // 存放城市首字母含是此字母的数组
+						forName: 'city-letter-' + (cityLetterArr[index] == '#' ? '0' : cityLetterArr[
+							index]) //label的绑定
+					});
+				}
+			},
+			/**
+			 * @desc 得到城市的首字母
+			 * @param str String
+			 */
+			getLetter(str) {
+				return citySelect.getFirstLetter(str[0]);
+			},
+			/**
+			 * @desc 构建城市索引
+			 * @return undefind
+			 */
+			buildCityindexs() {
+				this.cityindexs = [];
+				for (let i = 0; i < this.cityData.length; i++) {
+					// 获取首字母
+					const cityLetter = this.getLetter(this.cityData[i][this.formatName]).firstletter;
+					// 获取当前城市首字母的unicode,用作后续排序
+					const unicode = this.getLetter(this.cityData[i][this.formatName]).unicode;
+
+					const index = this.cityIndexPosition(cityLetter);
+					if (this.cityindexs.indexOf(cityLetter) === -1) {
+						this.handleCity[index].isCity = true;
+						this.cityindexs.push(cityLetter);
+					}
+
+					this.handleCity[index].citys.push({
+						cityName: this.cityData[i][this.formatName],
+						unicode: unicode,
+						oldData: this.cityData[i]
+					});
+				}
+			},
+			/**
+			 * @desc 滑动到城市索引所在的地方
+			 * @param id String 城市索引
+			 */
+			cityindex(id) {
+				this.toView = id;
+				// //创建节点查询器
+				// const query = uni.createSelectorQuery().in(this)
+				// var that = this
+				// that.scrollTop = 0
+				// //滑动到指定位置(解决方法:重置到顶部,重新计算,影响:页面会闪一下)
+				// setTimeout(() => {
+				// 	query
+				// 		.select('#city-letter-' + (id === '#' ? '0' : id))
+				// 		.boundingClientRect(data => {
+				// 			// console.log("得到布局位置信息" + JSON.stringify(data));
+				// 			// console.log("节点离页面顶部的距离为" + data.top);
+				// 			data ? (that.scrollTop = data.top) : void 0
+				// 		})
+				// 		.exec()
+				// }, 0)
+			},
+			/**
+			 * @desc 获取城市首字母的unicode
+			 * @param letter String 城市索引
+			 */
+			cityIndexPosition(letter) {
+				if (!letter) {
+					return '';
+				}
+				const ACode = 65;
+				return letter === '#' ? 26 : letter.charCodeAt(0) - ACode;
+			},
+			/** @desc 城市列表点击事件
+			 *  @param Object
+			 */
+			cityTrigger(item) {
+				// 传值到父组件
+				this.$emit('cityClick', item.oldData ? item.oldData : item);
+			}
+		}
+	};
+</script>
+
+<style lang="scss">
+	//宽度转换vw
+	@function vww($number) {
+		@return ($number / 375) * 750+rpx;
+	}
+
+	page {
+		background-color: #F7F7F7;
+	}
+
+	.bg {
+		background-color: #FFFFFF;
+	}
+
+	view {
+		box-sizing: border-box;
+	}
+
+	.city-serach {
+		width: 100%;
+		// color: #fff;
+		padding: 0 vww(10);
+
+		&-input {
+			margin: vww(10) 0;
+			height: vww(40);
+			line-height: vww(40);
+			font-size: vww(14);
+			padding: 0 vww(5);
+			border: 1px solid #e5e5e5;
+			border-radius: 3px;
+		}
+	}
+
+	.city-select-main {
+		position: relative;
+		// overflow: scroll;
+		// -webkit-overflow-scrolling: touch;
+		width: 100%;
+		height: 100%;
+		// background: #f6f5fa;
+		// overflow-y: auto;
+	}
+
+	.city-select {
+		position: relative;
+		width: 100vw;
+		height: 100vh;
+		background: #ffffff;
+
+		// 热门城市
+		.hot-title {
+			padding-left: vww(23);
+			width: 100vw;
+			font-size: 14px;
+			line-height: vww(40);
+			color: #9b9b9b;
+		}
+
+		.hot-city {
+			padding-left: vww(23);
+			padding-right: vww(20);
+			overflow: hidden;
+			width: 100vw;
+
+			.hot-item {
+				float: left;
+				padding: 0 vww(5);
+				margin-right: vww(16);
+				margin-bottom: vww(6);
+				overflow: hidden;
+				width: vww(100);
+				height: vww(31);
+				font-size: 14px;
+				text-align: center;
+
+				display: -webkit-box;
+				-webkit-box-orient: vertical;
+				-webkit-line-clamp: 1;
+
+				line-height: vww(31);
+				color: #4a4a4a;
+				background: #fff;
+				border: 1px solid #ebebf0;
+
+				&:nth-child(3n) {
+					margin-right: 0;
+				}
+			}
+
+			.hot-hidden {
+				display: none;
+				margin-right: 0;
+			}
+		}
+
+		.citys {
+			.citys-row {
+				padding: 0 vww(18);
+				width: 100%;
+				font-size: 14px;
+				// background: #fff;
+
+				.citys-item-letter {
+					margin-left: vww(-18);
+					padding-left: vww(18);
+					margin-top: -1px;
+					width: 100vw;
+					line-height: vww(30);
+					// color: #fff;
+					// background: #f6f5fa;
+					border-top: none;
+				}
+
+				.citys-item {
+					width: 100%;
+					line-height: vww(50);
+					// color: #FFFFFF;
+					border-bottom: 1px solid #e5e5e5;
+
+					&:last-child {
+						border: none;
+					}
+				}
+			}
+		}
+
+		.city-indexs-view {
+			position: absolute;
+			right: 0;
+			top: 0;
+			z-index: 999;
+			display: flex;
+			width: vww(20);
+			height: 100%;
+			text-align: center;
+
+			.city-indexs {
+				width: vww(20);
+				text-align: center;
+				vertical-align: middle;
+				align-self: center;
+
+				.city-indexs-text {
+					margin-bottom: vww(10);
+					width: vww(20);
+					font-size: 12px;
+					color: #000000;
+
+					&:last-child {
+						margin-bottom: 0;
+					}
+				}
+			}
+		}
+	}
+</style>

File diff suppressed because it is too large
+ 2 - 0
components/city-select/citySelect.js


BIN
components/colorui/.DS_Store


+ 184 - 0
components/colorui/animation.css

@@ -0,0 +1,184 @@
+/* 
+  Animation 微动画  
+  基于ColorUI组建库的动画模块 by 文晓港 2019年3月26日19:52:28
+ */
+
+/* css 滤镜 控制黑白底色gif的 */
+.gif-black{  
+  mix-blend-mode: screen;  
+}
+.gif-white{  
+  mix-blend-mode: multiply; 
+}
+
+
+/* Animation css */
+[class*=animation-] {
+    animation-duration: .5s;
+    animation-timing-function: ease-out;
+    animation-fill-mode: both
+}
+
+.animation-fade {
+    animation-name: fade;
+    animation-duration: .8s;
+    animation-timing-function: linear
+}
+
+.animation-scale-up {
+    animation-name: scale-up
+}
+
+.animation-scale-down {
+    animation-name: scale-down
+}
+
+.animation-slide-top {
+    animation-name: slide-top
+}
+
+.animation-slide-bottom {
+    animation-name: slide-bottom
+}
+
+.animation-slide-left {
+    animation-name: slide-left
+}
+
+.animation-slide-right {
+    animation-name: slide-right
+}
+
+.animation-shake {
+    animation-name: shake
+}
+
+.animation-reverse {
+    animation-direction: reverse
+}
+
+@keyframes fade {
+    0% {
+        opacity: 0
+    }
+
+    100% {
+        opacity: 1
+    }
+}
+
+@keyframes scale-up {
+    0% {
+        opacity: 0;
+        transform: scale(.2)
+    }
+
+    100% {
+        opacity: 1;
+        transform: scale(1)
+    }
+}
+
+@keyframes scale-down {
+    0% {
+        opacity: 0;
+        transform: scale(1.8)
+    }
+
+    100% {
+        opacity: 1;
+        transform: scale(1)
+    }
+}
+
+@keyframes slide-top {
+    0% {
+        opacity: 0;
+        transform: translateY(-100%)
+    }
+
+    100% {
+        opacity: 1;
+        transform: translateY(0)
+    }
+}
+
+@keyframes slide-bottom {
+    0% {
+        opacity: 0;
+        transform: translateY(100%)
+    }
+
+    100% {
+        opacity: 1;
+        transform: translateY(0)
+    }
+}
+
+@keyframes shake {
+
+    0%,
+    100% {
+        transform: translateX(0)
+    }
+
+    10% {
+        transform: translateX(-9px)
+    }
+
+    20% {
+        transform: translateX(8px)
+    }
+
+    30% {
+        transform: translateX(-7px)
+    }
+
+    40% {
+        transform: translateX(6px)
+    }
+
+    50% {
+        transform: translateX(-5px)
+    }
+
+    60% {
+        transform: translateX(4px)
+    }
+
+    70% {
+        transform: translateX(-3px)
+    }
+
+    80% {
+        transform: translateX(2px)
+    }
+
+    90% {
+        transform: translateX(-1px)
+    }
+}
+
+@keyframes slide-left {
+    0% {
+        opacity: 0;
+        transform: translateX(-100%)
+    }
+
+    100% {
+        opacity: 1;
+        transform: translateX(0)
+    }
+}
+
+@keyframes slide-right {
+    0% {
+        opacity: 0;
+        transform: translateX(100%)
+    }
+
+    100% {
+        opacity: 1;
+        transform: translateX(0)
+    }
+}

+ 65 - 0
components/colorui/components/cu-custom.vue

@@ -0,0 +1,65 @@
+<template>
+	<view>
+		<view class="cu-custom" :style="[{height:CustomBar + 'px'}]">
+			<view class="cu-bar fixed" :style="style" :class="[bgImage!=''?'none-bg text-white bg-img':'',bgColor]">
+				<view class="action" @tap="BackPage" v-if="isBack">
+					<text class="cuIcon-back"></text>
+					<slot name="backText"></slot>
+				</view>
+				<view class="content" :style="[{top:StatusBar + 'px'}]">
+					<slot name="content"></slot>
+				</view>
+				<slot name="right"></slot>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				StatusBar: this.StatusBar,
+				CustomBar: this.CustomBar
+			};
+		},
+		name: 'cu-custom',
+		computed: {
+			style() {
+				var StatusBar= this.StatusBar;
+				var CustomBar= this.CustomBar;
+				var bgImage = this.bgImage;
+				var style = `height:${CustomBar}px;padding-top:${StatusBar}px;`;
+				if (this.bgImage) {
+					style = `${style}background-image:url(${bgImage});`;
+				}
+				return style
+			}
+		},
+		props: {
+			bgColor: {
+				type: String,
+				default: ''
+			},
+			isBack: {
+				type: [Boolean, String],
+				default: false
+			},
+			bgImage: {
+				type: String,
+				default: ''
+			},
+		},
+		methods: {
+			BackPage() {
+				uni.navigateBack({
+					delta: 1
+				});
+			}
+		}
+	}
+</script>
+
+<style>
+
+</style>

File diff suppressed because it is too large
+ 36 - 0
components/colorui/icon.css


+ 3921 - 0
components/colorui/main.css

@@ -0,0 +1,3921 @@
+/*
+  ColorUi for uniApp  v2.1.6 | by 文晓港 2019-05-31 10:44:24
+  仅供学习交流,如作它用所承受的法律责任一概与作者无关
+
+  *使用ColorUi开发扩展与插件时,请注明基于ColorUi开发
+
+  (QQ交流群:240787041)
+*/
+
+/* ==================
+        初始化
+ ==================== */
+body {
+	background-color: #F8F8F8;
+	font-size: 28upx;
+	color: #333333;
+	font-family: Helvetica Neue, Helvetica, sans-serif;
+}
+
+view,
+scroll-view,
+swiper,
+button,
+input,
+textarea,
+label,
+navigator,
+image {
+	box-sizing: border-box;
+}
+
+.round {
+	border-radius: 5000upx;
+}
+
+.radius {
+	border-radius: 10upx;
+}
+
+/* ==================
+          图片
+ ==================== */
+
+image {
+	max-width: 100%;
+	display: inline-block;
+	position: relative;
+	z-index: 0;
+}
+
+image.loading::before {
+	content: "";
+	background-color: #f5f5f5;
+	display: block;
+	position: absolute;
+	width: 100%;
+	height: 100%;
+	z-index: -2;
+}
+
+image.loading::after {
+	content: "\e7f1";
+	font-family: "cuIcon";
+	position: absolute;
+	top: 0;
+	left: 0;
+	width: 32upx;
+	height: 32upx;
+	line-height: 32upx;
+	right: 0;
+	bottom: 0;
+	z-index: -1;
+	font-size: 32upx;
+	margin: auto;
+	color: #ccc;
+	-webkit-animation: cuIcon-spin 2s infinite linear;
+	animation: cuIcon-spin 2s infinite linear;
+	display: block;
+}
+
+.response {
+	width: 100%;
+}
+
+/* ==================
+         开关
+ ==================== */
+
+switch,
+checkbox,
+radio {
+	position: relative;
+}
+
+switch::after,
+switch::before {
+	font-family: "cuIcon";
+	content: "\e645";
+	position: absolute;
+	color: #ffffff !important;
+	top: 0%;
+	left: 0upx;
+	font-size: 26upx;
+	line-height: 26px;
+	width: 50%;
+	text-align: center;
+	pointer-events: none;
+	transform: scale(0, 0);
+	transition: all 0.3s ease-in-out 0s;
+	z-index: 9;
+	bottom: 0;
+	height: 26px;
+	margin: auto;
+}
+
+switch::before {
+	content: "\e646";
+	right: 0;
+	transform: scale(1, 1);
+	left: auto;
+}
+
+switch[checked]::after,
+switch.checked::after {
+	transform: scale(1, 1);
+}
+
+switch[checked]::before,
+switch.checked::before {
+	transform: scale(0, 0);
+}
+
+/* #ifndef MP-ALIPAY */
+radio::before,
+checkbox::before {
+	font-family: "cuIcon";
+	content: "\e645";
+	position: absolute;
+	color: #ffffff !important;
+	top: 50%;
+	margin-top: -8px;
+	right: 5px;
+	font-size: 32upx;
+	line-height: 16px;
+	pointer-events: none;
+	transform: scale(1, 1);
+	transition: all 0.3s ease-in-out 0s;
+	z-index: 9;
+}
+
+radio .wx-radio-input,
+checkbox .wx-checkbox-input,
+radio .uni-radio-input,
+checkbox .uni-checkbox-input {
+	margin: 0;
+	width: 24px;
+	height: 24px;
+}
+
+checkbox.round .wx-checkbox-input,
+checkbox.round .uni-checkbox-input {
+	border-radius: 100upx;
+}
+
+/* #endif */
+
+switch[checked]::before {
+	transform: scale(0, 0);
+}
+
+switch .wx-switch-input,
+switch .uni-switch-input {
+	border: none;
+	padding: 0 24px;
+	width: 48px;
+	height: 26px;
+	margin: 0;
+	border-radius: 100upx;
+}
+
+switch .wx-switch-input:not([class*="bg-"]),
+switch .uni-switch-input:not([class*="bg-"]) {
+	background: #8799a3 !important;
+}
+
+switch .wx-switch-input::after,
+switch .uni-switch-input::after {
+	margin: auto;
+	width: 26px;
+	height: 26px;
+	border-radius: 100upx;
+	left: 0upx;
+	top: 0upx;
+	bottom: 0upx;
+	position: absolute;
+	transform: scale(0.9, 0.9);
+	transition: all 0.1s ease-in-out 0s;
+}
+
+switch .wx-switch-input.wx-switch-input-checked::after,
+switch .uni-switch-input.uni-switch-input-checked::after {
+	margin: auto;
+	left: 22px;
+	box-shadow: none;
+	transform: scale(0.9, 0.9);
+}
+
+radio-group {
+	display: inline-block;
+}
+
+
+
+switch.radius .wx-switch-input::after,
+switch.radius .wx-switch-input,
+switch.radius .wx-switch-input::before,
+switch.radius .uni-switch-input::after,
+switch.radius .uni-switch-input,
+switch.radius .uni-switch-input::before {
+	border-radius: 10upx;
+}
+
+switch .wx-switch-input::before,
+radio.radio::before,
+checkbox .wx-checkbox-input::before,
+radio .wx-radio-input::before,
+switch .uni-switch-input::before,
+radio.radio::before,
+checkbox .uni-checkbox-input::before,
+radio .uni-radio-input::before {
+	display: none;
+}
+
+radio.radio[checked]::after,
+radio.radio .uni-radio-input-checked::after {
+	content: "";
+	background-color: transparent;
+	display: block;
+	position: absolute;
+	width: 8px;
+	height: 8px;
+	z-index: 999;
+	top: 0upx;
+	left: 0upx;
+	right: 0;
+	bottom: 0;
+	margin: auto;
+	border-radius: 200upx;
+	/* #ifndef MP */
+	border: 7px solid #ffffff !important;
+	/* #endif */
+
+	/* #ifdef MP */
+	border: 8px solid #ffffff !important;
+	/* #endif */
+}
+
+.switch-sex::after {
+	content: "\e71c";
+}
+
+.switch-sex::before {
+	content: "\e71a";
+}
+
+.switch-sex .wx-switch-input,
+.switch-sex .uni-switch-input {
+	background: #e54d42 !important;
+	border-color: #e54d42 !important;
+}
+
+.switch-sex[checked] .wx-switch-input,
+.switch-sex.checked .uni-switch-input {
+	background: #0081ff !important;
+	border-color: #0081ff !important;
+}
+
+switch.red[checked] .wx-switch-input.wx-switch-input-checked,
+checkbox.red[checked] .wx-checkbox-input,
+radio.red[checked] .wx-radio-input,
+switch.red.checked .uni-switch-input.uni-switch-input-checked,
+checkbox.red.checked .uni-checkbox-input,
+radio.red.checked .uni-radio-input {
+	background-color: #e54d42 !important;
+	border-color: #e54d42 !important;
+	color: #ffffff !important;
+}
+
+switch.orange[checked] .wx-switch-input,
+checkbox.orange[checked] .wx-checkbox-input,
+radio.orange[checked] .wx-radio-input,
+switch.orange.checked .uni-switch-input,
+checkbox.orange.checked .uni-checkbox-input,
+radio.orange.checked .uni-radio-input {
+	background-color: #f37b1d !important;
+	border-color: #f37b1d !important;
+	color: #ffffff !important;
+}
+
+switch.yellow[checked] .wx-switch-input,
+checkbox.yellow[checked] .wx-checkbox-input,
+radio.yellow[checked] .wx-radio-input,
+switch.yellow.checked .uni-switch-input,
+checkbox.yellow.checked .uni-checkbox-input,
+radio.yellow.checked .uni-radio-input {
+	background-color: #fbbd08 !important;
+	border-color: #fbbd08 !important;
+	color: #333333 !important;
+}
+
+switch.olive[checked] .wx-switch-input,
+checkbox.olive[checked] .wx-checkbox-input,
+radio.olive[checked] .wx-radio-input,
+switch.olive.checked .uni-switch-input,
+checkbox.olive.checked .uni-checkbox-input,
+radio.olive.checked .uni-radio-input {
+	background-color: #8dc63f !important;
+	border-color: #8dc63f !important;
+	color: #ffffff !important;
+}
+
+switch.green[checked] .wx-switch-input,
+switch[checked] .wx-switch-input,
+checkbox.green[checked] .wx-checkbox-input,
+checkbox[checked] .wx-checkbox-input,
+radio.green[checked] .wx-radio-input,
+radio[checked] .wx-radio-input,
+switch.green.checked .uni-switch-input,
+switch.checked .uni-switch-input,
+checkbox.green.checked .uni-checkbox-input,
+checkbox.checked .uni-checkbox-input,
+radio.green.checked .uni-radio-input,
+radio.checked .uni-radio-input {
+	background-color: #39b54a !important;
+	border-color: #39b54a !important;
+	color: #ffffff !important;
+	border-color: #39B54A !important;
+}
+
+switch.cyan[checked] .wx-switch-input,
+checkbox.cyan[checked] .wx-checkbox-input,
+radio.cyan[checked] .wx-radio-input,
+switch.cyan.checked .uni-switch-input,
+checkbox.cyan.checked .uni-checkbox-input,
+radio.cyan.checked .uni-radio-input {
+	background-color: #1cbbb4 !important;
+	border-color: #1cbbb4 !important;
+	color: #ffffff !important;
+}
+
+switch.blue[checked] .wx-switch-input,
+checkbox.blue[checked] .wx-checkbox-input,
+radio.blue[checked] .wx-radio-input,
+switch.blue.checked .uni-switch-input,
+checkbox.blue.checked .uni-checkbox-input,
+radio.blue.checked .uni-radio-input {
+	background-color: #0081ff !important;
+	border-color: #0081ff !important;
+	color: #ffffff !important;
+}
+
+switch.purple[checked] .wx-switch-input,
+checkbox.purple[checked] .wx-checkbox-input,
+radio.purple[checked] .wx-radio-input,
+switch.purple.checked .uni-switch-input,
+checkbox.purple.checked .uni-checkbox-input,
+radio.purple.checked .uni-radio-input {
+	background-color: #6739b6 !important;
+	border-color: #6739b6 !important;
+	color: #ffffff !important;
+}
+
+switch.mauve[checked] .wx-switch-input,
+checkbox.mauve[checked] .wx-checkbox-input,
+radio.mauve[checked] .wx-radio-input,
+switch.mauve.checked .uni-switch-input,
+checkbox.mauve.checked .uni-checkbox-input,
+radio.mauve.checked .uni-radio-input {
+	background-color: #9c26b0 !important;
+	border-color: #9c26b0 !important;
+	color: #ffffff !important;
+}
+
+switch.pink[checked] .wx-switch-input,
+checkbox.pink[checked] .wx-checkbox-input,
+radio.pink[checked] .wx-radio-input,
+switch.pink.checked .uni-switch-input,
+checkbox.pink.checked .uni-checkbox-input,
+radio.pink.checked .uni-radio-input {
+	background-color: #e03997 !important;
+	border-color: #e03997 !important;
+	color: #ffffff !important;
+}
+
+switch.brown[checked] .wx-switch-input,
+checkbox.brown[checked] .wx-checkbox-input,
+radio.brown[checked] .wx-radio-input,
+switch.brown.checked .uni-switch-input,
+checkbox.brown.checked .uni-checkbox-input,
+radio.brown.checked .uni-radio-input {
+	background-color: #a5673f !important;
+	border-color: #a5673f !important;
+	color: #ffffff !important;
+}
+
+switch.grey[checked] .wx-switch-input,
+checkbox.grey[checked] .wx-checkbox-input,
+radio.grey[checked] .wx-radio-input,
+switch.grey.checked .uni-switch-input,
+checkbox.grey.checked .uni-checkbox-input,
+radio.grey.checked .uni-radio-input {
+	background-color: #8799a3 !important;
+	border-color: #8799a3 !important;
+	color: #ffffff !important;
+}
+
+switch.gray[checked] .wx-switch-input,
+checkbox.gray[checked] .wx-checkbox-input,
+radio.gray[checked] .wx-radio-input,
+switch.gray.checked .uni-switch-input,
+checkbox.gray.checked .uni-checkbox-input,
+radio.gray.checked .uni-radio-input {
+	background-color: #f0f0f0 !important;
+	border-color: #f0f0f0 !important;
+	color: #333333 !important;
+}
+
+switch.black[checked] .wx-switch-input,
+checkbox.black[checked] .wx-checkbox-input,
+radio.black[checked] .wx-radio-input,
+switch.black.checked .uni-switch-input,
+checkbox.black.checked .uni-checkbox-input,
+radio.black.checked .uni-radio-input {
+	background-color: #333333 !important;
+	border-color: #333333 !important;
+	color: #ffffff !important;
+}
+
+switch.white[checked] .wx-switch-input,
+checkbox.white[checked] .wx-checkbox-input,
+radio.white[checked] .wx-radio-input,
+switch.white.checked .uni-switch-input,
+checkbox.white.checked .uni-checkbox-input,
+radio.white.checked .uni-radio-input {
+	background-color: #ffffff !important;
+	border-color: #ffffff !important;
+	color: #333333 !important;
+}
+
+/* ==================
+          边框
+ ==================== */
+
+/* -- 实线 -- */
+
+.solid,
+.solid-top,
+.solid-right,
+.solid-bottom,
+.solid-left,
+.solids,
+.solids-top,
+.solids-right,
+.solids-bottom,
+.solids-left,
+.dashed,
+.dashed-top,
+.dashed-right,
+.dashed-bottom,
+.dashed-left {
+	position: relative;
+}
+
+.solid::after,
+.solid-top::after,
+.solid-right::after,
+.solid-bottom::after,
+.solid-left::after,
+.solids::after,
+.solids-top::after,
+.solids-right::after,
+.solids-bottom::after,
+.solids-left::after,
+.dashed::after,
+.dashed-top::after,
+.dashed-right::after,
+.dashed-bottom::after,
+.dashed-left::after {
+	content: " ";
+	width: 200%;
+	height: 200%;
+	position: absolute;
+	top: 0;
+	left: 0;
+	border-radius: inherit;
+	transform: scale(0.5);
+	transform-origin: 0 0;
+	pointer-events: none;
+	box-sizing: border-box;
+}
+
+.solid::after {
+	border: 1upx solid rgba(0, 0, 0, 0.1);
+}
+
+.solid-top::after {
+	border-top: 1upx solid rgba(0, 0, 0, 0.1);
+}
+
+.solid-right::after {
+	border-right: 1upx solid rgba(0, 0, 0, 0.1);
+}
+
+.solid-bottom::after {
+	border-bottom: 1upx solid rgba(0, 0, 0, 0.1);
+}
+
+.solid-left::after {
+	border-left: 1upx solid rgba(0, 0, 0, 0.1);
+}
+
+.solids::after {
+	border: 8upx solid #eee;
+}
+
+.solids-top::after {
+	border-top: 8upx solid #eee;
+}
+
+.solids-right::after {
+	border-right: 8upx solid #eee;
+}
+
+.solids-bottom::after {
+	border-bottom: 8upx solid #eee;
+}
+
+.solids-left::after {
+	border-left: 8upx solid #eee;
+}
+
+/* -- 虚线 -- */
+
+.dashed::after {
+	border: 1upx dashed #ddd;
+}
+
+.dashed-top::after {
+	border-top: 1upx dashed #ddd;
+}
+
+.dashed-right::after {
+	border-right: 1upx dashed #ddd;
+}
+
+.dashed-bottom::after {
+	border-bottom: 1upx dashed #ddd;
+}
+
+.dashed-left::after {
+	border-left: 1upx dashed #ddd;
+}
+
+/* -- 阴影 -- */
+
+.shadow[class*='white'] {
+	--ShadowSize: 0 1upx 6upx;
+}
+
+.shadow-lg {
+	--ShadowSize: 0upx 40upx 100upx 0upx;
+}
+
+.shadow-warp {
+	position: relative;
+	box-shadow: 0 0 10upx rgba(0, 0, 0, 0.1);
+}
+
+.shadow-warp:before,
+.shadow-warp:after {
+	position: absolute;
+	content: "";
+	top: 20upx;
+	bottom: 30upx;
+	left: 20upx;
+	width: 50%;
+	box-shadow: 0 30upx 20upx rgba(0, 0, 0, 0.2);
+	transform: rotate(-3deg);
+	z-index: -1;
+}
+
+.shadow-warp:after {
+	right: 20upx;
+	left: auto;
+	transform: rotate(3deg);
+}
+
+.shadow-blur {
+	position: relative;
+}
+
+.shadow-blur::before {
+	content: "";
+	display: block;
+	background: inherit;
+	filter: blur(10upx);
+	position: absolute;
+	width: 100%;
+	height: 100%;
+	top: 10upx;
+	left: 10upx;
+	z-index: -1;
+	opacity: 0.4;
+	transform-origin: 0 0;
+	border-radius: inherit;
+	transform: scale(1, 1);
+}
+
+/* ==================
+          按钮
+ ==================== */
+
+.cu-btn {
+	position: relative;
+	border: 0upx;
+	display: inline-flex;
+	align-items: center;
+	justify-content: center;
+	box-sizing: border-box;
+	padding: 0 30upx;
+	font-size: 28upx;
+	height: 64upx;
+	line-height: 1;
+	text-align: center;
+	text-decoration: none;
+	overflow: visible;
+	margin-left: initial;
+	transform: translate(0upx, 0upx);
+	margin-right: initial;
+}
+
+.cu-btn::after {
+	display: none;
+}
+
+.cu-btn:not([class*="bg-"]) {
+	background-color: #f0f0f0;
+}
+
+.cu-btn[class*="line"] {
+	background-color: transparent;
+}
+
+.cu-btn[class*="line"]::after {
+	content: " ";
+	display: block;
+	width: 200%;
+	height: 200%;
+	position: absolute;
+	top: 0;
+	left: 0;
+	border: 1upx solid currentColor;
+	transform: scale(0.5);
+	transform-origin: 0 0;
+	box-sizing: border-box;
+	border-radius: 12upx;
+	z-index: 1;
+	pointer-events: none;
+}
+
+.cu-btn.round[class*="line"]::after {
+	border-radius: 1000upx;
+}
+
+.cu-btn[class*="lines"]::after {
+	border: 6upx solid currentColor;
+}
+
+.cu-btn[class*="bg-"]::after {
+	display: none;
+}
+
+.cu-btn.sm {
+	padding: 0 20upx;
+	font-size: 20upx;
+	height: 48upx;
+}
+
+.cu-btn.lg {
+	padding: 0 40upx;
+	font-size: 32upx;
+	height: 80upx;
+}
+
+.cu-btn.cuIcon.sm {
+	width: 48upx;
+	height: 48upx;
+}
+
+.cu-btn.cuIcon {
+	width: 64upx;
+	height: 64upx;
+	border-radius: 500upx;
+	padding: 0;
+}
+
+button.cuIcon.lg {
+	width: 80upx;
+	height: 80upx;
+}
+
+.cu-btn.shadow-blur::before {
+	top: 4upx;
+	left: 4upx;
+	filter: blur(6upx);
+	opacity: 0.6;
+}
+
+.cu-btn.button-hover {
+	transform: translate(1upx, 1upx);
+}
+
+.block {
+	display: block;
+}
+
+.cu-btn.block {
+	display: flex;
+}
+
+.cu-btn[disabled] {
+	opacity: 0.6;
+	color: #ffffff;
+}
+
+/* ==================
+          徽章
+ ==================== */
+
+.cu-tag {
+	font-size: 24upx;
+	vertical-align: middle;
+	position: relative;
+	display: inline-flex;
+	align-items: center;
+	justify-content: center;
+	box-sizing: border-box;
+	padding: 0upx 16upx;
+	height: 48upx;
+	font-family: Helvetica Neue, Helvetica, sans-serif;
+	white-space: nowrap;
+}
+
+.cu-tag:not([class*="bg"]):not([class*="line"]) {
+	background-color: #f1f1f1;
+}
+
+.cu-tag[class*="line-"]::after {
+	content: " ";
+	width: 200%;
+	height: 200%;
+	position: absolute;
+	top: 0;
+	left: 0;
+	border: 1upx solid currentColor;
+	transform: scale(0.5);
+	transform-origin: 0 0;
+	box-sizing: border-box;
+	border-radius: inherit;
+	z-index: 1;
+	pointer-events: none;
+}
+
+.cu-tag.radius[class*="line"]::after {
+	border-radius: 12upx;
+}
+
+.cu-tag.round[class*="line"]::after {
+	border-radius: 1000upx;
+}
+
+.cu-tag[class*="line-"]::after {
+	border-radius: 0;
+}
+
+.cu-tag+.cu-tag {
+	margin-left: 10upx;
+}
+
+.cu-tag.sm {
+	font-size: 20upx;
+	padding: 0upx 12upx;
+	height: 32upx;
+}
+
+.cu-capsule {
+	display: inline-flex;
+	vertical-align: middle;
+}
+
+.cu-capsule+.cu-capsule {
+	margin-left: 10upx;
+}
+
+.cu-capsule .cu-tag {
+	margin: 0;
+}
+
+.cu-capsule .cu-tag[class*="line-"]:last-child::after {
+	border-left: 0upx solid transparent;
+}
+
+.cu-capsule .cu-tag[class*="line-"]:first-child::after {
+	border-right: 0upx solid transparent;
+}
+
+.cu-capsule.radius .cu-tag:first-child {
+	border-top-left-radius: 6upx;
+	border-bottom-left-radius: 6upx;
+}
+
+.cu-capsule.radius .cu-tag:last-child::after,
+.cu-capsule.radius .cu-tag[class*="line-"] {
+	border-top-right-radius: 12upx;
+	border-bottom-right-radius: 12upx;
+}
+
+.cu-capsule.round .cu-tag:first-child {
+	border-top-left-radius: 200upx;
+	border-bottom-left-radius: 200upx;
+	text-indent: 4upx;
+}
+
+.cu-capsule.round .cu-tag:last-child::after,
+.cu-capsule.round .cu-tag:last-child {
+	border-top-right-radius: 200upx;
+	border-bottom-right-radius: 200upx;
+	text-indent: -4upx;
+}
+
+.cu-tag.badge {
+	border-radius: 200upx;
+	position: absolute;
+	top: -10upx;
+	right: -10upx;
+	font-size: 20upx;
+	padding: 0upx 10upx;
+	height: 28upx;
+	color: #ffffff;
+}
+
+.cu-tag.badge:not([class*="bg-"]) {
+	background-color: #dd514c;
+}
+
+.cu-tag:empty:not([class*="cuIcon-"]) {
+	padding: 0upx;
+	width: 16upx;
+	height: 16upx;
+	top: -4upx;
+	right: -4upx;
+}
+
+.cu-tag[class*="cuIcon-"] {
+	width: 32upx;
+	height: 32upx;
+	top: -4upx;
+	right: -4upx;
+}
+
+/* ==================
+          头像
+ ==================== */
+
+.cu-avatar {
+	font-variant: small-caps;
+	margin: 0;
+	padding: 0;
+	display: inline-flex;
+	text-align: center;
+	justify-content: center;
+	align-items: center;
+	background-color: #ccc;
+	color: #ffffff;
+	white-space: nowrap;
+	position: relative;
+	width: 64upx;
+	height: 64upx;
+	background-size: cover;
+	background-position: center;
+	vertical-align: middle;
+	font-size: 1.5em;
+}
+
+.cu-avatar.sm {
+	width: 48upx;
+	height: 48upx;
+	font-size: 1em;
+}
+
+.cu-avatar.lg {
+	width: 96upx;
+	height: 96upx;
+	font-size: 2em;
+}
+
+.cu-avatar.xl {
+	width: 128upx;
+	height: 128upx;
+	font-size: 2.5em;
+}
+
+.cu-avatar .avatar-text {
+	font-size: 0.4em;
+}
+
+.cu-avatar-group {
+	direction: rtl;
+	unicode-bidi: bidi-override;
+	padding: 0 10upx 0 40upx;
+	display: inline-block;
+}
+
+.cu-avatar-group .cu-avatar {
+	margin-left: -30upx;
+	border: 4upx solid #f1f1f1;
+	vertical-align: middle;
+}
+
+.cu-avatar-group .cu-avatar.sm {
+	margin-left: -20upx;
+	border: 1upx solid #f1f1f1;
+}
+
+/* ==================
+         进度条
+ ==================== */
+
+.cu-progress {
+	overflow: hidden;
+	height: 28upx;
+	background-color: #ebeef5;
+	display: inline-flex;
+	align-items: center;
+	width: 100%;
+}
+
+.cu-progress+view,
+.cu-progress+text {
+	line-height: 1;
+}
+
+.cu-progress.xs {
+	height: 10upx;
+}
+
+.cu-progress.sm {
+	height: 20upx;
+}
+
+.cu-progress view {
+	width: 0;
+	height: 100%;
+	align-items: center;
+	display: flex;
+	justify-items: flex-end;
+	justify-content: space-around;
+	font-size: 20upx;
+	color: #ffffff;
+	transition: width 0.6s ease;
+}
+
+.cu-progress text {
+	align-items: center;
+	display: flex;
+	font-size: 20upx;
+	color: #333333;
+	text-indent: 10upx;
+}
+
+.cu-progress.text-progress {
+	padding-right: 60upx;
+}
+
+.cu-progress.striped view {
+	background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+	background-size: 72upx 72upx;
+}
+
+.cu-progress.active view {
+	animation: progress-stripes 2s linear infinite;
+}
+
+@keyframes progress-stripes {
+	from {
+		background-position: 72upx 0;
+	}
+
+	to {
+		background-position: 0 0;
+	}
+}
+
+/* ==================
+          加载
+ ==================== */
+
+.cu-load {
+	display: block;
+	line-height: 3em;
+	text-align: center;
+}
+
+.cu-load::before {
+	font-family: "cuIcon";
+	display: inline-block;
+	margin-right: 6upx;
+}
+
+.cu-load.loading::before {
+	content: "\e67a";
+	animation: cuIcon-spin 2s infinite linear;
+}
+
+.cu-load.loading::after {
+	content: "加载中...";
+}
+
+.cu-load.over::before {
+	content: "\e64a";
+}
+
+.cu-load.over::after {
+	content: "没有更多了";
+}
+
+.cu-load.erro::before {
+	content: "\e658";
+}
+
+.cu-load.erro::after {
+	content: "加载失败";
+}
+
+.cu-load.load-cuIcon::before {
+	font-size: 32upx;
+}
+
+.cu-load.load-cuIcon::after {
+	display: none;
+}
+
+.cu-load.load-cuIcon.over {
+	display: none;
+}
+
+.cu-load.load-modal {
+	position: fixed;
+	top: 0;
+	right: 0;
+	bottom: 140upx;
+	left: 0;
+	margin: auto;
+	width: 260upx;
+	height: 260upx;
+	background-color: #ffffff;
+	border-radius: 10upx;
+	box-shadow: 0 0 0upx 2000upx rgba(0, 0, 0, 0.5);
+	display: flex;
+	align-items: center;
+	flex-direction: column;
+	justify-content: center;
+	font-size: 28upx;
+	z-index: 9999;
+	line-height: 2.4em;
+}
+
+.cu-load.load-modal [class*="cuIcon-"] {
+	font-size: 60upx;
+}
+
+.cu-load.load-modal image {
+	width: 70upx;
+	height: 70upx;
+}
+
+.cu-load.load-modal::after {
+	content: "";
+	position: absolute;
+	background-color: #ffffff;
+	border-radius: 50%;
+	width: 200upx;
+	height: 200upx;
+	font-size: 10px;
+	border-top: 6upx solid rgba(0, 0, 0, 0.05);
+	border-right: 6upx solid rgba(0, 0, 0, 0.05);
+	border-bottom: 6upx solid rgba(0, 0, 0, 0.05);
+	border-left: 6upx solid #f37b1d;
+	animation: cuIcon-spin 1s infinite linear;
+	z-index: -1;
+}
+
+.load-progress {
+	pointer-events: none;
+	top: 0;
+	position: fixed;
+	width: 100%;
+	left: 0;
+	z-index: 2000;
+}
+
+.load-progress.hide {
+	display: none;
+}
+
+.load-progress .load-progress-bar {
+	position: relative;
+	width: 100%;
+	height: 4upx;
+	overflow: hidden;
+	transition: all 200ms ease 0s;
+}
+
+.load-progress .load-progress-spinner {
+	position: absolute;
+	top: 10upx;
+	right: 10upx;
+	z-index: 2000;
+	display: block;
+}
+
+.load-progress .load-progress-spinner::after {
+	content: "";
+	display: block;
+	width: 24upx;
+	height: 24upx;
+	-webkit-box-sizing: border-box;
+	box-sizing: border-box;
+	border: solid 4upx transparent;
+	border-top-color: inherit;
+	border-left-color: inherit;
+	border-radius: 50%;
+	-webkit-animation: load-progress-spinner 0.4s linear infinite;
+	animation: load-progress-spinner 0.4s linear infinite;
+}
+
+@-webkit-keyframes load-progress-spinner {
+	0% {
+		-webkit-transform: rotate(0);
+		transform: rotate(0);
+	}
+
+	100% {
+		-webkit-transform: rotate(360deg);
+		transform: rotate(360deg);
+	}
+}
+
+@keyframes load-progress-spinner {
+	0% {
+		-webkit-transform: rotate(0);
+		transform: rotate(0);
+	}
+
+	100% {
+		-webkit-transform: rotate(360deg);
+		transform: rotate(360deg);
+	}
+}
+
+/* ==================
+          列表
+ ==================== */
+.grayscale {
+	filter: grayscale(1);
+}
+
+.cu-list+.cu-list {
+	margin-top: 30upx
+}
+
+.cu-list>.cu-item {
+	transition: all .6s ease-in-out 0s;
+	transform: translateX(0upx)
+}
+
+.cu-list>.cu-item.move-cur {
+	transform: translateX(-260upx)
+}
+
+.cu-list>.cu-item .move {
+	position: absolute;
+	right: 0;
+	display: flex;
+	width: 260upx;
+	height: 100%;
+	transform: translateX(100%)
+}
+
+.cu-list>.cu-item .move view {
+	display: flex;
+	flex: 1;
+	justify-content: center;
+	align-items: center
+}
+
+.cu-list.menu-avatar {
+	overflow: hidden;
+}
+
+.cu-list.menu-avatar>.cu-item {
+	position: relative;
+	display: flex;
+	padding-right: 10upx;
+	height: 140upx;
+	background-color: #ffffff;
+	justify-content: flex-end;
+	align-items: center
+}
+
+.cu-list.menu-avatar>.cu-item>.cu-avatar {
+	position: absolute;
+	left: 30upx
+}
+
+.cu-list.menu-avatar>.cu-item .flex .text-cut {
+	max-width: 510upx
+}
+
+.cu-list.menu-avatar>.cu-item .content {
+	position: absolute;
+	left: 146upx;
+	width: calc(100% - 96upx - 60upx - 120upx - 20upx);
+	line-height: 1.6em;
+}
+
+.cu-list.menu-avatar>.cu-item .content.flex-sub {
+	width: calc(100% - 96upx - 60upx - 20upx);
+}
+
+.cu-list.menu-avatar>.cu-item .content>view:first-child {
+	font-size: 30upx;
+	display: flex;
+	align-items: center
+}
+
+.cu-list.menu-avatar>.cu-item .content .cu-tag.sm {
+	display: inline-block;
+	margin-left: 10upx;
+	height: 28upx;
+	font-size: 16upx;
+	line-height: 32upx
+}
+
+.cu-list.menu-avatar>.cu-item .action {
+	width: 100upx;
+	text-align: center
+}
+
+.cu-list.menu-avatar>.cu-item .action view+view {
+	margin-top: 10upx
+}
+
+.cu-list.menu-avatar.comment>.cu-item .content {
+	position: relative;
+	left: 0;
+	width: auto;
+	flex: 1;
+}
+
+.cu-list.menu-avatar.comment>.cu-item {
+	padding: 30upx 30upx 30upx 120upx;
+	height: auto
+}
+
+.cu-list.menu-avatar.comment .cu-avatar {
+	align-self: flex-start
+}
+
+.cu-list.menu>.cu-item {
+	position: relative;
+	display: flex;
+	padding: 0 30upx;
+	min-height: 100upx;
+	background-color: #ffffff;
+	justify-content: space-between;
+	align-items: center
+}
+
+.cu-list.menu>.cu-item:last-child:after {
+	border: none
+}
+
+.cu-list.menu-avatar>.cu-item:after,
+.cu-list.menu>.cu-item:after {
+	position: absolute;
+	top: 0;
+	left: 0;
+	box-sizing: border-box;
+	width: 200%;
+	height: 200%;
+	border-bottom: 1upx solid #ddd;
+	border-radius: inherit;
+	content: " ";
+	transform: scale(.5);
+	transform-origin: 0 0;
+	pointer-events: none
+}
+
+.cu-list.menu>.cu-item.grayscale {
+	background-color: #f5f5f5
+}
+
+.cu-list.menu>.cu-item.cur {
+	background-color: #fcf7e9
+}
+
+.cu-list.menu>.cu-item.arrow {
+	padding-right: 90upx
+}
+
+.cu-list.menu>.cu-item.arrow:before {
+	position: absolute;
+	top: 0;
+	right: 30upx;
+	bottom: 0;
+	display: block;
+	margin: auto;
+	width: 30upx;
+	height: 30upx;
+	color: #8799a3;
+	content: "\e6a3";
+	text-align: center;
+	font-size: 34upx;
+	font-family: cuIcon;
+	line-height: 30upx
+}
+
+.cu-list.menu>.cu-item button.content {
+	padding: 0;
+	background-color: transparent;
+	justify-content: flex-start
+}
+
+.cu-list.menu>.cu-item button.content:after {
+	display: none
+}
+
+.cu-list.menu>.cu-item .cu-avatar-group .cu-avatar {
+	border-color: #ffffff
+}
+
+.cu-list.menu>.cu-item .content>view:first-child {
+	display: flex;
+	align-items: center
+}
+
+.cu-list.menu>.cu-item .content>text[class*=cuIcon] {
+	display: inline-block;
+	margin-right: 10upx;
+	width: 1.6em;
+	text-align: center
+}
+
+.cu-list.menu>.cu-item .content>image {
+	display: inline-block;
+	margin-right: 10upx;
+	width: 1.6em;
+	height: 1.6em;
+	vertical-align: middle
+}
+
+.cu-list.menu>.cu-item .content {
+	font-size: 30upx;
+	line-height: 1.6em;
+	flex: 1
+}
+
+.cu-list.menu>.cu-item .content .cu-tag.sm {
+	display: inline-block;
+	margin-left: 10upx;
+	height: 28upx;
+	font-size: 16upx;
+	line-height: 32upx
+}
+
+.cu-list.menu>.cu-item .action .cu-tag:empty {
+	right: 10upx
+}
+
+.cu-list.menu {
+	display: block;
+	overflow: hidden
+}
+
+.cu-list.menu.sm-border>.cu-item:after {
+	left: 30upx;
+	width: calc(200% - 120upx)
+}
+
+.cu-list.grid>.cu-item {
+	position: relative;
+	display: flex;
+	padding: 20upx 0 30upx;
+	transition-duration: 0s;
+	flex-direction: column
+}
+
+.cu-list.grid>.cu-item:after {
+	position: absolute;
+	top: 0;
+	left: 0;
+	box-sizing: border-box;
+	width: 200%;
+	height: 200%;
+	border-right: 1px solid rgba(0, 0, 0, .1);
+	border-bottom: 1px solid rgba(0, 0, 0, .1);
+	border-radius: inherit;
+	content: " ";
+	transform: scale(.5);
+	transform-origin: 0 0;
+	pointer-events: none
+}
+
+.cu-list.grid>.cu-item text {
+	display: block;
+	margin-top: 10upx;
+	color: #888;
+	font-size: 26upx;
+	line-height: 40upx
+}
+
+.cu-list.grid>.cu-item [class*=cuIcon] {
+	position: relative;
+	display: block;
+	margin-top: 20upx;
+	width: 100%;
+	font-size: 48upx
+}
+
+.cu-list.grid>.cu-item .cu-tag {
+	right: auto;
+	left: 50%;
+	margin-left: 20upx
+}
+
+.cu-list.grid {
+	background-color: #ffffff;
+	text-align: center
+}
+
+.cu-list.grid.no-border>.cu-item {
+	padding-top: 10upx;
+	padding-bottom: 20upx
+}
+
+.cu-list.grid.no-border>.cu-item:after {
+	border: none
+}
+
+.cu-list.grid.no-border {
+	padding: 20upx 10upx
+}
+
+.cu-list.grid.col-3>.cu-item:nth-child(3n):after,
+.cu-list.grid.col-4>.cu-item:nth-child(4n):after,
+.cu-list.grid.col-5>.cu-item:nth-child(5n):after {
+	border-right-width: 0
+}
+
+.cu-list.card-menu {
+	overflow: hidden;
+	margin-right: 30upx;
+	margin-left: 30upx;
+	border-radius: 20upx
+}
+
+
+/* ==================
+          操作条
+ ==================== */
+
+.cu-bar {
+	display: flex;
+	position: relative;
+	align-items: center;
+	min-height: 100upx;
+	justify-content: space-between;
+}
+
+.cu-bar .action {
+	display: flex;
+	align-items: center;
+	height: 100%;
+	justify-content: center;
+	max-width: 100%;
+}
+
+.cu-bar .action.border-title {
+	position: relative;
+	top: -10upx;
+}
+
+.cu-bar .action.border-title text[class*="bg-"]:last-child {
+	position: absolute;
+	bottom: -0.5rem;
+	min-width: 2rem;
+	height: 6upx;
+	left: 0;
+}
+
+.cu-bar .action.sub-title {
+	position: relative;
+	top: -0.2rem;
+}
+
+.cu-bar .action.sub-title text {
+	position: relative;
+	z-index: 1;
+}
+
+.cu-bar .action.sub-title text[class*="bg-"]:last-child {
+	position: absolute;
+	display: inline-block;
+	bottom: -0.2rem;
+	border-radius: 6upx;
+	width: 100%;
+	height: 0.6rem;
+	left: 0.6rem;
+	opacity: 0.3;
+	z-index: 0;
+}
+
+.cu-bar .action.sub-title text[class*="text-"]:last-child {
+	position: absolute;
+	display: inline-block;
+	bottom: -0.7rem;
+	left: 0.5rem;
+	opacity: 0.2;
+	z-index: 0;
+	text-align: right;
+	font-weight: 900;
+	font-size: 36upx;
+}
+
+.cu-bar.justify-center .action.border-title text:last-child,
+.cu-bar.justify-center .action.sub-title text:last-child {
+	left: 0;
+	right: 0;
+	margin: auto;
+	text-align: center;
+}
+
+.cu-bar .action:first-child {
+	margin-left: 30upx;
+	font-size: 30upx;
+}
+
+.cu-bar .action text.text-cut {
+	text-align: left;
+	width: 100%;
+}
+
+.cu-bar .cu-avatar:first-child {
+	margin-left: 20upx;
+}
+
+.cu-bar .action:first-child>text[class*="cuIcon-"] {
+	margin-left: -0.3em;
+	margin-right: 0.3em;
+}
+
+.cu-bar .action:last-child {
+	margin-right: 30upx;
+}
+
+.cu-bar .action>text[class*="cuIcon-"],
+.cu-bar .action>view[class*="cuIcon-"] {
+	font-size: 36upx;
+}
+
+.cu-bar .action>text[class*="cuIcon-"]+text[class*="cuIcon-"] {
+	margin-left: 0.5em;
+}
+
+.cu-bar .content {
+	position: absolute;
+	text-align: center;
+	width: calc(100% - 340upx);
+	left: 0;
+	right: 0;
+	bottom: 0;
+	top: 0;
+	margin: auto;
+	height: 60upx;
+	font-size: 32upx;
+	line-height: 60upx;
+	cursor: none;
+	pointer-events: none;
+	text-overflow: ellipsis;
+	white-space: nowrap;
+	overflow: hidden;
+}
+
+.cu-bar.ios .content {
+	bottom: 7px;
+	height: 30px;
+	font-size: 32upx;
+	line-height: 30px;
+}
+
+.cu-bar.btn-group {
+	justify-content: space-around;
+}
+
+.cu-bar.btn-group button {
+	padding: 20upx 32upx;
+}
+
+.cu-bar.btn-group button {
+	flex: 1;
+	margin: 0 20upx;
+	max-width: 50%;
+}
+
+.cu-bar .search-form {
+	background-color: #f5f5f5;
+	line-height: 64upx;
+	height: 64upx;
+	font-size: 24upx;
+	color: #333333;
+	flex: 1;
+	display: flex;
+	align-items: center;
+	margin: 0 30upx;
+}
+
+.cu-bar .search-form+.action {
+	margin-right: 30upx;
+}
+
+.cu-bar .search-form input {
+	flex: 1;
+	padding-right: 30upx;
+	height: 64upx;
+	line-height: 64upx;
+	font-size: 26upx;
+	background-color: transparent;
+}
+
+.cu-bar .search-form [class*="cuIcon-"] {
+	margin: 0 0.5em 0 0.8em;
+}
+
+.cu-bar .search-form [class*="cuIcon-"]::before {
+	top: 0upx;
+}
+
+.cu-bar.fixed,
+.nav.fixed {
+	position: fixed;
+	width: 100%;
+	top: 0;
+	z-index: 1024;
+	box-shadow: 0 1upx 6upx rgba(0, 0, 0, 0.1);
+}
+
+.cu-bar.foot {
+	position: fixed;
+	width: 100%;
+	bottom: 0;
+	z-index: 1024;
+	box-shadow: 0 -1upx 6upx rgba(0, 0, 0, 0.1);
+}
+
+.cu-bar.tabbar {
+	padding: 0;
+	height: calc(100upx + env(safe-area-inset-bottom) / 2);
+	padding-bottom: calc(env(safe-area-inset-bottom) / 2);
+}
+
+.cu-tabbar-height {
+	min-height: 100upx;
+	height: calc(100upx + env(safe-area-inset-bottom) / 2);
+}
+
+.cu-bar.tabbar.shadow {
+	box-shadow: 0 -1upx 6upx rgba(0, 0, 0, 0.1);
+}
+
+.cu-bar.tabbar .action {
+	font-size: 22upx;
+	position: relative;
+	flex: 1;
+	text-align: center;
+	padding: 0;
+	display: block;
+	height: auto;
+	line-height: 1;
+	margin: 0;
+	background-color: inherit;
+	overflow: initial;
+}
+
+.cu-bar.tabbar.shop .action {
+	width: 140upx;
+	flex: initial;
+}
+
+.cu-bar.tabbar .action.add-action {
+	position: relative;
+	z-index: 2;
+	padding-top: 50upx;
+}
+
+.cu-bar.tabbar .action.add-action [class*="cuIcon-"] {
+	position: absolute;
+	width: 70upx;
+	z-index: 2;
+	height: 70upx;
+	border-radius: 50%;
+	line-height: 70upx;
+	font-size: 50upx;
+	top: -35upx;
+	left: 0;
+	right: 0;
+	margin: auto;
+	padding: 0;
+}
+
+.cu-bar.tabbar .action.add-action::after {
+	content: "";
+	position: absolute;
+	width: 100upx;
+	height: 100upx;
+	top: -50upx;
+	left: 0;
+	right: 0;
+	margin: auto;
+	box-shadow: 0 -3upx 8upx rgba(0, 0, 0, 0.08);
+	border-radius: 50upx;
+	background-color: inherit;
+	z-index: 0;
+}
+
+.cu-bar.tabbar .action.add-action::before {
+	content: "";
+	position: absolute;
+	width: 100upx;
+	height: 30upx;
+	bottom: 30upx;
+	left: 0;
+	right: 0;
+	margin: auto;
+	background-color: inherit;
+	z-index: 1;
+}
+
+.cu-bar.tabbar .btn-group {
+	flex: 1;
+	display: flex;
+	justify-content: space-around;
+	align-items: center;
+	padding: 0 10upx;
+}
+
+.cu-bar.tabbar button.action::after {
+	border: 0;
+}
+
+.cu-bar.tabbar .action [class*="cuIcon-"] {
+	width: 100upx;
+	position: relative;
+	display: block;
+	height: auto;
+	margin: 0 auto 10upx;
+	text-align: center;
+	font-size: 40upx;
+}
+
+.cu-bar.tabbar .action .cuIcon-cu-image {
+	margin: 0 auto;
+}
+
+.cu-bar.tabbar .action .cuIcon-cu-image image {
+	width: 50upx;
+	height: 50upx;
+	display: inline-block;
+}
+
+.cu-bar.tabbar .submit {
+	align-items: center;
+	display: flex;
+	justify-content: center;
+	text-align: center;
+	position: relative;
+	flex: 2;
+	align-self: stretch;
+}
+
+.cu-bar.tabbar .submit:last-child {
+	flex: 2.6;
+}
+
+.cu-bar.tabbar .submit+.submit {
+	flex: 2;
+}
+
+.cu-bar.tabbar.border .action::before {
+	content: " ";
+	width: 200%;
+	height: 200%;
+	position: absolute;
+	top: 0;
+	left: 0;
+	transform: scale(0.5);
+	transform-origin: 0 0;
+	border-right: 1upx solid rgba(0, 0, 0, 0.1);
+	z-index: 3;
+}
+
+.cu-bar.tabbar.border .action:last-child:before {
+	display: none;
+}
+
+.cu-bar.input {
+	padding-right: 20upx;
+	background-color: #ffffff;
+}
+
+.cu-bar.input input {
+	overflow: initial;
+	line-height: 64upx;
+	height: 64upx;
+	min-height: 64upx;
+	flex: 1;
+	font-size: 30upx;
+	margin: 0 20upx;
+}
+
+.cu-bar.input .action {
+	margin-left: 20upx;
+}
+
+.cu-bar.input .action [class*="cuIcon-"] {
+	font-size: 48upx;
+}
+
+.cu-bar.input input+.action {
+	margin-right: 20upx;
+	margin-left: 0upx;
+}
+
+.cu-bar.input .action:first-child [class*="cuIcon-"] {
+	margin-left: 0upx;
+}
+
+.cu-custom {
+	display: block;
+	position: relative;
+}
+
+.cu-custom .cu-bar .content {
+	width: calc(100% - 440upx);
+}
+
+/* #ifdef MP-ALIPAY */
+.cu-custom .cu-bar .action .cuIcon-back {
+	opacity: 0;
+}
+
+/* #endif */
+
+.cu-custom .cu-bar .content image {
+	height: 60upx;
+	width: 240upx;
+}
+
+.cu-custom .cu-bar {
+	min-height: 0px;
+	/* #ifdef MP-WEIXIN */
+	padding-right: 220upx;
+	/* #endif */
+	/* #ifdef MP-ALIPAY */
+	padding-right: 150upx;
+	/* #endif */
+	box-shadow: 0upx 0upx 0upx;
+	z-index: 9999;
+}
+
+.cu-custom .cu-bar .border-custom {
+	position: relative;
+	background: rgba(0, 0, 0, 0.15);
+	border-radius: 1000upx;
+	height: 30px;
+}
+
+.cu-custom .cu-bar .border-custom::after {
+	content: " ";
+	width: 200%;
+	height: 200%;
+	position: absolute;
+	top: 0;
+	left: 0;
+	border-radius: inherit;
+	transform: scale(0.5);
+	transform-origin: 0 0;
+	pointer-events: none;
+	box-sizing: border-box;
+	border: 1upx solid #ffffff;
+	opacity: 0.5;
+}
+
+.cu-custom .cu-bar .border-custom::before {
+	content: " ";
+	width: 1upx;
+	height: 110%;
+	position: absolute;
+	top: 22.5%;
+	left: 0;
+	right: 0;
+	margin: auto;
+	transform: scale(0.5);
+	transform-origin: 0 0;
+	pointer-events: none;
+	box-sizing: border-box;
+	opacity: 0.6;
+	background-color: #ffffff;
+}
+
+.cu-custom .cu-bar .border-custom text {
+	display: block;
+	flex: 1;
+	margin: auto !important;
+	text-align: center;
+	font-size: 34upx;
+}
+
+/* ==================
+         导航栏
+ ==================== */
+
+.nav {
+	white-space: nowrap;
+}
+
+::-webkit-scrollbar {
+	display: none;
+}
+
+.nav .cu-item {
+	height: 90upx;
+	display: inline-block;
+	line-height: 90upx;
+	margin: 0 10upx;
+	padding: 0 20upx;
+}
+
+.nav .cu-item.cur {
+	/*border-bottom: 4upx solid;*/
+}
+
+/* ==================
+         时间轴
+ ==================== */
+
+.cu-timeline {
+	display: block;
+	background-color: #ffffff;
+}
+
+.cu-timeline .cu-time {
+	width: 120upx;
+	text-align: center;
+	padding: 20upx 0;
+	font-size: 26upx;
+	color: #888;
+	display: block;
+}
+
+.cu-timeline>.cu-item {
+	padding: 30upx 30upx 30upx 120upx;
+	position: relative;
+	display: block;
+	z-index: 0;
+}
+
+.cu-timeline>.cu-item:not([class*="text-"]) {
+	color: #ccc;
+}
+
+.cu-timeline>.cu-item::after {
+	content: "";
+	display: block;
+	position: absolute;
+	width: 1upx;
+	background-color: #ddd;
+	left: 60upx;
+	height: 100%;
+	top: 0;
+	z-index: 8;
+}
+
+.cu-timeline>.cu-item::before {
+	font-family: "cuIcon";
+	display: block;
+	position: absolute;
+	top: 36upx;
+	z-index: 9;
+	background-color: #ffffff;
+	width: 50upx;
+	height: 50upx;
+	text-align: center;
+	border: none;
+	line-height: 50upx;
+	left: 36upx;
+}
+
+.cu-timeline>.cu-item:not([class*="cuIcon-"])::before {
+	content: "\e763";
+}
+
+.cu-timeline>.cu-item[class*="cuIcon-"]::before {
+	background-color: #ffffff;
+	width: 50upx;
+	height: 50upx;
+	text-align: center;
+	border: none;
+	line-height: 50upx;
+	left: 36upx;
+}
+
+.cu-timeline>.cu-item>.content {
+	padding: 30upx;
+	border-radius: 6upx;
+	display: block;
+	line-height: 1.6;
+}
+
+.cu-timeline>.cu-item>.content:not([class*="bg-"]) {
+	background-color: #f1f1f1;
+	color: #333333;
+}
+
+.cu-timeline>.cu-item>.content+.content {
+	margin-top: 20upx;
+}
+
+/* ==================
+         聊天
+ ==================== */
+
+.cu-chat {
+	display: flex;
+	flex-direction: column;
+}
+
+.cu-chat .cu-item {
+	display: flex;
+	padding: 30upx 30upx 70upx;
+	position: relative;
+}
+
+.cu-chat .cu-item>.cu-avatar {
+	width: 80upx;
+	height: 80upx;
+}
+
+.cu-chat .cu-item>.main {
+	max-width: calc(100% - 260upx);
+	margin: 0 40upx;
+	display: flex;
+	align-items: center;
+}
+
+.cu-chat .cu-item>image {
+	height: 320upx;
+}
+
+.cu-chat .cu-item>.main .content {
+	padding: 20upx;
+	border-radius: 6upx;
+	display: inline-flex;
+	max-width: 100%;
+	align-items: center;
+	font-size: 30upx;
+	position: relative;
+	min-height: 80upx;
+	line-height: 40upx;
+	text-align: left;
+}
+
+.cu-chat .cu-item>.main .content:not([class*="bg-"]) {
+	background-color: #ffffff;
+	color: #333333;
+}
+
+.cu-chat .cu-item .date {
+	position: absolute;
+	font-size: 24upx;
+	color: #8799a3;
+	width: calc(100% - 320upx);
+	bottom: 20upx;
+	left: 160upx;
+}
+
+.cu-chat .cu-item .action {
+	padding: 0 30upx;
+	display: flex;
+	align-items: center;
+}
+
+.cu-chat .cu-item>.main .content::after {
+	content: "";
+	top: 27upx;
+	transform: rotate(45deg);
+	position: absolute;
+	z-index: 100;
+	display: inline-block;
+	overflow: hidden;
+	width: 24upx;
+	height: 24upx;
+	left: -12upx;
+	right: initial;
+	background-color: inherit;
+}
+
+.cu-chat .cu-item.self>.main .content::after {
+	left: auto;
+	right: -12upx;
+}
+
+.cu-chat .cu-item>.main .content::before {
+	content: "";
+	top: 30upx;
+	transform: rotate(45deg);
+	position: absolute;
+	z-index: -1;
+	display: inline-block;
+	overflow: hidden;
+	width: 24upx;
+	height: 24upx;
+	left: -12upx;
+	right: initial;
+	background-color: inherit;
+	filter: blur(5upx);
+	opacity: 0.3;
+}
+
+.cu-chat .cu-item>.main .content:not([class*="bg-"])::before {
+	background-color: #333333;
+	opacity: 0.1;
+}
+
+.cu-chat .cu-item.self>.main .content::before {
+	left: auto;
+	right: -12upx;
+}
+
+.cu-chat .cu-item.self {
+	justify-content: flex-end;
+	text-align: right;
+}
+
+.cu-chat .cu-info {
+	display: inline-block;
+	margin: 20upx auto;
+	font-size: 24upx;
+	padding: 8upx 12upx;
+	background-color: rgba(0, 0, 0, 0.2);
+	border-radius: 6upx;
+	color: #ffffff;
+	max-width: 400upx;
+	line-height: 1.4;
+}
+
+/* ==================
+         卡片
+ ==================== */
+
+.cu-card {
+	display: block;
+	overflow: hidden;
+}
+
+.cu-card>.cu-item {
+	display: block;
+	background-color: #ffffff;
+	overflow: hidden;
+	border-radius: 10upx;
+	margin: 30upx;
+}
+
+.cu-card>.cu-item.shadow-blur {
+	overflow: initial;
+}
+
+.cu-card.no-card>.cu-item {
+	margin: 0upx;
+	border-radius: 0upx;
+}
+
+.cu-card .grid.grid-square {
+	margin-bottom: -20upx;
+}
+
+.cu-card.case .image {
+	position: relative;
+}
+
+.cu-card.case .image image {
+	width: 100%;
+}
+
+.cu-card.case .image .cu-tag {
+	position: absolute;
+	right: 0;
+	top: 0;
+}
+
+.cu-card.case .image .cu-bar {
+	position: absolute;
+	bottom: 0;
+	width: 100%;
+	background-color: transparent;
+	padding: 0upx 30upx;
+}
+
+.cu-card.case.no-card .image {
+	margin: 30upx 30upx 0;
+	overflow: hidden;
+	border-radius: 10upx;
+}
+
+.cu-card.dynamic {
+	display: block;
+}
+
+.cu-card.dynamic>.cu-item {
+	display: block;
+	background-color: #ffffff;
+	overflow: hidden;
+}
+
+.cu-card.dynamic>.cu-item>.text-content {
+	padding: 0 30upx 0;
+	max-height: 6.4em;
+	overflow: hidden;
+	font-size: 30upx;
+	margin-bottom: 20upx;
+}
+
+.cu-card.dynamic>.cu-item .square-img {
+	width: 100%;
+	height: 200upx;
+	border-radius: 6upx;
+}
+
+.cu-card.dynamic>.cu-item .only-img {
+	width: 100%;
+	height: 320upx;
+	border-radius: 6upx;
+}
+
+/* card.dynamic>.cu-item .comment {
+  padding: 20upx;
+  background-color: #f1f1f1;
+  margin: 0 30upx 30upx;
+  border-radius: 6upx;
+} */
+
+.cu-card.article {
+	display: block;
+}
+
+.cu-card.article>.cu-item {
+	padding-bottom: 30upx;
+}
+
+.cu-card.article>.cu-item .title {
+	font-size: 30upx;
+	font-weight: 900;
+	color: #333333;
+	line-height: 100upx;
+	padding: 0 30upx;
+}
+
+.cu-card.article>.cu-item .content {
+	display: flex;
+	padding: 0 30upx;
+}
+
+.cu-card.article>.cu-item .content>image {
+	width: 240upx;
+	height: 6.4em;
+	margin-right: 20upx;
+	border-radius: 6upx;
+}
+
+.cu-card.article>.cu-item .content .desc {
+	flex: 1;
+	display: flex;
+	flex-direction: column;
+	justify-content: space-between;
+}
+
+.cu-card.article>.cu-item .content .text-content {
+	font-size: 28upx;
+	color: #888;
+	height: 4.8em;
+	overflow: hidden;
+}
+
+/* ==================
+         表单
+ ==================== */
+
+.cu-form-group {
+	background-color: #ffffff;
+	padding: 1upx 30upx;
+	display: flex;
+	align-items: center;
+	min-height: 80upx;
+	justify-content: space-between;
+}
+
+.cu-form-group+.cu-form-group {
+	/* border-top: 1upx solid #eee; */
+}
+
+.cu-form-group .title {
+	text-align: justify;
+	padding-right: 30upx;
+	font-size: 30upx;
+	position: relative;
+	height: 60upx;
+	line-height: 60upx;
+}
+
+.cu-form-group input {
+	flex: 1;
+	font-size: 30upx;
+	color: #555;
+	padding-right: 20upx;
+}
+
+.cu-form-group>text[class*="cuIcon-"] {
+	font-size: 36upx;
+	padding: 0;
+	box-sizing: border-box;
+}
+
+.cu-form-group textarea {
+	margin: 32upx 0 30upx;
+	height: 4.6em;
+	width: 100%;
+	line-height: 1.2em;
+	flex: 1;
+	font-size: 28upx;
+	padding: 0;
+}
+
+.cu-form-group.align-start .title {
+	height: 1em;
+	margin-top: 32upx;
+	line-height: 1em;
+}
+
+.cu-form-group picker {
+	flex: 1;
+	padding-right: 40upx;
+	overflow: hidden;
+	position: relative;
+}
+
+.cu-form-group picker .picker {
+	line-height: 100upx;
+	font-size: 28upx;
+	text-overflow: ellipsis;
+	white-space: nowrap;
+	overflow: hidden;
+	width: 100%;
+	text-align: right;
+}
+
+.cu-form-group picker::after {
+	font-family: cuIcon;
+	display: block;
+	content: "\e6a3";
+	position: absolute;
+	font-size: 34upx;
+	color: #8799a3;
+	line-height: 100upx;
+	width: 60upx;
+	text-align: center;
+	top: 0;
+	bottom: 0;
+	right: -20upx;
+	margin: auto;
+}
+
+.cu-form-group textarea[disabled],
+.cu-form-group textarea[disabled] .placeholder {
+	color: transparent;
+}
+
+/* ==================
+         模态窗口
+ ==================== */
+
+.cu-modal {
+	position: fixed;
+	top: 0;
+	right: 0;
+	bottom: 0;
+	left: 0;
+	z-index: 1110;
+	opacity: 0;
+	outline: 0;
+	text-align: center;
+	-ms-transform: scale(1.185);
+	transform: scale(1.185);
+	backface-visibility: hidden;
+	perspective: 2000upx;
+	background: rgba(0, 0, 0, 0.6);
+	transition: all 0.3s ease-in-out 0s;
+	pointer-events: none;
+}
+
+.cu-modal::before {
+	content: "\200B";
+	display: inline-block;
+	height: 100%;
+	vertical-align: middle;
+}
+
+.cu-modal.show {
+	opacity: 1;
+	transition-duration: 0.3s;
+	-ms-transform: scale(1);
+	transform: scale(1);
+	overflow-x: hidden;
+	overflow-y: auto;
+	pointer-events: auto;
+}
+
+.cu-dialog {
+	position: relative;
+	display: inline-block;
+	vertical-align: middle;
+	margin-left: auto;
+	margin-right: auto;
+	width: 680upx;
+	max-width: 100%;
+	background-color: #f8f8f8;
+	border-radius: 10upx;
+	overflow: hidden;
+}
+
+.cu-modal.bottom-modal::before {
+	vertical-align: bottom;
+}
+
+.cu-modal.bottom-modal .cu-dialog {
+	width: 100%;
+	border-radius: 0;
+}
+
+.cu-modal.bottom-modal {
+	margin-bottom: -1000upx;
+}
+
+.cu-modal.bottom-modal.show {
+	margin-bottom: 0;
+}
+
+.cu-modal.drawer-modal {
+	transform: scale(1);
+	display: flex;
+}
+
+.cu-modal.drawer-modal .cu-dialog {
+	height: 100%;
+	min-width: 200upx;
+	border-radius: 0;
+	margin: initial;
+	transition-duration: 0.3s;
+}
+
+.cu-modal.drawer-modal.justify-start .cu-dialog {
+	transform: translateX(-100%);
+}
+
+.cu-modal.drawer-modal.justify-end .cu-dialog {
+	transform: translateX(100%);
+}
+
+.cu-modal.drawer-modal.show .cu-dialog {
+	transform: translateX(0%);
+}
+.cu-modal .cu-dialog>.cu-bar:first-child .action{
+  min-width: 100rpx;
+  margin-right: 0;
+  min-height: 100rpx;
+}
+/* ==================
+         轮播
+ ==================== */
+swiper .a-swiper-dot {
+	display: inline-block;
+	width: 16upx;
+	height: 16upx;
+	background: rgba(0, 0, 0, .3);
+	border-radius: 50%;
+	vertical-align: middle;
+}
+
+swiper[class*="-dot"] .wx-swiper-dots,
+swiper[class*="-dot"] .a-swiper-dots,
+swiper[class*="-dot"] .uni-swiper-dots {
+	display: flex;
+	align-items: center;
+	width: 100%;
+	justify-content: center;
+}
+
+swiper.square-dot .wx-swiper-dot,
+swiper.square-dot .a-swiper-dot,
+swiper.square-dot .uni-swiper-dot {
+	background-color: #ffffff;
+	opacity: 0.4;
+	width: 10upx;
+	height: 10upx;
+	border-radius: 20upx;
+	margin: 0 8upx !important;
+}
+
+swiper.square-dot .wx-swiper-dot.wx-swiper-dot-active,
+swiper.square-dot .a-swiper-dot.a-swiper-dot-active,
+swiper.square-dot .uni-swiper-dot.uni-swiper-dot-active {
+	opacity: 1;
+	width: 30upx;
+}
+
+swiper.round-dot .wx-swiper-dot,
+swiper.round-dot .a-swiper-dot,
+swiper.round-dot .uni-swiper-dot {
+	width: 10upx;
+	height: 10upx;
+	position: relative;
+	margin: 4upx 8upx !important;
+}
+
+swiper.round-dot .wx-swiper-dot.wx-swiper-dot-active::after,
+swiper.round-dot .a-swiper-dot.a-swiper-dot-active::after,
+swiper.round-dot .uni-swiper-dot.uni-swiper-dot-active::after {
+	content: "";
+	position: absolute;
+	width: 10upx;
+	height: 10upx;
+	top: 0upx;
+	left: 0upx;
+	right: 0;
+	bottom: 0;
+	margin: auto;
+	background-color: #ffffff;
+	border-radius: 20upx;
+}
+
+swiper.round-dot .wx-swiper-dot.wx-swiper-dot-active,
+swiper.round-dot .a-swiper-dot.a-swiper-dot-active,
+swiper.round-dot .uni-swiper-dot.uni-swiper-dot-active {
+	width: 18upx;
+	height: 18upx;
+}
+
+.screen-swiper {
+	min-height: 200upx;
+}
+
+.screen-swiper image,
+.screen-swiper video,
+.swiper-item image,
+.swiper-item video {
+	width: 100%;
+	display: block;
+	height: 100%;
+	margin: 0;
+	pointer-events: none;
+}
+
+.card-swiper {
+	height: 420upx !important;
+}
+
+.card-swiper swiper-item {
+	width: 610upx !important;
+	left: 70upx;
+	box-sizing: border-box;
+	padding: 40upx 0upx 70upx;
+	overflow: initial;
+}
+
+.card-swiper swiper-item .swiper-item {
+	width: 100%;
+	display: block;
+	height: 100%;
+	border-radius: 10upx;
+	transform: scale(0.9);
+	transition: all 0.2s ease-in 0s;
+	overflow: hidden;
+}
+
+.card-swiper swiper-item.cur .swiper-item {
+	transform: none;
+	transition: all 0.2s ease-in 0s;
+}
+
+
+.tower-swiper {
+	height: 420upx;
+	position: relative;
+	max-width: 750upx;
+	overflow: hidden;
+}
+
+.tower-swiper .tower-item {
+	position: absolute;
+	width: 300upx;
+	height: 380upx;
+	top: 0;
+	bottom: 0;
+	left: 50%;
+	margin: auto;
+	transition: all 0.2s ease-in 0s;
+	opacity: 1;
+}
+
+.tower-swiper .tower-item.none {
+	opacity: 0;
+}
+
+.tower-swiper .tower-item .swiper-item {
+	width: 100%;
+	height: 100%;
+	border-radius: 6upx;
+	overflow: hidden;
+}
+
+/* ==================
+          步骤条
+ ==================== */
+
+.cu-steps {
+	display: flex;
+}
+
+scroll-view.cu-steps {
+	display: block;
+	white-space: nowrap;
+}
+
+scroll-view.cu-steps .cu-item {
+	display: inline-block;
+}
+
+.cu-steps .cu-item {
+	flex: 1;
+	text-align: center;
+	position: relative;
+	min-width: 100upx;
+}
+
+.cu-steps .cu-item:not([class*="text-"]) {
+	color: #8799a3;
+}
+
+.cu-steps .cu-item [class*="cuIcon-"],
+.cu-steps .cu-item .num {
+	display: block;
+	font-size: 40upx;
+	line-height: 80upx;
+}
+
+.cu-steps .cu-item::before,
+.cu-steps .cu-item::after,
+.cu-steps.steps-arrow .cu-item::before,
+.cu-steps.steps-arrow .cu-item::after {
+	content: "";
+	display: block;
+	position: absolute;
+	height: 0px;
+	width: calc(100% - 80upx);
+	border-bottom: 1px solid #ccc;
+	left: calc(0px - (100% - 80upx) / 2);
+	top: 40upx;
+	z-index: 0;
+}
+
+.cu-steps.steps-arrow .cu-item::before,
+.cu-steps.steps-arrow .cu-item::after {
+	content: "\e6a3";
+	font-family: 'cuIcon';
+	height: 30upx;
+	border-bottom-width: 0px;
+	line-height: 30upx;
+	top: 0;
+	bottom: 0;
+	margin: auto;
+	color: #ccc;
+}
+
+.cu-steps.steps-bottom .cu-item::before,
+.cu-steps.steps-bottom .cu-item::after {
+	bottom: 40upx;
+	top: initial;
+}
+
+.cu-steps .cu-item::after {
+	border-bottom: 1px solid currentColor;
+	width: 0px;
+	transition: all 0.3s ease-in-out 0s;
+}
+
+.cu-steps .cu-item[class*="text-"]::after {
+	width: calc(100% - 80upx);
+	color: currentColor;
+}
+
+.cu-steps .cu-item:first-child::before,
+.cu-steps .cu-item:first-child::after {
+	display: none;
+}
+
+.cu-steps .cu-item .num {
+	width: 40upx;
+	height: 40upx;
+	border-radius: 50%;
+	line-height: 40upx;
+	margin: 20upx auto;
+	font-size: 24upx;
+	border: 1px solid currentColor;
+	position: relative;
+	overflow: hidden;
+}
+
+.cu-steps .cu-item[class*="text-"] .num {
+	background-color: currentColor;
+}
+
+.cu-steps .cu-item .num::before,
+.cu-steps .cu-item .num::after {
+	content: attr(data-index);
+	position: absolute;
+	left: 0;
+	right: 0;
+	top: 0;
+	bottom: 0;
+	margin: auto;
+	transition: all 0.3s ease-in-out 0s;
+	transform: translateY(0upx);
+}
+
+.cu-steps .cu-item[class*="text-"] .num::before {
+	transform: translateY(-40upx);
+	color: #ffffff;
+}
+
+.cu-steps .cu-item .num::after {
+	transform: translateY(40upx);
+	color: #ffffff;
+	transition: all 0.3s ease-in-out 0s;
+}
+
+.cu-steps .cu-item[class*="text-"] .num::after {
+	content: "\e645";
+	font-family: 'cuIcon';
+	color: #ffffff;
+	transform: translateY(0upx);
+}
+
+.cu-steps .cu-item[class*="text-"] .num.err::after {
+	content: "\e646";
+}
+
+/* ==================
+          布局
+ ==================== */
+
+/*  -- flex弹性布局 -- */
+
+.flex {
+	display: flex;
+}
+
+.basis-xs {
+	flex-basis: 20%;
+}
+
+.basis-sm {
+	flex-basis: 40%;
+}
+
+.basis-df {
+	flex-basis: 50%;
+}
+
+.basis-lg {
+	flex-basis: 60%;
+}
+
+.basis-xl {
+	flex-basis: 80%;
+}
+
+.flex-sub {
+	flex: 1;
+}
+
+.flex-twice {
+	flex: 2;
+}
+
+.flex-treble {
+	flex: 3;
+}
+
+.flex-direction {
+	flex-direction: column;
+}
+
+.flex-wrap {
+	flex-wrap: wrap;
+}
+
+.align-start {
+	align-items: flex-start;
+}
+
+.align-end {
+	align-items: flex-end;
+}
+
+.align-center {
+	align-items: center;
+}
+
+.align-stretch {
+	align-items: stretch;
+}
+
+.self-start {
+	align-self: flex-start;
+}
+
+.self-center {
+	align-self: flex-center;
+}
+
+.self-end {
+	align-self: flex-end;
+}
+
+.self-stretch {
+	align-self: stretch;
+}
+
+.align-stretch {
+	align-items: stretch;
+}
+
+.justify-start {
+	justify-content: flex-start;
+}
+
+.justify-end {
+	justify-content: flex-end;
+}
+
+.justify-center {
+	justify-content: center;
+}
+
+.justify-between {
+	justify-content: space-between;
+}
+
+.justify-around {
+	justify-content: space-around;
+}
+
+/* grid布局 */
+
+.grid {
+	display: flex;
+	flex-wrap: wrap;
+}
+
+.grid.grid-square {
+	overflow: hidden;
+}
+
+.grid.grid-square .cu-tag {
+	position: absolute;
+	right: 0;
+	top: 0;
+	border-bottom-left-radius: 6upx;
+	padding: 6upx 12upx;
+	height: auto;
+	background-color: rgba(0, 0, 0, 0.5);
+}
+
+.grid.grid-square>view>text[class*="cuIcon-"] {
+	font-size: 52upx;
+	position: absolute;
+	color: #8799a3;
+	margin: auto;
+	top: 0;
+	bottom: 0;
+	left: 0;
+	right: 0;
+	display: flex;
+	justify-content: center;
+	align-items: center;
+	flex-direction: column;
+}
+
+.grid.grid-square>view {
+	margin-right: 20upx;
+	margin-bottom: 20upx;
+	border-radius: 6upx;
+	position: relative;
+	overflow: hidden;
+}
+.grid.grid-square>view.bg-img image {
+	width: 100%;
+	height: 100%;
+	position: absolute;
+}
+.grid.col-1.grid-square>view {
+	padding-bottom: 100%;
+	height: 0;
+	margin-right: 0;
+}
+
+.grid.col-2.grid-square>view {
+	padding-bottom: calc((100% - 20upx)/2);
+	height: 0;
+	width: calc((100% - 20upx)/2);
+}
+
+.grid.col-3.grid-square>view {
+	padding-bottom: calc((100% - 40upx)/3);
+	height: 0;
+	width: calc((100% - 40upx)/3);
+}
+
+.grid.col-4.grid-square>view {
+	padding-bottom: calc((100% - 60upx)/4);
+	height: 0;
+	width: calc((100% - 60upx)/4);
+}
+
+.grid.col-5.grid-square>view {
+	padding-bottom: calc((100% - 80upx)/5);
+	height: 0;
+	width: calc((100% - 80upx)/5);
+}
+
+.grid.col-2.grid-square>view:nth-child(2n),
+.grid.col-3.grid-square>view:nth-child(3n),
+.grid.col-4.grid-square>view:nth-child(4n),
+.grid.col-5.grid-square>view:nth-child(5n) {
+	margin-right: 0;
+}
+
+.grid.col-1>view {
+	width: 100%;
+}
+
+.grid.col-2>view {
+	width: 50%;
+}
+
+.grid.col-3>view {
+	width: 33.33%;
+}
+
+.grid.col-4>view {
+	width: 25%;
+}
+
+.grid.col-5>view {
+	width: 20%;
+}
+
+/*  -- 内外边距 -- */
+
+.margin-top-16{
+	margin-top: 16upx;
+}
+.margin-0 {
+	margin: 0;
+}
+
+.margin-xs {
+	margin: 10upx;
+}
+
+.margin-sm {
+	margin: 20upx;
+}
+
+.margin {
+	margin: 30upx;
+}
+
+.margin-lg {
+	margin: 40upx;
+}
+
+.margin-xl {
+	margin: 50upx;
+}
+
+.margin-top-xs {
+	margin-top: 10upx;
+}
+
+.margin-top-sm {
+	margin-top: 20upx;
+}
+
+.margin-top {
+	margin-top: 30upx;
+}
+
+.margin-top-lg {
+	margin-top: 40upx;
+}
+
+.margin-top-xl {
+	margin-top: 50upx;
+}
+
+.margin-right-xs {
+	margin-right: 10upx;
+}
+
+.margin-right-sm {
+	margin-right: 20upx;
+}
+
+.margin-right {
+	margin-right: 30upx;
+}
+
+.margin-right-lg {
+	margin-right: 40upx;
+}
+
+.margin-right-xl {
+	margin-right: 50upx;
+}
+
+.margin-bottom-xs {
+	margin-bottom: 10upx;
+}
+
+.margin-bottom-sm {
+	margin-bottom: 20upx;
+}
+
+.margin-bottom {
+	margin-bottom: 30upx;
+}
+
+.margin-bottom-lg {
+	margin-bottom: 40upx;
+}
+
+.margin-bottom-xl {
+	margin-bottom: 50upx;
+}
+
+.margin-left-xs {
+	margin-left: 10upx;
+}
+
+.margin-left-sm {
+	margin-left: 20upx;
+}
+
+.margin-left {
+	margin-left: 30upx;
+}
+
+.margin-left-lg {
+	margin-left: 40upx;
+}
+
+.margin-left-xl {
+	margin-left: 50upx;
+}
+
+.margin-lr-xs {
+	margin-left: 10upx;
+	margin-right: 10upx;
+}
+
+.margin-lr-sm {
+	margin-left: 20upx;
+	margin-right: 20upx;
+	/* background-color: #F7F7F7; */
+}
+
+.margin-lr {
+	margin-left: 30upx;
+	margin-right: 30upx;
+}
+
+.margin-lr-lg {
+	margin-left: 40upx;
+	margin-right: 40upx;
+}
+
+.margin-lr-xl {
+	margin-left: 50upx;
+	margin-right: 50upx;
+}
+
+.margin-tb-xs {
+	margin-top: 10upx;
+	margin-bottom: 10upx;
+}
+
+.margin-tb-sm {
+	margin-top: 20upx;
+	margin-bottom: 20upx;
+}
+
+.margin-tb {
+	margin-top: 30upx;
+	margin-bottom: 30upx;
+}
+
+.margin-tb-lg {
+	margin-top: 40upx;
+	margin-bottom: 40upx;
+}
+
+.margin-tb-xl {
+	margin-top: 50upx;
+	margin-bottom: 50upx;
+}
+
+.padding-0 {
+	padding: 0;
+}
+
+.padding-xs {
+	padding: 10upx;
+}
+
+.padding-sm {
+	padding: 20upx;
+}
+
+.padding {
+	padding: 30upx;
+}
+
+.padding-lg {
+	padding: 40upx;
+}
+
+.padding-xl {
+	padding: 50upx;
+}
+
+.padding-top-xs {
+	padding-top: 10upx;
+}
+
+.padding-top-sm {
+	padding-top: 20upx;
+}
+
+.padding-top {
+	padding-top: 30upx;
+}
+
+.padding-top-lg {
+	padding-top: 40upx;
+}
+
+.padding-top-xl {
+	padding-top: 50upx;
+}
+
+.padding-right-xs {
+	padding-right: 10upx;
+}
+
+.padding-right-sm {
+	padding-right: 20upx;
+}
+
+.padding-right {
+	padding-right: 30upx;
+}
+
+.padding-right-lg {
+	padding-right: 40upx;
+}
+
+.padding-right-xl {
+	padding-right: 50upx;
+}
+
+.padding-bottom-xs {
+	padding-bottom: 10upx;
+}
+
+.padding-bottom-sm {
+	padding-bottom: 20upx;
+}
+
+.padding-bottom {
+	padding-bottom: 30upx;
+}
+
+.padding-bottom-lg {
+	padding-bottom: 40upx;
+}
+
+.padding-bottom-xl {
+	padding-bottom: 50upx;
+}
+
+.padding-left-xs {
+	padding-left: 10upx;
+}
+
+.padding-left-sm {
+	padding-left: 20upx;
+}
+
+.padding-left {
+	padding-left: 30upx;
+}
+
+.padding-left-lg {
+	padding-left: 40upx;
+}
+
+.padding-left-xl {
+	padding-left: 50upx;
+}
+
+.padding-lr-xs {
+	padding-left: 10upx;
+	padding-right: 10upx;
+}
+
+.padding-lr-sm {
+	padding-left: 20upx;
+	padding-right: 20upx;
+}
+
+.padding-lr {
+	padding-left: 30upx;
+	padding-right: 30upx;
+}
+
+.padding-lr-lg {
+	padding-left: 40upx;
+	padding-right: 40upx;
+}
+
+.padding-lr-xl {
+	padding-left: 50upx;
+	padding-right: 50upx;
+}
+
+.padding-tb-xs {
+	padding-top: 10upx;
+	padding-bottom: 10upx;
+}
+
+.padding-tb-sm {
+	padding-top: 20upx;
+	padding-bottom: 20upx;
+}
+
+.padding-tb {
+	padding-top: 30upx;
+	padding-bottom: 30upx;
+}
+
+.padding-tb-lg {
+	padding-top: 40upx;
+	padding-bottom: 40upx;
+}
+
+.padding-tb-xl {
+	padding-top: 50upx;
+	padding-bottom: 50upx;
+}
+
+/* -- 浮动 --  */
+
+.cf::after,
+.cf::before {
+	content: " ";
+	display: table;
+}
+
+.cf::after {
+	clear: both;
+}
+
+.fl {
+	float: left;
+}
+
+.fr {
+	float: right;
+}
+
+/* ==================
+          背景
+ ==================== */
+
+.line-red::after,
+.lines-red::after {
+	border-color: #e54d42;
+}
+
+.line-orange::after,
+.lines-orange::after {
+	border-color: #f37b1d;
+}
+
+.line-yellow::after,
+.lines-yellow::after {
+	border-color: #fbbd08;
+}
+
+.line-olive::after,
+.lines-olive::after {
+	border-color: #8dc63f;
+}
+
+.line-green::after,
+.lines-green::after {
+	border-color: #39b54a;
+}
+
+.line-cyan::after,
+.lines-cyan::after {
+	border-color: #1cbbb4;
+}
+
+.line-blue::after,
+.lines-blue::after {
+	border-color: #0081ff;
+}
+
+.line-purple::after,
+.lines-purple::after {
+	border-color: #6739b6;
+}
+
+.line-mauve::after,
+.lines-mauve::after {
+	border-color: #9c26b0;
+}
+
+.line-pink::after,
+.lines-pink::after {
+	border-color: #e03997;
+}
+
+.line-brown::after,
+.lines-brown::after {
+	border-color: #a5673f;
+}
+
+.line-grey::after,
+.lines-grey::after {
+	border-color: #8799a3;
+}
+
+.line-gray::after,
+.lines-gray::after {
+	border-color: #aaaaaa;
+}
+
+.line-black::after,
+.lines-black::after {
+	border-color: #333333;
+}
+
+.line-white::after,
+.lines-white::after {
+	border-color: #ffffff;
+}
+
+.bg-red {
+	background-color: #e54d42;
+	color: #ffffff;
+}
+
+.bg-orange {
+	background-color: #f37b1d;
+	color: #ffffff;
+}
+
+.bg-yellow {
+	background-color: #fbbd08;
+	color: #333333;
+}
+
+.bg-olive {
+	background-color: #8dc63f;
+	color: #ffffff;
+}
+
+.bg-green {
+	background-color: #39b54a;
+	color: #ffffff;
+}
+
+.bg-cyan {
+	background-color: #1cbbb4;
+	color: #ffffff;
+}
+
+.bg-blue {
+	background-color: #0081ff;
+	color: #ffffff;
+}
+
+.bg-purple {
+	background-color: #6739b6;
+	color: #ffffff;
+}
+
+.bg-mauve {
+	background-color: #9c26b0;
+	color: #ffffff;
+}
+
+.bg-pink {
+	background-color: #e03997;
+	color: #ffffff;
+}
+
+.bg-brown {
+	background-color: #a5673f;
+	color: #ffffff;
+}
+
+.bg-grey {
+	background-color: #8799a3;
+	color: #ffffff;
+}
+
+.bg-gray {
+	background-color: #f0f0f0;
+	color: #333333;
+}
+
+.bg-black {
+	background-color: #333333;
+	color: #ffffff;
+}
+
+.bg-white {
+	background-color: #ffffff;
+	color: #666666;
+}
+
+.bg-shadeTop {
+	background-image: linear-gradient(rgba(0, 0, 0, 1), rgba(0, 0, 0, 0.01));
+	color: #ffffff;
+}
+
+.bg-shadeBottom {
+	background-image: linear-gradient(rgba(0, 0, 0, 0.01), rgba(0, 0, 0, 1));
+	color: #ffffff;
+}
+
+.bg-red.light {
+	color: #e54d42;
+	background-color: #fadbd9;
+}
+
+.bg-orange.light {
+	color: #f37b1d;
+	background-color: #fde6d2;
+}
+
+.bg-yellow.light {
+	color: #fbbd08;
+	background-color: #fef2ced2;
+}
+
+.bg-olive.light {
+	color: #8dc63f;
+	background-color: #e8f4d9;
+}
+
+.bg-green.light {
+	color: #39b54a;
+	background-color: #d7f0dbff;
+}
+
+.bg-cyan.light {
+	color: #1cbbb4;
+	background-color: #d2f1f0;
+}
+
+.bg-blue.light {
+	color: #0081ff;
+	background-color: #cce6ff;
+}
+
+.bg-purple.light {
+	color: #6739b6;
+	background-color: #e1d7f0;
+}
+
+.bg-mauve.light {
+	color: #9c26b0;
+	background-color: #ebd4ef;
+}
+
+.bg-pink.light {
+	color: #e03997;
+	background-color: #f9d7ea;
+}
+
+.bg-brown.light {
+	color: #a5673f;
+	background-color: #ede1d9;
+}
+
+.bg-grey.light {
+	color: #8799a3;
+	background-color: #e7ebed;
+}
+
+.bg-gradual-red {
+	background-image: linear-gradient(45deg, #e10a07, #ec008c);
+	color: #ffffff;
+}
+
+.bg-gradual-orange {
+	background-image: linear-gradient(45deg, #ff9700, #ed1c24);
+	color: #ffffff;
+}
+
+.bg-gradual-green {
+	background-image: linear-gradient(45deg, #39b54a, #8dc63f);
+	color: #ffffff;
+}
+
+.bg-gradual-purple {
+	background-image: linear-gradient(45deg, #9000ff, #5e00ff);
+	color: #ffffff;
+}
+
+.bg-gradual-pink {
+	background-image: linear-gradient(45deg, #ec008c, #6739b6);
+	color: #ffffff;
+}
+
+.bg-gradual-blue {
+	background-image: linear-gradient(45deg, #0081ff, #1cbbb4);
+	color: #ffffff;
+}
+
+.shadow[class*="-red"] {
+	box-shadow: 6upx 6upx 8upx rgba(204, 69, 59, 0.2);
+}
+
+.shadow[class*="-orange"] {
+	box-shadow: 6upx 6upx 8upx rgba(217, 109, 26, 0.2);
+}
+
+.shadow[class*="-yellow"] {
+	box-shadow: 6upx 6upx 8upx rgba(224, 170, 7, 0.2);
+}
+
+.shadow[class*="-olive"] {
+	box-shadow: 6upx 6upx 8upx rgba(124, 173, 55, 0.2);
+}
+
+.shadow[class*="-green"] {
+	box-shadow: 6upx 6upx 8upx rgba(48, 156, 63, 0.2);
+}
+
+.shadow[class*="-cyan"] {
+	box-shadow: 6upx 6upx 8upx rgba(28, 187, 180, 0.2);
+}
+
+.shadow[class*="-blue"] {
+	box-shadow: 6upx 6upx 8upx rgba(0, 102, 204, 0.2);
+}
+
+.shadow[class*="-purple"] {
+	box-shadow: 6upx 6upx 8upx rgba(88, 48, 156, 0.2);
+}
+
+.shadow[class*="-mauve"] {
+	box-shadow: 6upx 6upx 8upx rgba(133, 33, 150, 0.2);
+}
+
+.shadow[class*="-pink"] {
+	box-shadow: 6upx 6upx 8upx rgba(199, 50, 134, 0.2);
+}
+
+.shadow[class*="-brown"] {
+	box-shadow: 6upx 6upx 8upx rgba(140, 88, 53, 0.2);
+}
+
+.shadow[class*="-grey"] {
+	box-shadow: 6upx 6upx 8upx rgba(114, 130, 138, 0.2);
+}
+
+.shadow[class*="-gray"] {
+	box-shadow: 6upx 6upx 8upx rgba(114, 130, 138, 0.2);
+}
+
+.shadow[class*="-black"] {
+	box-shadow: 6upx 6upx 8upx rgba(26, 26, 26, 0.2);
+}
+
+.shadow[class*="-white"] {
+	box-shadow: 6upx 6upx 8upx rgba(26, 26, 26, 0.2);
+}
+
+.text-shadow[class*="-red"] {
+	text-shadow: 6upx 6upx 8upx rgba(204, 69, 59, 0.2);
+}
+
+.text-shadow[class*="-orange"] {
+	text-shadow: 6upx 6upx 8upx rgba(217, 109, 26, 0.2);
+}
+
+.text-shadow[class*="-yellow"] {
+	text-shadow: 6upx 6upx 8upx rgba(224, 170, 7, 0.2);
+}
+
+.text-shadow[class*="-olive"] {
+	text-shadow: 6upx 6upx 8upx rgba(124, 173, 55, 0.2);
+}
+
+.text-shadow[class*="-green"] {
+	text-shadow: 6upx 6upx 8upx rgba(48, 156, 63, 0.2);
+}
+
+.text-shadow[class*="-cyan"] {
+	text-shadow: 6upx 6upx 8upx rgba(28, 187, 180, 0.2);
+}
+
+.text-shadow[class*="-blue"] {
+	text-shadow: 6upx 6upx 8upx rgba(0, 102, 204, 0.2);
+}
+
+.text-shadow[class*="-purple"] {
+	text-shadow: 6upx 6upx 8upx rgba(88, 48, 156, 0.2);
+}
+
+.text-shadow[class*="-mauve"] {
+	text-shadow: 6upx 6upx 8upx rgba(133, 33, 150, 0.2);
+}
+
+.text-shadow[class*="-pink"] {
+	text-shadow: 6upx 6upx 8upx rgba(199, 50, 134, 0.2);
+}
+
+.text-shadow[class*="-brown"] {
+	text-shadow: 6upx 6upx 8upx rgba(140, 88, 53, 0.2);
+}
+
+.text-shadow[class*="-grey"] {
+	text-shadow: 6upx 6upx 8upx rgba(114, 130, 138, 0.2);
+}
+
+.text-shadow[class*="-gray"] {
+	text-shadow: 6upx 6upx 8upx rgba(114, 130, 138, 0.2);
+}
+
+.text-shadow[class*="-black"] {
+	text-shadow: 6upx 6upx 8upx rgba(26, 26, 26, 0.2);
+}
+
+.bg-img {
+	background-size: cover;
+	background-position: center;
+	background-repeat: no-repeat;
+}
+
+.bg-mask {
+	background-color: #333333;
+	position: relative;
+}
+
+.bg-mask::after {
+	content: "";
+	border-radius: inherit;
+	width: 100%;
+	height: 100%;
+	display: block;
+	background-color: rgba(0, 0, 0, 0.4);
+	position: absolute;
+	left: 0;
+	right: 0;
+	bottom: 0;
+	top: 0;
+}
+
+.bg-mask view,
+.bg-mask cover-view {
+	z-index: 5;
+	position: relative;
+}
+
+.bg-video {
+	position: relative;
+}
+
+.bg-video video {
+	display: block;
+	height: 100%;
+	width: 100%;
+	-o-object-fit: cover;
+	object-fit: cover;
+	position: absolute;
+	top: 0;
+	z-index: 0;
+	pointer-events: none;
+}
+
+/* ==================
+          文本
+ ==================== */
+
+.text-xs {
+	font-size: 20upx;
+}
+
+.text-sm {
+	font-size: 24upx;
+}
+.text-26 {
+	font-size: 26upx;
+}
+
+.text-df {
+	font-size: 28upx;
+}
+.text-30{
+	font-size: 30upx;
+}
+.text-lg {
+	font-size: 32upx;
+}
+
+.text-xl {
+	font-size: 36upx;
+}
+
+.text-xxl {
+	font-size: 44upx;
+}
+
+.text-sl {
+	font-size: 80upx;
+}
+
+.text-xsl {
+	font-size: 120upx;
+}
+
+.text-Abc {
+	text-transform: Capitalize;
+}
+
+.text-ABC {
+	text-transform: Uppercase;
+}
+
+.text-abc {
+	text-transform: Lowercase;
+}
+
+.text-price::before {
+	content: "¥";
+	font-size: 80%;
+	margin-right: 4upx;
+}
+
+.text-cut {
+	text-overflow: ellipsis;
+	white-space: nowrap;
+	overflow: hidden;
+}
+
+.text-bold {
+	font-weight: bold;
+}
+
+.text-center {
+	text-align: center;
+}
+
+.text-content {
+	line-height: 1.6;
+}
+
+.text-left {
+	text-align: left;
+}
+
+.text-right {
+	text-align: right;
+}
+
+.text-red,
+.line-red,
+.lines-red {
+	color: #e54d42;
+}
+
+.text-orange,
+.line-orange,
+.lines-orange {
+	color: #f37b1d;
+}
+
+.text-yellow,
+.line-yellow,
+.lines-yellow {
+	color: #fbbd08;
+}
+
+.text-olive,
+.line-olive,
+.lines-olive {
+	color: #8dc63f;
+}
+
+.text-green,
+.line-green,
+.lines-green {
+	color: #e10a07;
+}
+
+.text-cyan,
+.line-cyan,
+.lines-cyan {
+	color: #1cbbb4;
+}
+
+.text-blue,
+.line-blue,
+.lines-blue {
+	color: #557EFD;
+}
+
+.text-purple,
+.line-purple,
+.lines-purple {
+	color: #6739b6;
+}
+
+.text-mauve,
+.line-mauve,
+.lines-mauve {
+	color: #9c26b0;
+}
+
+.text-pink,
+.line-pink,
+.lines-pink {
+	color: #e03997;
+}
+
+.text-brown,
+.line-brown,
+.lines-brown {
+	color: #a5673f;
+}
+
+.text-grey,
+.line-grey,
+.lines-grey {
+	color: #8799a3;
+}
+
+.text-gray,
+.line-gray,
+.lines-gray {
+	color: #aaaaaa;
+}
+
+.text-black,
+.line-black,
+.lines-black {
+	color: #333333;
+}
+
+.text-white,
+.line-white,
+.lines-white {
+	color: #343546;
+}

+ 312 - 0
components/com-input.vue

@@ -0,0 +1,312 @@
+<template>
+	<view>
+		<view class="mix-list-cell" :class="border" hover-class="cell-hover" :hover-stay-time="50">
+			<text class="cell-tit">{{title}}</text>
+			<input class="main-input" :value="value" :type="_type" placeholder-class="placeholder-class"
+				:maxlength="maxlength" :placeholder="placeholder" :password="type==='password'&&!showPassword"
+				@input="onInput" :disabled="readOnly" />
+
+			<!-- 是否可见密码 -->
+			<image v-if="_isShowPass&&type==='password'&&!_isShowCode" class="img cuIcon"
+				:class="showPassword?'cuIcon-attention':'cuIcon-attentionforbid'" @tap="showPass"></image>
+			<!-- 倒计时 -->
+			<view v-if="_isShowCode&&!_isShowPass" :class="['vercode',{'vercode-run': second>0}]" @click="setCode">
+				{{ getVerCodeSecond }}
+			</view>
+		</view>
+	</view>
+
+</template>
+
+<script>
+	var _this, countDown;
+	/**
+	 *  简单封装了下, 应用范围比较狭窄,可以在此基础上进行扩展使用
+	 *  比如加入image, iconSize可控等
+	 */
+	export default {
+		data() {
+			return {
+				showPassword: false, //是否显示明文
+				second: 0, //倒计时
+				isRunCode: false, //是否开始倒计时
+				typeList: {
+					left: 'icon-zuo',
+					right: 'icon-you',
+					up: 'icon-shang',
+					down: 'icon-xia'
+				},
+			}
+		},
+		props: {
+			readOnly: {
+				//是否显示获取验证码(二选一)
+				type: [Boolean, String],
+				default: false,
+			},
+			type: String, //类型
+			logo: String, //类型
+			value: String, //值
+			placeholder: String, //框内提示
+			isShowCode: {
+				//是否显示获取验证码(二选一)
+				type: [Boolean, String],
+				default: false,
+			},
+			codeText: {
+				type: String,
+				default: "获取验证码",
+			},
+			setTime: {
+				//倒计时时间设置
+				type: [Number, String],
+				default: 60,
+			},
+			maxlength: {
+				//最大长度
+				type: [Number, String],
+				default: 30,
+			},
+			isShowPass: {
+				//是否显示密码图标(二选一)
+				type: [Boolean, String],
+				default: false,
+			},
+			icon: {
+				type: String,
+				default: ''
+			},
+			title: {
+				type: String,
+				default: '标题'
+			},
+			tips: {
+				type: String,
+				default: ''
+			},
+			navigateType: {
+				type: String,
+				default: 'right'
+			},
+			border: {
+				type: String,
+				default: 'b-b'
+			},
+			hoverClass: {
+				type: String,
+				default: 'cell-hover'
+			},
+			iconColor: {
+				type: String,
+				default: '#333'
+			}
+		},
+		mounted() {
+			_this = this
+			//准备触发
+			this.$on('runCodes', (val) => {
+				this.runCodes(val);
+			});
+			clearInterval(countDown); //先清理一次循环,避免缓存
+		},
+		methods: {
+			showPass() {
+				//是否显示密码
+				this.showPassword = !this.showPassword
+			},
+			onInput(e) {
+				//传出值
+				this.$emit('input', e.target.value)
+			},
+			setCode() {
+				//设置获取验证码的事件
+				if (this.isRunCode) {
+					//判断是否开始倒计时,避免重复点击
+					return false;
+				}
+				this.$emit('setCode')
+			},
+			runCodes(val) {
+				console.error("runCodes")
+				//开始倒计时
+				if (String(val) == "0") {
+
+					//判断是否需要终止循环
+					this.second = 0; //初始倒计时
+					clearInterval(countDown); //清理循环
+					this.isRunCode = false; //关闭循环状态
+					return false;
+				}
+				if (this.isRunCode) {
+					//判断是否开始倒计时,避免重复点击
+					return false;
+				}
+				this.isRunCode = true
+				this.second = this._setTime //倒数秒数
+
+				let _this = this;
+				countDown = setInterval(function() {
+					_this.second--
+					if (_this.second == 0) {
+						_this.isRunCode = false
+						clearInterval(countDown)
+					}
+				}, 1000)
+			}
+
+
+		},
+		computed: {
+			_type() {
+				//处理值
+				const type = this.type
+				return type == 'password' ? 'text' : type
+			},
+			_isShowPass() {
+				//处理值
+				return String(this.isShowPass) !== 'false'
+			},
+			_isShowCode() {
+				//处理值
+				return String(this.isShowCode) !== 'false'
+			},
+			_setTime() {
+				//处理值
+				const setTime = Number(this.setTime)
+				return setTime > 0 ? setTime : 60
+			},
+			getVerCodeSecond() {
+				//验证码倒计时计算
+				if (this.second <= 0) {
+					return this.codeText;
+				} else {
+					if (this.second < 10) {
+						return '0' + this.second + "s";
+					} else {
+						return this.second + "s";
+					}
+				}
+
+			}
+		}
+	}
+</script>
+
+<style lang='scss'>
+	.main-input {
+		flex: 1;
+		text-align: left;
+		/* color: white; */
+		font-size: 16px;
+		padding-right: 6px;
+		margin-left: 10px;
+		border: 1rpx solid #d9d9d9;
+		height: 90rpx;
+		border-radius: 10rpx;
+		padding:0 15rpx;
+	}
+
+	.icon .mix-list-cell.b-b:after {
+		left: 45px;
+	}
+
+	.placeholder-class {
+		/* color: white; */
+		opacity: 0.5;
+	}
+
+	.mix-list-cell {
+		border-radius: 32upx;
+		margin-top: 1px;
+		font-size: 32upx;
+		background: #ffffff;
+		text-align: left;
+		display: flex;
+		margin: 32upx;
+		/* padding: 24upx 32upx; */
+		position: relative;
+
+		&.cell-hover {
+			background: transparent;
+		}
+
+		&.b-b:after {
+			left: 16px;
+		}
+
+		.cell-icon {
+			align-self: center;
+			width: 28px;
+			max-height: 30px;
+			font-size: 18px;
+		}
+
+		.cell-more {
+			align-self: center;
+			font-size: 16px;
+			color: #606266;
+			margin-left: 10px;
+		}
+
+		.cell-tit {
+			width: 80px;
+			font-size: 16px;
+			/* color: white; */
+			display: flex;
+			align-items: center;
+		}
+
+		.cell-tip {
+			font-size: 14px;
+			color: white;
+		}
+
+	}
+
+	.items {
+		position: absolute;
+		height: 48px;
+		width: 100%;
+		background: #FFFFFF;
+		/*opacity:0.05;*/
+	}
+
+	.main-list {
+		opacity: 0.8;
+		z-index: 88;
+		background: white;
+		border: 1px solid white;
+		display: flex;
+		flex-direction: row;
+		justify-content: space-between;
+		align-items: center;
+		height: 18px;
+		/* Input 高度 */
+		color: #333333;
+		padding: 16px;
+		margin-top: 12px;
+		margin-bottom: 12px;
+	}
+
+	.img {
+		width: 16px;
+		height: 16px;
+		font-size: 16px;
+	}
+
+	.vercode {
+		color: #e10a07;
+		font-size: 14px;
+	}
+
+	.vercode-run {
+		color: black !important;
+	}
+
+	.oBorder {
+
+		border-radius: 2.5rem;
+		-webkit-box-shadow: 0 0 30px 0 rgba(43, 86, 112, .1);
+		box-shadow: 0 0 30px 0 rgba(43, 86, 112, .1);
+	}
+</style>

+ 180 - 0
components/companyListIndex/companyListIndex.vue

@@ -0,0 +1,180 @@
+<template>
+	<view class="list flex align-center justify-center">
+		<view class="list-box" @click="goInfo(item)">
+			<view class="list-box-info flex justify-between">
+				<image :src="item.companyLogo?item.companyLogo:'/static/logo.png'" mode="aspectFill"></image>
+				<view class="list-box-info-r">
+					<view class="list-box-info-r-name">
+						{{item.companyName?item.companyName:'匿名公司'}}
+					</view>
+					<view class="list-box-info-r-tips flex flex-wrap"
+						v-if="item.city || item.companyScope || item.companyPeople">
+						<view class="list-box-info-r-tipss" v-if="item.city">
+							{{item.city}}
+						</view>
+						<view class="list-box-info-r-tipss" v-if="item.companyScope">
+							{{item.companyScope}}
+						</view>
+						<view class="list-box-info-r-tipss" v-if="item.companyPeople">
+							{{item.companyPeople}}
+						</view>
+					</view>
+					<view class="list-box-info-r-address">
+						{{item.companyAddress}}
+					</view>
+				</view>
+			</view>
+			<view class="list-box-line" :style="item.postPushList.length>0?'':'border:none'"></view>
+			<view class="list-box-item" :style="{border:ind==item.postPushList.length-1?'none':''}"
+				v-for="(ite,ind) in item.postPushList" :key="ind">
+				<view class="list-box-item-top flex align-center justify-between">
+					<view class="list-box-item-top-l">
+						<block v-if="isSameName(ite.ruleClassifyName,ite.stationName)">
+							{{ite.ruleClassifyName}}-
+						</block>
+						<block>
+							{{ite.stationName}}
+						</block>
+					</view>
+					<view class="list-box-item-top-r">
+						{{ite.salaryRange}}
+					</view>
+				</view>
+				<view class="list-box-item-tips flex">
+					<view class="list-box-item-tipss" v-if="ite.education">
+						{{ite.education}}
+					</view>
+					<view class="list-box-item-tipss" v-if="ite.experience">
+						{{ite.experience}}
+					</view>
+					<view class="list-box-item-tipss" v-if="ite.industry">
+						{{ite.industry}}
+					</view>
+				</view>
+			</view>
+			<view class="list-box-btn flex align-center justify-center">
+				查看更多职位
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		name: "companyListIndex",
+		props: {
+			item: {
+				type: Object,
+				default: () => {}
+			}
+		},
+		data() {
+			return {};
+		},
+		methods: {
+			goInfo(item) {
+				this.$emit('goInfo', item)
+			},
+			isSameName(className, name) {
+				let str1 = className.trim();
+				let str2 = name.trim();
+				if (str1.length !== str2.length) {
+					return true;
+				}
+				return str1.toLowerCase() !== str2.toLowerCase();
+			},
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	.list {
+		width: 100%;
+		margin-bottom: 20rpx;
+
+		.list-box {
+			width: 686rpx;
+			height: 100%;
+			background-color: #ffffff;
+			border-radius: 24rpx;
+			padding: 30rpx;
+		}
+
+		.list-box-info {
+			image {
+				width: 160rpx;
+				height: 160rpx;
+				border-radius: 24rpx;
+			}
+
+			.list-box-info-r {
+				width: calc(100% - 180rpx);
+				height: 100%;
+			}
+
+			.list-box-info-r-name {
+				color: #1F1F1F;
+				font-size: 36rpx;
+				font-weight: bold;
+			}
+
+			.list-box-info-r-tips {}
+
+			.list-box-info-r-tipss {
+				background-color: #F6F6F6;
+				border-radius: 8rpx;
+				margin-right: 20rpx;
+				color: #666666;
+				font-size: 24rpx;
+				padding: 8rpx 16rpx;
+				margin-top: 16rpx;
+			}
+
+			.list-box-info-r-address {
+				margin-top: 16rpx;
+				color: #999999;
+				font-size: 26rpx;
+			}
+		}
+
+		.list-box-line {
+			border-bottom: 1px solid #E6E6E6;
+			margin-top: 30rpx;
+		}
+
+		.list-box-item {
+			margin-top: 30rpx;
+			border-bottom: 1px solid #E6E6E6;
+			padding-bottom: 30rpx;
+		}
+
+		.list-box-item-top-l {
+			font-size: 34rpx;
+			color: #1F1F1F;
+			font-weight: 500;
+		}
+
+		.list-box-item-top-r {
+			color: #00B88F;
+			font-size: 32rpx;
+			font-weight: bold;
+		}
+
+		.list-box-item-tipss {
+			background-color: #F6F6F6;
+			border-radius: 8rpx;
+			margin-right: 20rpx;
+			color: #666666;
+			font-size: 24rpx;
+			padding: 8rpx 16rpx;
+			margin-top: 16rpx;
+		}
+
+		.list-box-btn {
+			width: 100%;
+			height: 74rpx;
+			border: 1px solid #E6E6E6;
+			border-radius: 10rpx;
+		}
+	}
+</style>

+ 74 - 0
components/empty.vue

@@ -0,0 +1,74 @@
+<template>
+	<view class="page-box" :style="isShow?'height:50vh':''">
+		<view class="centre">
+			<image src="../static/images/empty.png" mode=""></image>
+			<view class="tips">
+				{{content}}
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		props: {
+			content: {
+				type: String,
+				default: '暂无内容'
+			},
+			isShow:{
+				type:Boolean,
+				default:true
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	.page-box {
+		position: relative;
+		left: 0;
+		// height: 50vh;
+		
+	}
+
+	.centre {
+		position: absolute;
+		left: 0;
+		top: 0;
+		right: 0;
+		bottom: 0;
+		margin: auto;
+		height: 0rpx;
+		text-align: center;
+		// padding: 200rpx auto;
+		font-size: 32rpx;
+
+		image {
+			// width: 387rpx;
+			// height: 341rpx;
+			width: 340rpx;
+			height: 270rpx;
+			// margin-bottom: 20rpx;
+			margin: 0 auto 20rpx;
+			// border: 1px dotted #000000;
+		}
+
+		.tips {
+			font-size: 32rpx;
+			color: #2F3044;
+			margin-top: 20rpx;
+			font-weight: 700;
+		}
+
+		.btn {
+			margin: 80rpx auto;
+			width: 600rpx;
+			border-radius: 32rpx;
+			line-height: 90rpx;
+			color: #ffffff;
+			font-size: 34rpx;
+			background: #5074FF;
+		}
+	}
+</style>

+ 74 - 0
components/emptys.vue

@@ -0,0 +1,74 @@
+<template>
+	<view class="page-box">
+		<view class="centre">
+			<image src="../static/images/empty.png" mode=""></image>
+			<view class="tips">
+				{{content}}
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		props: {
+			// content: {
+			// 	type: String,
+			// 	default: '暂无内容'
+			// }
+		},
+		data(){
+			return {
+				content:'暂无内容'
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	.page-box {
+		position: relative;
+		left: 0;
+		// height: 50vh;
+	}
+
+	.centre {
+		position: absolute;
+		left: 0;
+		top: 0;
+		right: 0;
+		bottom: 0;
+		margin: auto;
+		height: 0rpx;
+		text-align: center;
+		// padding: 200rpx auto;
+		font-size: 32rpx;
+
+		image {
+			// width: 387rpx;
+			// height: 341rpx;
+			width: 340rpx;
+			height: 270rpx;
+			// margin-bottom: 20rpx;
+			margin: 0 auto 20rpx;
+			// border: 1px dotted #000000;
+		}
+
+		.tips {
+			font-size: 32rpx;
+			color: #2F3044;
+			margin-top: 20rpx;
+			font-weight: 700;
+		}
+
+		.btn {
+			margin: 80rpx auto;
+			width: 600rpx;
+			border-radius: 32rpx;
+			line-height: 90rpx;
+			color: #ffffff;
+			font-size: 34rpx;
+			background: #5074FF;
+		}
+	}
+</style>

+ 141 - 0
components/home-list/homeList.vue

@@ -0,0 +1,141 @@
+<template>
+	<view>
+		<view class="listbox" v-for="(item,index) in list" :key="index" @tap="clickItem(item,index)">
+			<view class="flex align-center justify-between">
+				<view>
+					<view class="" style="color: #000;font-size: 34rpx; font-weight: 800;">{{getfomate(item.resumesName,item.resumesSex)}}</view>
+					<view class="flex align-center margin-top-xs flex-wrap" style="color: #999999;">
+						<text>{{item.resumesSex==1?'男':'女'}}</text>
+						<text class="margin-lr-sm" style="width: 1rpx;height: 25rpx;background: #CCCCCC;">
+						</text>
+						<text>{{item.resumesAge}}岁</text>
+						<text class="margin-lr-sm" style="width: 1rpx;height: 25rpx;background: #CCCCCC;">
+						</text>
+						<text>{{item.resumesWorkExperience}}</text>
+						<text class="margin-lr-sm" style="width: 1rpx;height: 25rpx;background: #CCCCCC;">
+						</text>
+						<text>{{item.resumesEducation}}</text>
+						<text class="margin-lr-sm" style="width: 1rpx;height: 25rpx;background: #CCCCCC;">
+						</text>
+						<text>{{item.resumesCompensation}}</text>
+					</view>
+				</view>
+				<view>
+					<image :src="item.avatar?item.avatar:'../../static/logo.png'"
+						style="width: 100upx;height: 100upx;border-radius: 55upx;">
+					</image>
+				</view>
+			</view>
+			<view class="margin-top-sm" v-if="item.resumesCompanyList">
+				<view class="flex align-center">
+					<view class="margin-right-xs">
+						<image src="../../static/images/qi.png" style="width: 30upx;height: 32upx;"></image>
+					</view>
+					<view style="color: #121212;">{{item.resumesCompanyList.length!=0?item.resumesCompanyList[0].resumesTitle:'暂无'}}</view>
+				</view>
+				<view class="text-sm margin-left margin-top-xs padding-left-xs" style="color: #999999;">
+					{{item.resumesCompanyList.length!=0?item.resumesCompanyList[0].resumesPost:'暂无'}}
+				</view>
+			</view>
+			<!-- <view class="margin-top-sm" v-if="item.school">
+				<view class="flex align-center">
+					<view class="margin-right-xs">
+						<image src="../../static/images/geren.png" style="width: 30upx;height:26upx;"></image>
+					</view>
+					<view style="color: #121212;">{{item.school?item.school:'暂无'}}</view>
+				</view>
+				<view class="text-sm margin-left margin-top-xs padding-left-xs" style="color: #999999;">
+					{{item.rulePostName}}
+				</view>
+			</view> -->
+			<view class="margin-top-sm" v-if="item.resumesWorkList">
+				<view class="flex">
+					<view class="margin-right-xs" style="padding-top: 8rpx;">
+						<image src="https://zhaopin.xianmaxiong.com/file/uploadPath/2022/09/20/15a1ea35392f86d2dc0f1dc2def0f2d9.png" style="width: 30upx;height:30upx;"></image>
+					</view>
+					<view style="color: #121212;">意向岗位<text class="text-sm" style="margin-left: 10rpx;color: #999999;">{{item.rulePostName}}</text></view>
+				</view>
+				<view class="text-sm margin-left margin-top-xs padding-left-xs" style="color: #999999;">
+					{{item.resumesPost}}
+				</view>
+			</view>
+			<view class="margin-top-sm" v-if="item.resumesImageName">
+				<view class="flex">
+					<view class="margin-right-xs" style="padding-top: 8rpx;">
+						<image src="https://zhaopin.xianmaxiong.com/file/uploadPath/2022/10/28/6f2c3bc743f8676cf959e4ecda1ca9d6.png" style="width: 30upx;height:30upx;"></image>
+					</view>
+					<view style="color: #121212;">资格证书</view>
+				</view>
+				<view class="text-sm margin-left margin-top-xs padding-left-xs" style="color: #999999;">
+					{{item.resumesImageName}}
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		name: 'taskHomeList',
+		props: {
+			list: {
+				type: Array,
+				default () {
+					return [];
+				}
+			},
+			//背景颜色
+			backgroundColor: {
+				type: String,
+				default: '#FFFFFF'
+			},
+			//是否需要下方横线
+			splitLine: {
+				type: Boolean,
+				default: false
+			},
+			//下方20rpx margin
+			bottomMargin: {
+				type: Boolean,
+				default: false
+			},
+			radius: {
+				type: Boolean,
+				default: false
+			},
+		},
+		watch: {
+	
+		},
+		data() {
+			return {};
+		},
+		methods: {
+			//格式化姓名
+			getfomate(data,sex){
+				let housex = ''
+				if(sex==1){ //先生
+					housex = '先生'
+				}else{ //女士
+					housex = '女士'
+				}
+				return data.substring(0,1)+housex
+			},
+			clickItem(item, index) {
+				this.$emit('click', {
+					item: item,
+					index: index,
+				});
+			}
+		}
+	};
+</script>
+
+<style>
+	.listbox {
+		background: #FFFFFF;
+		border-radius: 24upx;
+		margin: 0upx 30upx 20upx 30upx;
+		padding: 30upx;
+	}
+</style>

+ 114 - 0
components/home-list/homeuserList.vue

@@ -0,0 +1,114 @@
+<template>
+	<view>
+		<view class="listbox" v-for="(item,index) in list" :key="index" @tap="clickItems(item,index)">
+			<view class="flex align-center justify-between">
+				<!-- <view class="text-xl text-bold" style="color: #6696FF;">{{item.projectName}}</view> -->
+				<view class="text-xl text-bold" v-if="types==true" style="color: rgba(105, 150, 255, 0.8);">{{item.address}}</view>
+				<view class="text-xl text-bold" v-else style="color: rgba(105, 150, 255, 1);">{{item.address}}</view>
+				<view class="text-lg text-bold" style="color: #FF4A28;">{{item.projectDayNum?item.projectDayNum:0}}个月</view>
+			</view>
+			<view class="flex align-center margin-top-sm flex-wrap">
+				<view class="argrtn">{{item.projectType}}</view>
+				<block v-if="item.projectAward && types==true">
+					<view class="argrtn" style="background-color: #ffffff;color: #6996ff;border: 1rpx solid #6996ff;" v-for="(ite,index) in item.projectAward?item.projectAward.split(','):[]" :key="index">{{ite}}</view>
+				</block>
+				
+			</view>
+			<!-- <view class="flex align-center justify-between margin-top">
+				<view class="" style="color: #666666;">{{item.companyName}}</view>
+				<view>{{item.address}}</view>
+			</view> -->
+			<view class="flex align-center" style="margin-top: 10rpx;" v-if="item.postDetailsList.length>0" v-for="(ite,ind) in item.postDetailsList" :key="ind">
+				<!-- <view class="" >
+					{{ite.postAge}}
+				</view>
+				<view class="" style="height: 26rpx;width: 2rpx;border: 2rpx solid #cccccc;margin: 0 10rpx 0 10rpx;"></view>
+				<view class="">
+					{{ite.postSex}}
+				</view>
+				<view class="" style="height: 26rpx;width: 2rpx;border: 2rpx solid #cccccc;margin: 0 10rpx 0 10rpx;"></view> -->
+				<view class="">
+					{{ite.postName}}
+				</view>
+				<view class="" style="height: 26rpx;width: 2rpx;border: 2rpx solid #cccccc;margin: 0 10rpx 0 10rpx;"></view>
+				<view class="">
+					{{ite.postPeopleNum}}人
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		
+		name: 'taskHomeList',
+		props: {
+			list: {
+				type: Array,
+				default () {
+					return [];
+				}
+			},
+			//是否显示福利
+			types: {
+				type: Boolean,
+				default: false
+			},
+			//背景颜色
+			backgroundColor: {
+				type: String,
+				default: '#FFFFFF'
+			},
+			//是否需要下方横线
+			splitLine: {
+				type: Boolean,
+				default: false
+			},
+			//下方20rpx margin
+			bottomMargin: {
+				type: Boolean,
+				default: false
+			},
+			radius: {
+				type: Boolean,
+				default: false
+			},
+		},
+		watch: {
+	
+		},
+		data() {
+			return {
+			};
+		},
+		methods: {
+			clickItems(item, index) {
+				this.$emit('click', {
+					item: item,
+					index: index,
+				});
+			}
+		}
+	};
+</script>
+
+<style>
+	.listbox {
+		background: #FFFFFF;
+		border-radius: 24upx;
+		margin: 0upx 30upx 20upx 30upx;
+		padding: 30upx;
+	}
+	.argrtn {
+		background: #FFFFFF;
+		color: #6996ff;
+		font-size: 24upx;
+		border: 1rpx solid #6996ff;
+		border-radius: 8upx;
+		padding: 10upx 20upx;
+		margin-right: 20upx;
+		margin-bottom: 10rpx;
+	}
+	
+</style>

+ 19 - 0
components/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);
+	}
+}

+ 422 - 0
components/mescroll-uni/components/mescroll-body/mescroll-body.vue

@@ -0,0 +1,422 @@
+<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>

+ 55 - 0
components/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
components/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
components/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
components/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
components/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
components/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
components/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
components/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: '没有更多数据了', // 没有更多数据的提示文本
+				empty: {
+					tip: '~ 空空如也 ~' // 空提示
+				}
+			}
+		},
+		// 英文
+		en: {
+			down: {
+				textInOffset: 'drop down refresh',
+				textOutOffset: 'release updates',
+				textLoading: 'loading ...',
+				textSuccess: 'loaded successfully',
+				textErr: 'loading failed'
+			},
+			up: {
+				textLoading: 'loading ...',
+				textNoMore: '没有更多数据了',
+				empty: {
+					tip: '~ absolutely empty ~'
+				}
+			}
+		}
+	}
+}
+
+export default GlobalOption

+ 36 - 0
components/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
components/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: '没有更多数据了', // 没有更多数据的提示文本
+		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
components/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
components/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
components/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
components/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
components/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
components/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
components/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
+}

+ 78 - 0
components/mescroll-uni/good-list/good-list.vue

@@ -0,0 +1,78 @@
+<!-- 商品列表组件 <good-list :list="xx"></good-list> -->
+<template>
+	<view>
+		<view class="padding-sm  bg-white" v-if="list.length != 0"
+			style=" width: 100%;column-count: 2;column-gap: 10px;margin: 0 auto;">
+			<view class="bg-white radius margin-bottom-sm"
+				style="width: 100%;overflow: hidden;box-shadow: 0rpx 0rpx 10rpx #e5e5e5;break-inside: avoid;"
+				v-for="(item, index) in list" :key="index" @click="goHouseDet(item)">
+				<image :src="item.titleImg?item.titleImg: '../../../static/logo.png'"
+					style="width: 100%;height: 280rpx;" mode="aspectFill"></image>
+				<view>
+					<view class="padding-lr-sm">
+						<view class="text-lg text-bold "
+							style="overflow: hidden;text-overflow: ellipsis;-webkit-line-clamp:2;display:-webkit-box;-webkit-box-orient: vertical;">
+							{{item.name}}
+						</view>
+						<view class="flex flex-wrap" v-if="item.label != ''" style="width: 100%;">
+							<view v-if="ite" v-for="(ite,ind) in item.label" :key="ind"
+								class='cu-tag radius bg-orange light' style="margin-top: 4px;">{{ite}}</view>
+						</view>
+						<view class="text-orange text-bold flex" v-if="item.price" style="margin-top: 10px;">
+							<view class="text-sm" style="line-height:46upx;margin-right: 4upx;">¥</view>
+							<view class="text-lg">{{item.price}}</view>
+						</view>
+					</view>
+					<view class="padding-lr-sm padding-bottom-sm" style="margin-top: 5px;">
+						<view style="height: 1px;background-color: #F7F7F7;"></view>
+						<view class="text-gray"
+							style="overflow: hidden;white-space: nowrap;text-overflow: ellipsis;margin-top: 8px;">
+							<text class='cuIcon-location' style="margin-right: 4px;font-size: 16px;"></text>
+							{{item.address}}
+						</view>
+					</view>
+				</view>
+			</view>
+		</view>
+		<empty v-if="list.length === 0" des="暂无数据" show="false"></empty>
+	</view>
+</template>
+
+<script>
+	export default {
+		props: {
+			list: {
+				type: Array,
+				default () {
+					return []
+				}
+			}
+		},
+		methods: {
+			goHouseDet(e) {
+				console.log(e)
+				let token = uni.getStorageSync('token')
+				if (token) {
+					uni.navigateTo({
+						url: "/pages/locality/houseDet?id=" + e.id + "&name=" + e.classifyName
+					})
+				} else {
+					uni.navigateTo({
+						url: '/pages/public/login'
+					});
+				}
+
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	.cu-tag {
+		height: 36rpx;
+	}
+
+	.radius {
+		border-radius: 4rpx;
+	}
+</style>

+ 66 - 0
components/mescroll-uni/good-list/orderList.vue

@@ -0,0 +1,66 @@
+<template>
+	<view>
+		<view class="margin-sm padding-sm bg-white" v-for="(item, index) in list">
+			<view class="flex justify-between solid-bottom padding-tb-sm">
+				<view>佛系小草莓</view>
+				<view class="text-orange" v-if="item.status==2">待接单</view>
+				<view class="text-orange" v-if="item.status==3">接单中</view>
+				<view class="text-orange" v-if="item.status==1">已完成</view>
+			</view>
+			<view class="text-lg text-black text-bold margin-top-sm">{{item.content}}</view>
+			<view class="flex margin-top-sm">
+				<view class="bg-blue light radius" style="padding: 1px 4px;">限美女</view>
+				<view class="bg-orange light radius margin-left" style="padding: 1px 4px;">取快递</view>
+				<view class="bg-orange light radius margin-left" style="padding: 1px 4px;">待接单</view>
+			</view>
+			<view class="margin-top-sm">下单时间:{{item.createTime}}</view>
+			<view class="flex justify-between margin-top-sm">
+				<view v-if="item.code">收货码:{{item.code}}</view>
+				<view>实付款:<text class="text-black text-bold">¥{{item.money}}</text></view>
+			</view>
+			<view class="flex justify-end bg-white padding-tb-sm" style="">
+				<button class="cu-btn  round margin-right" style="width: 90px;" @click="delDemand(item)" v-if="item.status==2">删除</button>
+				<button class="cu-btn bg-orange  round" style="width: 90px;" @click="edit(item)">完善需求</button>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		props: {
+			list: {
+				type: Array,
+				default () {
+					return []
+				}
+			}
+		},
+		methods: {
+			delDemand(e) {
+				let data = {
+					helpOrderId: e.id
+				}
+				this.$Request.postT('/help/deleteHelpOrder', data).then(res => {
+					uni.showToast({
+						title:"删除成功"
+					})
+				})
+			},
+			edit(item) {
+				uni.navigateTo({
+					url: '/pages/order/release?type=hasEdit&id=' + item.id + '&content=' + item.content +
+						'&site=' + item.site +
+						'&phone=' + item.phone + '&deliveryTime=' + item.deliveryTime + '&classifyId=' + item
+						.classifyId +
+						'&classifyName=' + item.classifyName + '&userType=' + item.userType + '&commission=' + item
+						.commission
+				})
+			}
+		}
+
+	}
+</script>
+
+<style>
+</style>

+ 49 - 0
components/mescroll-uni/good-list/publishList.vue

@@ -0,0 +1,49 @@
+<template>
+	<view>
+		<view class="bg-white" v-for="(item, index) in list" :key='index' @click="goNavList(item.id)">
+			<view class="flex justify-around padding-sm  ">
+				<img :src="item.titleImg" alt="" style="width: 100px;height: 100px;" >
+				<view class="margin-left-sm">
+					<view class="text-lg text-bold text-black" style="height: 42px;overflow: hidden;text-overflow: ellipsis;width: 225px;">{{item.address}}</view>
+					<view v-if="item.price" class="text-orange text-bold text-lg margin-tb-sm">¥ {{item.price}}</view>
+					<view>浏览6/联系1</view>
+				</view>
+			</view>
+			<view class="flex justify-end padding-tb-sm padding-right-sm">
+				<button class="cu-btn round margin-right-sm" v-if="item.status == 1" @click="editStatus(item)" >上架</button>
+				<button class="cu-btn round margin-right-sm" v-if="item.status == 2" @click="editStatus(item)" >下架</button>
+				<button class="cu-btn round margin-right-sm" v-if="item.status == 3" >已驳回</button>
+				<button class="cu-btn round margin-right-sm">删除</button>
+				<button class="cu-btn round ">编辑</button>
+			</view>
+		</view>
+		
+	</view>
+</template>
+
+<script>
+	export default {
+		props:{
+			list: {
+				type: Array,
+				default(){
+					return []
+				}
+			}
+		},
+		methods: {
+			
+			editStatus(e) {
+				let data = {
+					id: e.id
+				}
+				this.$Request.postT('/information/updateInformationStatus', data).then(res => {
+					
+				})
+			}
+		}
+	}
+</script>
+
+<style>
+</style>

+ 208 - 0
components/mescroll-uni/me-tabs/me-tabs.vue

@@ -0,0 +1,208 @@
+<!-- tab组件: <me-tabs v-model="tabIndex" :tabs="tabs" @change="tabChange"></me-tabs> -->
+<template>
+	<view class="me-tabs" :class="{'tabs-fixed': fixed}" :style="{height: tabHeightVal, top:topFixed, 'margin-top':topMargin}">
+		<scroll-view v-if="tabs.length" :id="viewId" :scroll-left="scrollLeft" scroll-x scroll-with-animation :scroll-animation-duration="300">
+			<view class="tabs-item" :class="{'tabs-flex':!isScroll, 'tabs-scroll':isScroll}">
+				<!-- tab -->
+				<view class="tab-item" :style="{width: tabWidthVal, height: tabHeightVal, 'line-height':tabHeightVal}" v-for="(tab, i) in tabs" :class="{'active': value===i}" :key="i" @click="tabClick(i)">
+					{{getTabName(tab)}}
+					<!-- {{tab.gameName}} -->
+				</view>
+				<!-- 下划线 -->
+				<!-- <view class="tabs-line" :style="{left:lineLeft}"></view> -->
+			</view>
+		</scroll-view>
+	</view>
+</template>
+
+<script>
+	export default {
+		props:{
+			tabs: { // 支持格式: ['全部', '待付款'] 或 [{name:'全部'}, {name:'待付款'}]
+				type: Array,
+				default(){
+					return []
+				}
+			},
+			nameKey: { // 取name的字段
+				type: String,
+				default: 'name'
+			},
+			value: { // 当前显示的下标 (使用v-model语法糖: 1.props需为value; 2.需回调input事件)
+				type: [String, Number],
+				default: 0
+			},
+			fixed: Boolean, // 是否悬浮,默认false
+			tabWidth: Number, // 每个tab的宽度,默认不设置值,为flex平均分配; 如果指定宽度,则不使用flex,每个tab居左,超过则水平滑动(单位默认rpx)
+			height: { // 高度,单位rpx
+				type: Number,
+				default: 80
+			},
+			top: { // 顶部偏移的距离,默认单位rpx (当fixed=true时,已加上windowTop)
+				type: Number,
+				default: 0
+			}
+		},
+		data() {
+			return {
+				viewId: 'id_' + Math.random().toString(36).substr(2,16),
+				scrollLeft: 0,
+				windowWidth: 0,
+				windowTop: 0
+			}
+		},
+		computed: {
+			isScroll(){
+				return this.tabWidth && this.tabs.length // 指定了tabWidth的宽度,则支持水平滑动
+			},
+			tabHeightPx(){
+				return uni.upx2px(this.height)
+			},
+			tabHeightVal(){
+				return this.tabHeightPx+'px'
+			},
+			tabWidthPx(){
+				return uni.upx2px(this.tabWidth)
+			},
+			tabWidthVal(){
+				return this.isScroll ? this.tabWidthPx+'px' : ''
+			},
+			lineLeft() {
+				if (this.isScroll) {
+					return this.tabWidthPx * this.value + this.tabWidthPx/2 + 'px' // 需转为px (用rpx的话iOS真机显示有误差)
+				} else{
+					return 100/this.tabs.length*(this.value + 1) - 100/(this.tabs.length*2) + '%'
+				}
+			},
+			topFixed(){
+				return this.fixed ? this.windowTop + uni.upx2px(this.top) + 'px' : 0
+			},
+			topMargin(){
+				return this.fixed ? 0 : this.top + 'rpx'
+			}
+		},
+		watch: {
+			tabs() {
+				this.warpWidth = null; // 重新计算容器宽度
+				this.scrollCenter(); // 水平滚动到中间
+			},
+			value() {
+				this.scrollCenter(); // 水平滚动到中间
+			}
+		},
+		created() {
+			let sys = uni.getSystemInfoSync();
+			this.windowWidth = sys.windowWidth
+			this.windowTop = sys.windowTop
+		},
+		mounted() {
+			this.scrollCenter() // 滚动到当前下标
+		},
+		methods: {
+			getTabName(tab){
+				return typeof tab === "object" ? tab[this.nameKey] : tab
+			},
+			tabClick(i){
+				if(this.value!=i){
+					this.$emit("input",i);
+					this.$emit("change",i);
+				}
+			},
+			async scrollCenter(){
+				if(!this.isScroll) return;
+				if(!this.warpWidth){ // tabs容器的宽度
+					let rect = await this.initWarpRect()
+					this.warpWidth = rect ? rect.width : this.windowWidth; // 某些情况下取不到宽度,暂时取屏幕宽度
+				}
+				let tabLeft = this.tabWidthPx * this.value + this.tabWidthPx/2; // 当前tab中心点到左边的距离
+				let diff = tabLeft - this.warpWidth/2 // 如果超过tabs容器的一半,则滚动差值
+				this.scrollLeft = diff;
+				// #ifdef MP-TOUTIAO
+				this.scrollTimer && clearTimeout(this.scrollTimer)
+				this.scrollTimer = setTimeout(()=>{ // 字节跳动小程序,需延时再次设置scrollLeft,否则tab切换跨度较大时不生效
+					this.scrollLeft = Math.ceil(diff)
+				},400)
+				// #endif
+			},
+			initWarpRect(){
+				return new Promise(resolve=>{
+					setTimeout(()=>{ // 延时确保dom已渲染, 不使用$nextclick
+						let query = uni.createSelectorQuery();
+						// #ifndef MP-ALIPAY
+						query = query.in(this) // 支付宝小程序不支持in(this),而字节跳动小程序必须写in(this), 否则都取不到值
+						// #endif
+						query.select('#'+this.viewId).boundingClientRect(data => {
+							resolve(data)
+						}).exec();
+					},20)
+				})
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	.me-tabs{
+		position: relative;
+		font-size: 30rpx;
+		background: #FFFFFF;
+		color: #343546;
+		// border-bottom: 1rpx solid #eee;
+		box-sizing: border-box;
+		overflow-y: hidden;
+		// padding: 0 10px ;
+		&.tabs-fixed{
+			z-index: 990;
+			position: fixed;
+			left: 0;
+			width: 100%;
+		}
+		
+		.tabs-item{
+			position: relative;
+			white-space: nowrap;
+			// padding-bottom: 30rpx; // 撑开高度,再配合me-tabs的overflow-y: hidden,以达到隐藏滚动条的目的
+			box-sizing: border-box;
+			
+			.tab-item{
+				position: relative;
+				text-align: center;
+				box-sizing: border-box;
+				margin: 0 15px;
+				&.active{
+					font-weight: bold;
+					color: #00B88F;
+					font-size: 32upx;
+					border-bottom: 4upx solid #00B88F;
+				}
+			}
+		}
+		
+		// 平分的方式显示item
+		.tabs-flex{
+			display: flex;
+			.tab-item{
+				flex: 1;
+			}
+		}
+		// 居左显示item,支持水平滑动
+		.tabs-scroll{
+			.tab-item{
+				display: inline-block;
+			}
+		}
+		
+		// 选中tab的线
+		.tabs-line{
+			z-index: 1;
+			position: absolute;
+			bottom: 30rpx; // 至少与.tabs-item的padding-bottom一致,才能保证在底部边缘
+			width: 50rpx;
+			height: 6rpx;
+			transform: translateX(-50%);
+			border-radius: 4rpx;
+			transition: left .3s;
+			background: #1789FD;
+		}
+	}
+</style>

+ 80 - 0
components/mescroll-uni/package.json

@@ -0,0 +1,80 @@
+{
+  "id": "mescroll-uni",
+  "displayName": "【wxs+renderjs实现】高性能的下拉刷新上拉加载组件",
+  "version": "1.3.7",
+  "description": "mescroll - 支持uni-app的下拉刷新和上拉加载的组件,支持原生页面和局部区域滚动",
+  "keywords": [
+    "下拉刷新",
+    "上拉加载",
+    "翻页",
+    "分页",
+    "wxs"
+],
+  "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": "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"
+        }
+      }
+    }
+  }
+}

+ 240 - 0
components/phone-directory/README.md

@@ -0,0 +1,240 @@
+#组件使用说明
+
+>示例数据均为静态数据,接口生成如下结构的数据:(已上传完整demo)
+
+---
+#### 索引列表
+```
+    {
+         "A": [{
+                "id": 56,
+                "spell": "aba",
+                "name": "阿坝"
+		    }]
+			......
+    }
+```
+
+#### 通讯录列表
+```
+    {
+         "A": [{
+                "id": 56,
+                "spell": "zs",
+                "name": "张三",
+                "phoneNumber":"13143599999"
+		    }]
+			......
+    }
+```
+
+> id spell name 为必须字段  phoneNumber字段如果没有则不返回
+
+
+---
+
+#### 使用示例 (phones.vue)
+
+```
+<template>
+	<view>
+		<phone-directory :phones="phones" @paramClick="paramClick"></phone-directory>
+	</view>
+</template>
+
+<script>
+	import phoneDirectory from '@/components/phone-directory/phone-directory.vue'
+	export default {
+		name:"phones",
+		components:{
+			phoneDirectory
+		},
+		data() {
+			return {
+				//示例数据
+				phones:{
+					"A": [{
+						"id": 56,
+						"spell": "aba",
+						"name": "阿坝"
+					}, {
+						"id": 57,
+						"spell": "akesu",
+						"name": "阿克苏"
+					}, {
+						"id": 58,
+						"spell": "alashanmeng",
+						"name": "阿拉善盟"
+					}, {
+						"id": 59,
+						"spell": "aletai",
+						"name": "阿勒泰"
+					}, {
+						"id": 60,
+						"spell": "ali",
+						"name": "阿里"
+					}, {
+						"id": 61,
+						"spell": "ankang",
+						"name": "安康"
+					}, {
+						"id": 62,
+						"spell": "anqing",
+						"name": "安庆"
+					}, {
+						"id": 63,
+						"spell": "anshan",
+						"name": "鞍山"
+					}, {
+						"id": 64,
+						"spell": "anshun",
+						"name": "安顺"
+					}, {
+						"id": 65,
+						"spell": "anyang",
+						"name": "安阳"
+					}, {
+						"id": 338,
+						"spell": "acheng",
+						"name": "阿城"
+					}, {
+						"id": 339,
+						"spell": "anfu",
+						"name": "安福"
+					}, {
+						"id": 340,
+						"spell": "anji",
+						"name": "安吉"
+					}, {
+						"id": 341,
+						"spell": "anning",
+						"name": "安宁"
+					}, {
+						"id": 342,
+						"spell": "anqiu",
+						"name": "安丘"
+					}, {
+						"id": 343,
+						"spell": "anxi",
+						"name": "安溪"
+					}, {
+						"id": 344,
+						"spell": "anyi",
+						"name": "安义"
+					}, {
+						"id": 345,
+						"spell": "anyuan",
+						"name": "安远"
+					}],
+					"B": [{
+						"id": 1,
+						"spell": "beijing",
+						"name": "北京"
+					}, {
+						"id": 66,
+						"spell": "baicheng",
+						"name": "白城"
+					}, {
+						"id": 67,
+						"spell": "baise",
+						"name": "百色"
+					}, {
+						"id": 68,
+						"spell": "baishan",
+						"name": "白山"
+                    }]
+                    ........
+				}
+			
+			}
+		},
+		methods : {
+			paramClick (e) {
+				console.log(e)
+			}
+		}
+	}
+</script>
+
+<style>
+
+</style>
+
+```
+
+#### 使用示例 (phone-search.vue)
+
+```
+<template>
+	<view>
+		<phone-search-list :phones="phones" @paramClick="paramClick"></phone-search-list>
+	</view>		
+</template>
+
+<script>
+	
+	import phoneSearchList from '@/components/phone-directory/phone-search-list.vue'
+	
+	export default {
+		name:"phone-search",
+		components:{
+			phoneSearchList
+		},
+		data() {
+			return {
+				phones:null,
+			}
+		},
+		onLoad (option) {
+			this.phones = JSON.parse(unescape(option.phones))
+		},
+		methods:{
+			paramClick (e) {
+				console.log(e)
+			}
+		}
+	}
+</script>
+
+<style>
+	
+</style>
+
+
+```
+
+#### 引入
+
+>把phone-directory目录拷贝至项目components
+
+>把pages下的phones目录拷贝至项目pages目录下
+
+>在项目pages.json文件写入
+
+(```)
+    {
+        "path" : "pages/phones/phones",
+        "style" : {
+            "navigationBarTitleText" : "通讯录",
+            "app-plus":{
+                "bounce":"none",
+                "scrollIndicator":"none"
+            }
+        }
+    },
+    {
+        "path" : "pages/phones/phone-search",
+        "style" : {
+            "navigationBarTitleText" : "搜索联系人"
+        }
+    }
+(```)
+
+### 组件参数说明
+
+属性名|类型|是否必填|说明
+--|:--:|--:
+phones|Object|是|接口数据
+paramClick|Object|是|当点击时返回对应参数
+
+>文件名 参数 也可自己定义 搜索框内容可在phone-directory.vue 和phone-search-list.vue  文件修改

+ 34 - 0
components/phone-directory/pages/phones/phone-search.vue

@@ -0,0 +1,34 @@
+<template>
+	<view>
+		<phone-search-list :phones="phones" @paramClick="paramClick"></phone-search-list>
+	</view>		
+</template>
+
+<script>
+	
+	import phoneSearchList from '@/components/phone-directory/phone-search-list.vue'
+	
+	export default {
+		name:"phone-search",
+		components:{
+			phoneSearchList
+		},
+		data() {
+			return {
+				phones:null,
+			}
+		},
+		onLoad (option) {
+			this.phones = JSON.parse(unescape(option.phones))
+		},
+		methods:{
+			paramClick (e) {
+				console.log(e)
+			}
+		}
+	}
+</script>
+
+<style>
+	
+</style>

+ 727 - 0
components/phone-directory/pages/phones/phones.vue

@@ -0,0 +1,727 @@
+<template>
+	<view>
+		<phone-directory :phones="phones" @paramClick="paramClick"></phone-directory>
+	</view>
+</template>
+
+<script>
+	import phoneDirectory from '@/components/phone-directory/phone-directory.vue'
+	export default {
+		name:"phones",
+		components:{
+			phoneDirectory
+		},
+		data() {
+			return {
+				//示例数据
+				phones:{
+					"A": [{
+						"id": 56,
+						"spell": "aba",
+						"name": "阿坝"
+					}, {
+						"id": 57,
+						"spell": "akesu",
+						"name": "阿克苏"
+					}, {
+						"id": 58,
+						"spell": "alashanmeng",
+						"name": "阿拉善盟"
+					}, {
+						"id": 59,
+						"spell": "aletai",
+						"name": "阿勒泰"
+					}, {
+						"id": 60,
+						"spell": "ali",
+						"name": "阿里"
+					}, {
+						"id": 61,
+						"spell": "ankang",
+						"name": "安康"
+					}, {
+						"id": 62,
+						"spell": "anqing",
+						"name": "安庆"
+					}, {
+						"id": 63,
+						"spell": "anshan",
+						"name": "鞍山"
+					}, {
+						"id": 64,
+						"spell": "anshun",
+						"name": "安顺"
+					}, {
+						"id": 65,
+						"spell": "anyang",
+						"name": "安阳"
+					}, {
+						"id": 338,
+						"spell": "acheng",
+						"name": "阿城"
+					}, {
+						"id": 339,
+						"spell": "anfu",
+						"name": "安福"
+					}, {
+						"id": 340,
+						"spell": "anji",
+						"name": "安吉"
+					}, {
+						"id": 341,
+						"spell": "anning",
+						"name": "安宁"
+					}, {
+						"id": 342,
+						"spell": "anqiu",
+						"name": "安丘"
+					}, {
+						"id": 343,
+						"spell": "anxi",
+						"name": "安溪"
+					}, {
+						"id": 344,
+						"spell": "anyi",
+						"name": "安义"
+					}, {
+						"id": 345,
+						"spell": "anyuan",
+						"name": "安远"
+					}],
+					"B": [{
+						"id": 1,
+						"spell": "beijing",
+						"name": "北京"
+					}, {
+						"id": 66,
+						"spell": "baicheng",
+						"name": "白城"
+					}, {
+						"id": 67,
+						"spell": "baise",
+						"name": "百色"
+					}, {
+						"id": 68,
+						"spell": "baishan",
+						"name": "白山"
+					}, {
+						"id": 69,
+						"spell": "baiyin",
+						"name": "白银"
+					}, {
+						"id": 70,
+						"spell": "bangbu",
+						"name": "蚌埠"
+					}, {
+						"id": 71,
+						"spell": "baoding",
+						"name": "保定"
+					}, {
+						"id": 72,
+						"spell": "baoji",
+						"name": "宝鸡"
+					}, {
+						"id": 73,
+						"spell": "baoshan",
+						"name": "保山"
+					}, {
+						"id": 74,
+						"spell": "baotou",
+						"name": "包头"
+					}, {
+						"id": 75,
+						"spell": "bayannaoer",
+						"name": "巴彦淖尔"
+					}, {
+						"id": 76,
+						"spell": "bayinguoleng",
+						"name": "巴音郭楞"
+					}, {
+						"id": 77,
+						"spell": "bazhong",
+						"name": "巴中"
+					}, {
+						"id": 78,
+						"spell": "beihai",
+						"name": "北海"
+					}, {
+						"id": 79,
+						"spell": "benxi",
+						"name": "本溪"
+					}, {
+						"id": 80,
+						"spell": "bijie",
+						"name": "毕节"
+					}, {
+						"id": 81,
+						"spell": "binzhou",
+						"name": "滨州"
+					}, {
+						"id": 82,
+						"spell": "boertala",
+						"name": "博尔塔拉"
+					}, {
+						"id": 83,
+						"spell": "bozhou",
+						"name": "亳州"
+					}, {
+						"id": 346,
+						"spell": "baoying",
+						"name": "宝应"
+					}, {
+						"id": 347,
+						"spell": "bayan",
+						"name": "巴彦"
+					}, {
+						"id": 348,
+						"spell": "binhai",
+						"name": "滨海"
+					}, {
+						"id": 349,
+						"spell": "binxian",
+						"name": "宾县"
+					}, {
+						"id": 350,
+						"spell": "binyang",
+						"name": "宾阳"
+					}, {
+						"id": 351,
+						"spell": "bishan",
+						"name": "璧山"
+					}, {
+						"id": 352,
+						"spell": "boai",
+						"name": "博爱"
+					}, {
+						"id": 353,
+						"spell": "boluo",
+						"name": "博罗"
+					}, {
+						"id": 354,
+						"spell": "boxing",
+						"name": "博兴"
+					}],
+					"C": [{
+						"id": 2,
+						"spell": "chongqing",
+						"name": "重庆"
+					}, {
+						"id": 5,
+						"spell": "changchun",
+						"name": "长春"
+					}, {
+						"id": 6,
+						"spell": "changsha",
+						"name": "长沙"
+					}, {
+						"id": 7,
+						"spell": "changzhou",
+						"name": "常州"
+					}, {
+						"id": 8,
+						"spell": "chengdu",
+						"name": "成都"
+					}, {
+						"id": 84,
+						"spell": "cangzhou",
+						"name": "沧州"
+					}, {
+						"id": 85,
+						"spell": "changde",
+						"name": "常德"
+					}, {
+						"id": 86,
+						"spell": "changdu",
+						"name": "昌都"
+					}, {
+						"id": 87,
+						"spell": "changji",
+						"name": "昌吉"
+					}, {
+						"id": 88,
+						"spell": "changzhi",
+						"name": "长治"
+					}, {
+						"id": 89,
+						"spell": "chaohu",
+						"name": "巢湖"
+					}, {
+						"id": 90,
+						"spell": "chaoyang",
+						"name": "朝阳"
+					}, {
+						"id": 91,
+						"spell": "chaozhou",
+						"name": "潮州"
+					}, {
+						"id": 92,
+						"spell": "chengde",
+						"name": "承德"
+					}, {
+						"id": 93,
+						"spell": "chenzhou",
+						"name": "郴州"
+					}, {
+						"id": 94,
+						"spell": "chifeng",
+						"name": "赤峰"
+					}, {
+						"id": 95,
+						"spell": "chizhou",
+						"name": "池州"
+					}, {
+						"id": 96,
+						"spell": "chongzuo",
+						"name": "崇左"
+					}, {
+						"id": 97,
+						"spell": "chuxiong",
+						"name": "楚雄"
+					}, {
+						"id": 98,
+						"spell": "chuzhou",
+						"name": "滁州"
+					}, {
+						"id": 355,
+						"spell": "cangnan",
+						"name": "苍南"
+					}, {
+						"id": 356,
+						"spell": "cangshan",
+						"name": "苍山"
+					}, {
+						"id": 357,
+						"spell": "caoxian",
+						"name": "曹县"
+					}, {
+						"id": 358,
+						"spell": "changdao",
+						"name": "长岛"
+					}, {
+						"id": 359,
+						"spell": "changfeng",
+						"name": "长丰"
+					}, {
+						"id": 360,
+						"spell": "changhai",
+						"name": "长海"
+					}, {
+						"id": 361,
+						"spell": "changle",
+						"name": "长乐"
+					}, {
+						"id": 362,
+						"spell": "changle",
+						"name": "昌乐"
+					}, {
+						"id": 363,
+						"spell": "changshan",
+						"name": "常山"
+					}, {
+						"id": 364,
+						"spell": "changshu",
+						"name": "常熟"
+					}, {
+						"id": 365,
+						"spell": "changtai",
+						"name": "长泰"
+					}, {
+						"id": 366,
+						"spell": "changting",
+						"name": "长汀"
+					}, {
+						"id": 367,
+						"spell": "changxing",
+						"name": "长兴"
+					}, {
+						"id": 368,
+						"spell": "changyi",
+						"name": "昌邑"
+					}, {
+						"id": 369,
+						"spell": "chaoan",
+						"name": "潮安"
+					}, {
+						"id": 370,
+						"spell": "chenggong",
+						"name": "呈贡"
+					}, {
+						"id": 371,
+						"spell": "chengkou",
+						"name": "城口"
+					}, {
+						"id": 372,
+						"spell": "chengwu",
+						"name": "成武"
+					}, {
+						"id": 373,
+						"spell": "chiping",
+						"name": "茌平"
+					}, {
+						"id": 374,
+						"spell": "chongren",
+						"name": "崇仁"
+					}, {
+						"id": 375,
+						"spell": "chongyi",
+						"name": "崇义"
+					}, {
+						"id": 376,
+						"spell": "chongzhou",
+						"name": "崇州"
+					}, {
+						"id": 377,
+						"spell": "chunan",
+						"name": "淳安"
+					}, {
+						"id": 378,
+						"spell": "cixi",
+						"name": "慈溪"
+					}, {
+						"id": 379,
+						"spell": "conghua",
+						"name": "从化"
+					}, {
+						"id": 380,
+						"spell": "congyang",
+						"name": "枞阳"
+					}],
+					"K": [{
+						"id": 25,
+						"spell": "kunming",
+						"name": "昆明"
+					}, {
+						"id": 174,
+						"spell": "kaifeng",
+						"name": "开封"
+					}, {
+						"id": 175,
+						"spell": "kashidi",
+						"name": "喀什地"
+					}, {
+						"id": 176,
+						"spell": "kelamayi",
+						"name": "克拉玛依"
+					}, {
+						"id": 177,
+						"spell": "kezile",
+						"name": "克孜勒"
+					}, {
+						"id": 555,
+						"spell": "kaihua",
+						"name": "开化"
+					}, {
+						"id": 556,
+						"spell": "kaiping",
+						"name": "开平"
+					}, {
+						"id": 557,
+						"spell": "kaixian",
+						"name": "开县"
+					}, {
+						"id": 558,
+						"spell": "kaiyang",
+						"name": "开阳"
+					}, {
+						"id": 559,
+						"spell": "kangping",
+						"name": "康平"
+					}, {
+						"id": 560,
+						"spell": "kenli",
+						"name": "垦利"
+					}, {
+						"id": 561,
+						"spell": "kunshan",
+						"name": "昆山"
+					}],
+					"M": [{
+						"id": 203,
+						"spell": "maanshan",
+						"name": "马鞍山"
+					}, {
+						"id": 204,
+						"spell": "maoming",
+						"name": "茂名"
+					}],
+					"S": [{
+						"id": 3,
+						"spell": "shanghai",
+						"name": "上海"
+					}, {
+						"id": 36,
+						"spell": "shenyang",
+						"name": "沈阳"
+					}, {
+						"id": 37,
+						"spell": "shenzhen",
+						"name": "深圳"
+					}, {
+						"id": 38,
+						"spell": "shijiazhuang",
+						"name": "石家庄"
+					}, {
+						"id": 39,
+						"spell": "suzhou",
+						"name": "苏州"
+					}, {
+						"id": 237,
+						"spell": "sanmenxia",
+						"name": "三门峡"
+					}, {
+						"id": 238,
+						"spell": "sanming",
+						"name": "三明"
+					}, {
+						"id": 239,
+						"spell": "sanya",
+						"name": "三亚"
+					}, {
+						"id": 240,
+						"spell": "shangluo",
+						"name": "商洛"
+					}, {
+						"id": 241,
+						"spell": "shangqiu",
+						"name": "商丘"
+					}, {
+						"id": 242,
+						"spell": "shangrao",
+						"name": "上饶"
+					}, {
+						"id": 243,
+						"spell": "shannan",
+						"name": "山南"
+					}, {
+						"id": 244,
+						"spell": "shantou",
+						"name": "汕头"
+					}, {
+						"id": 245,
+						"spell": "shanwei",
+						"name": "汕尾"
+					}, {
+						"id": 246,
+						"spell": "shaoguan",
+						"name": "韶关"
+					}, {
+						"id": 247,
+						"spell": "shaoxing",
+						"name": "绍兴"
+					}, {
+						"id": 248,
+						"spell": "shaoyang",
+						"name": "邵阳"
+					}, {
+						"id": 249,
+						"spell": "shiyan",
+						"name": "十堰"
+					}, {
+						"id": 250,
+						"spell": "shizuishan",
+						"name": "石嘴山"
+					}, {
+						"id": 251,
+						"spell": "shuangyashan",
+						"name": "双鸭山"
+					}, {
+						"id": 252,
+						"spell": "shuozhou",
+						"name": "朔州"
+					}, {
+						"id": 253,
+						"spell": "siping",
+						"name": "四平"
+					}, {
+						"id": 254,
+						"spell": "songyuan",
+						"name": "松原"
+					}, {
+						"id": 255,
+						"spell": "suihua",
+						"name": "绥化"
+					}, {
+						"id": 256,
+						"spell": "suining",
+						"name": "遂宁"
+					}],
+					"T": [{
+						"id": 4,
+						"spell": "tianjin",
+						"name": "天津"
+					}, {
+						"id": 40,
+						"spell": "taizhou",
+						"name": "台州"
+					}, {
+						"id": 41,
+						"spell": "tangshan",
+						"name": "唐山"
+					}, {
+						"id": 260,
+						"spell": "tachengdi",
+						"name": "塔城地"
+					}, {
+						"id": 261,
+						"spell": "taian",
+						"name": "泰安"
+					}, {
+						"id": 262,
+						"spell": "taiyuan",
+						"name": "太原"
+					}, {
+						"id": 263,
+						"spell": "taizhou",
+						"name": "泰州"
+					}, {
+						"id": 264,
+						"spell": "tianshui",
+						"name": "天水"
+					}, {
+						"id": 265,
+						"spell": "tieling",
+						"name": "铁岭"
+					}, {
+						"id": 266,
+						"spell": "tongchuan",
+						"name": "铜川"
+					}, {
+						"id": 267,
+						"spell": "tonghua",
+						"name": "通化"
+					}, {
+						"id": 268,
+						"spell": "tongliao",
+						"name": "通辽"
+					}],
+					"X": [{
+						"id": 46,
+						"spell": "xiamen",
+						"name": "厦门"
+					}, {
+						"id": 47,
+						"spell": "xian",
+						"name": "西安"
+					}, {
+						"id": 48,
+						"spell": "xuchang",
+						"name": "许昌"
+					}],
+					"Y": [{
+						"id": 50,
+						"spell": "yangzhou",
+						"name": "扬州"
+					}, {
+						"id": 51,
+						"spell": "yantai",
+						"name": "烟台"
+					}, {
+						"id": 298,
+						"spell": "yaan",
+						"name": "雅安"
+					}, {
+						"id": 299,
+						"spell": "yanan",
+						"name": "延安"
+					}],
+					"Z": [{
+						"id": 52,
+						"spell": "zhangzhou",
+						"name": "漳州"
+					}, {
+						"id": 53,
+						"spell": "zhengzhou",
+						"name": "郑州"
+					}, {
+						"id": 54,
+						"spell": "zhongshan",
+						"name": "中山"
+					}, {
+						"id": 55,
+						"spell": "zhuhai",
+						"name": "珠海"
+					}, {
+						"id": 321,
+						"spell": "zaozhuang",
+						"name": "枣庄"
+					}, {
+						"id": 322,
+						"spell": "zhengzhou",
+						"name": "郑州"
+					}, {
+						"id": 323,
+						"spell": "zhongshan",
+						"name": "中山"
+					}, {
+						"id": 324,
+						"spell": "zhuhai",
+						"name": "珠海"
+					}, {
+						"id": 325,
+						"spell": "zaozhuang",
+						"name": "枣庄"
+					}, {
+						"id": 326,
+						"spell": "zhengzhou",
+						"name": "郑州"
+					}, {
+						"id": 254,
+						"spell": "zhongshan",
+						"name": "中山"
+					}, {
+						"id": 355,
+						"spell": "zhuhai",
+						"name": "珠海"
+					}, {
+						"id": 121,
+						"spell": "zaozhuang",
+						"name": "枣庄"
+					}, {
+						"id": 453,
+						"spell": "zhengzhou",
+						"name": "郑州"
+					}, {
+						"id": 554,
+						"spell": "zhongshan",
+						"name": "中山"
+					}, {
+						"id": 255,
+						"spell": "zhuhai",
+						"name": "珠海"
+					}, {
+						"id": 368,
+						"spell": "zaozhuang",
+						"name": "枣庄"
+					}, {
+						"id": 369,
+						"spell": "zhengzhou",
+						"name": "郑州"
+					}, {
+						"id": 754,
+						"spell": "zhongshan",
+						"name": "中山"
+					}, {
+						"id": 655,
+						"spell": "zhuhai",
+						"name": "珠海"
+					}, {
+						"id": 668,
+						"spell": "zaozhuang",
+						"name": "枣庄"
+					}]
+					
+				}
+			
+			}
+		},
+		methods : {
+			paramClick (e) {
+				console.log(e)
+			}
+		}
+	}
+</script>
+
+<style>
+
+</style>

+ 120 - 0
components/phone-directory/phone-alphabet.vue

@@ -0,0 +1,120 @@
+<template>
+	<view class="alphabet">
+		<view class="alphabet-item" 
+		v-for="(item, key) of phones" 
+		:key="key"
+		:data-key="key"
+		:class="activeClass == key ? 'active' : ''"
+		@touchstart="handleTouchStart"
+		@touchmove = "handleTouchMove"
+		@touchend="handleTouchEnd"
+		@touchcancel="handleTouchCancel" 
+		>
+			{{key}}
+		</view>
+		<view class="alphabet-alert" v-if="touchmove">
+			{{activeClass}}
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		name:"phone-alphabet",
+		props:{
+			phones:Object,
+			phoneListIndex:String
+		},
+		data() {
+			return {
+				touchStatus:false,
+				timer:null,
+				activeClass:'A', 
+				phonesArr:[],
+				touchmove:false
+			};
+		},
+		mounted () {
+			let phonesArr = Object.keys(this.phones)
+			this.phonesArr = phonesArr
+		},
+		watch:{
+			phoneListIndex (index) {
+				this.activeClass = index
+			}
+		},
+		methods:{
+			handleTouchStart (e) {
+				this.$emit('reset',false)
+				let key = e.target.dataset.key
+				this.activeClass = key
+				this.$emit('change',e.target.dataset.key)
+				this.touchStatus = true
+			},
+			handleTouchMove (e) {
+				this.$emit('scrollAnimationOFF',false)
+				if(this.touchStatus){
+					this.touchmove = true
+					if(this.timer){
+						clearTimeout(this.timer)
+					}
+					this.timer = setTimeout(()=>{
+						const touchY = e.touches[0].clientY - 54
+						const index = Math.floor(touchY / 20)
+						if(index >= 0 && index < this.phonesArr.length){
+							this.activeClass = this.phonesArr[index]
+							this.$emit('change',this.phonesArr[index])
+						} 
+					},16)
+				}
+			},
+			handleTouchEnd (e) {
+				this.$emit('scrollAnimationOFF',true)
+				this.touchStatus = false
+				this.touchmove = false
+			}
+		}
+	}
+</script>
+
+<style>
+.alphabet>.active,.hover{
+	color: #fff;
+	background-color: #0190a0;
+	border-radius: 40upx;
+}
+
+.alphabet{
+	display: flex;
+	flex-direction: column;
+	text-align: center;
+	z-index: 10;
+	padding: 10upx 10upx 0 10upx;
+}
+
+.alphabet-item{
+	width: 40upx;
+	font-size:24upx; 
+	height: 40upx;
+	line-height: 40upx;
+	
+}
+
+.alphabet-alert{
+	position: absolute;
+	z-index: 20;
+	width: 160upx;
+	height: 160upx;
+	left: 50%;
+	top: 50%;
+	margin-left: -80upx;
+	margin-top: -80upx;
+	border-radius: 80upx;
+	text-align: center;
+	line-height: 160upx;
+	font-size: 70upx;
+	color: #fff;
+	background-color: rgba(0, 0, 0, 0.5);
+}
+
+</style>

+ 126 - 0
components/phone-directory/phone-directory.vue

@@ -0,0 +1,126 @@
+<template>
+	<view class="phone-main" :style="{height: winHeight + 'px'}">
+		<view class="phone-main-search">
+			<navigator :url="'phone-search?phones=' + phonesEscape" hover-class="none">
+				<input disabled="false" class="phone-main-input" type="text" placeholder="请输入要搜索的联系人"/>
+			</navigator>
+		</view>
+		<view class="phoneDirectory">
+			<phone-list 
+			:phones="phones" 
+			:letter="letter"
+			:scrollAnimationOFF="scrollAnimationOFF" 
+			@change="handlePhoneListIndex"
+			@reset="handleReset"
+			@handleClick="handleClick"
+			>
+			</phone-list>
+			<phone-alphabet 
+			:phones="phones"
+			:phoneListIndex="phoneListIndex"
+			@change="handleDatasetKey" 
+			@scrollAnimationOFF="handleScrollAnimationOFF"
+			@reset="handleReset"
+			>
+			</phone-alphabet>
+		</view>
+	</view>
+</template>
+
+<script>
+	import phoneList from './phone-list.vue'
+	import phoneAlphabet from './phone-alphabet.vue'
+	
+	export default {
+		name:"phone-directory",
+		components:{
+			phoneList,
+			phoneAlphabet
+		},
+		props:{
+			phones:Object,
+			default:false
+		},
+		data () {
+			return {
+				winHeight:0,
+				letter : 'A',
+				scrollAnimationOFF:true,
+				phoneListIndex:'A',
+				reset:true
+			}
+		},
+		computed:{
+			phonesEscape () {
+				return escape(JSON.stringify(this.phones))
+			}
+		},
+		mounted () {
+			let windowHeight = uni.getSystemInfoSync().windowHeight
+			
+			// #ifndef APP-PLUS
+			this.winHeight = windowHeight
+			//#endif
+			
+			//#ifdef APP-PLUS
+			this.winHeight = windowHeight - 56
+			//#endif
+			
+ 			if(!this.phones){
+				uni.showToast({
+					title: '没有数据',
+					icon:"none",
+					mask: false,
+					duration: 1500
+				})
+			}
+		},
+		methods:{
+			handleClick (e) {
+				this.$emit('paramClick',e)
+			},
+			handleDatasetKey (val) {
+				this.letter = val
+			},
+			handleScrollAnimationOFF (val) {
+				this.scrollAnimationOFF = val
+			},
+			handlePhoneListIndex(val){
+				if(this.reset){
+					this.phoneListIndex = val
+				}
+			},
+			handleReset (val){
+				if(val){
+					this.letter = ''
+				}
+				this.reset = val
+			}
+			
+		}
+	}
+</script>
+
+<style>
+.phone-main{
+	display: flex;
+	flex-direction: column;
+	overflow: hidden;
+}
+.phoneDirectory{
+	display: flex;
+	flex-direction: row;
+}
+.phone-main-search{
+	background-color: #fff;
+	padding: 10upx 20upx;
+	border-bottom: 1px solid #e5e5e5;
+}
+
+.phone-main-input{
+	font-size:28upx;
+	border: 1px solid #e5e5e5;
+	border-radius: 3px;
+	padding: 10upx 20upx 10upx 20upx;
+}
+</style>

+ 152 - 0
components/phone-directory/phone-list.vue

@@ -0,0 +1,152 @@
+<template>
+	<view>
+		<scroll-view class="scroll-list"
+		:scroll-top="1"
+		scroll-y="true"
+		:scroll-with-animation="scrollAnimationOFF" 
+		:scroll-into-view="scrollViewId" 
+		:style="{height:winHeight + 'px'}" 
+		@scroll="handleScroll">
+			<view class="phone-list">
+				<view class="list-item" 
+				v-for="(item, key) of phones" 
+				:key="key" 
+				:id="key">
+					<view class="list-item-title">{{key}}</view>
+					<view class="list-item-phone" 
+					@click="handleClick"
+					hover-class="commonly-hover" 
+					:hover-start-time="20" 
+					:hover-stay-time="70" 
+					v-for="innerItem in item"
+					:key="innerItem.id"
+					:data-name="innerItem.name"
+					:data-id="innerItem.id"
+					:data-phoneNumber="innerItem.phoneNumber"
+					>
+					{{innerItem.name}}
+					</view>
+				</view>
+			</view>
+		</scroll-view>
+	</view>
+</template>
+
+<script>
+	export default {
+		name:"phone-list",
+		props:{
+			phones:Object,
+			letter:String,
+			scrollAnimationOFF:Boolean
+		},
+		data () {
+			return {
+				winHeight:0,
+				scrollTop:0,
+				letterDetails:[],
+				timer:null
+			}
+		},
+		computed:{
+			scrollViewId () {
+				return this.letter
+			}
+		},
+		mounted(){
+			// #ifndef APP-PLUS
+			this.winHeight = uni.getSystemInfoSync().windowHeight - 49.50
+			//#endif
+			
+			//#ifdef APP-PLUS
+			this.winHeight = uni.getSystemInfoSync().windowHeight - 100
+			//#endif
+
+		},
+		methods:{
+			handleClick (e) {
+				this.$emit('handleClick',e.target.dataset)
+			},
+			handleScroll (e){
+				if(this.letterDetails.length === 0){
+					let view = uni.createSelectorQuery().selectAll('.list-item')
+					view.boundingClientRect(data=>{
+						let top = data[0].top
+						data.forEach((item,index)=>{
+							item.top = item.top - top
+							item.bottom  = item.bottom - top
+							this.letterDetails.push({
+								id:item.id,
+								top:item.top,
+								bottom:item.bottom
+							})
+						})
+					}).exec()	
+				}
+				
+				const scrollTop = e.detail.scrollTop
+				this.letterDetails.some((item,index)=>{
+					if(scrollTop>=item.top && scrollTop <= item.bottom - 5){
+						this.$emit('change',item.id)
+						this.$emit('reset',true)
+						return true
+					}
+				})
+			}
+		}
+			
+	}
+</script>
+
+<style>
+	
+	.commonly-hover{
+		background-color: #eee;
+	}
+	
+	.scroll-list{
+		flex: 1;
+		height: 100vh;
+		overflow-y: hidden;
+	}
+
+	.phone-list{
+		display: flex;
+		background-color: #fff;
+		flex-direction:column;
+		position:relative;
+		width: 100%;
+	}
+	
+	.list-item {
+		width: 100%;
+		display: flex;
+		align-items: center;
+		flex-wrap:wrap;
+		height: 92upx;
+		background-color: #fff;
+		height: 100%;
+		
+	}
+	
+	.list-item >.list-item-phone{
+		font-weight: normal;
+	}
+	
+	.list-item-title{
+		background-color: #eee;
+	}
+	
+	.list-item-title,.list-item-phone{
+		width: 100%;
+		height: 92upx;
+		line-height: 92upx;
+		font-size: 32upx;
+		font-weight: bold;
+		padding: 0 20upx;
+		border-bottom: 1px solid #e5e5e5;
+	}
+	
+	
+
+</style>

+ 114 - 0
components/phone-directory/phone-search-list.vue

@@ -0,0 +1,114 @@
+<template>
+	<view>
+		<view class="search">
+			<input 
+			@input="handleInput"
+			class="search-input" 
+			type="text" 
+			focus  
+			placeholder="请输入要搜索的联系人"
+			/>
+		</view>
+		<view class="search-main" v-if="keyword">
+			<view class="search-main-errtitle" v-if="hasNoData">无搜索结果</view>
+			<view class="search-main-title"
+			hover-class="hover" 
+			@click="handleClick"
+			:hover-start-time="20" 
+			:hover-stay-time="70" 
+			v-for="item of list" 
+			:key="item.id"
+			:data-name="item.name"
+			:data-id="item.id"
+			:data-phoneNumber="item.phoneNumber">
+				{{item.name}}
+			</view>
+		</view>
+	</view>		
+</template>
+
+<script>
+	export default {
+		name:"phone-search-list",
+		props:{
+			phones:Object
+		},
+		data() {
+			return {
+				keyword:'',
+				list:[],
+				timer:null
+			}
+		},
+		computed:{
+			hasNoData () {
+				return !this.list.length
+			}
+		},
+		watch:{
+			keyword () {
+				if(this.timer) {
+					clearTimeout(this.timer)
+				}
+				if(!this.keyword){
+					this.list = []
+					return
+				}
+				this.timer = setTimeout(()=>{
+					const result = []
+					for (let i in this.phones){
+						this.phones[i].forEach((item)=>{
+							if(item.spell.indexOf(this.keyword) > -1||item.name.indexOf(this.keyword) > -1){
+								result.push(item)
+							}
+						})
+					}
+					this.list = result
+				},100)
+			}
+		},
+		methods:{
+			handleInput (e) {
+				this.keyword = e.detail.value
+			},
+			handleClick (e) {
+				this.$emit('paramClick',e.target.dataset)
+			}
+		}
+	}
+</script>
+
+<style>
+	.hover{
+		background-color: #eee;
+	}
+	.search{
+		background-color: #fff;
+		padding: 10upx 20upx;
+		border-bottom: 1px solid #e5e5e5;
+	}
+
+	.search-input{
+		font-size:28upx;
+		border: 1px solid #e5e5e5;
+		border-radius: 3px;
+		padding: 10upx 20upx 10upx 20upx;
+	}
+	
+	.search-main{
+		height: 100%;
+		padding-bottom: 20upx;
+		background-color:#fff;
+		overflow: hidden;
+	}
+	
+	.search-main-errtitle,.search-main-title{
+		width: 100%;
+		height: 92upx;
+		line-height: 92upx;
+		font-size: 32upx;
+		padding: 0 20upx;
+		border-bottom: 1px solid #e5e5e5;
+	}
+
+</style>

+ 278 - 0
components/ren-dropdown-filter/ren-dropdown-filter.vue

@@ -0,0 +1,278 @@
+<template>
+	<view class="filter-wrapper" :style="{ top: top,'border-top':border?'1rpx solid #f2f2f2':'none' }"
+		@touchmove.stop.prevent="discard">
+		<view class="inner-wrapper">
+			<view class="mask" :class="showMask ? 'show' : 'hide'" @tap="tapMask"></view>
+			<view class="navs">
+				<view class="c-flex-align c-flex-center"
+					:class="{ 'c-flex-center': index > 0, actNav: index === actNav }" v-for="(item, index) in navData"
+					:key="index" @click="navClick(index)">
+					<view v-for="(child, childx) in item" :key="childx" v-if="child.select">{{ child.label }}</view>
+					<image src="https://i.loli.net/2020/07/15/QsHxlr1gbSImvWt.png" mode="" class="icon-triangle"
+						v-if="index === actNav"></image>
+					<image src="https://i.loli.net/2020/07/15/xjVSvzWcH9NO7al.png" mode="" class="icon-triangle" v-else>
+					</image>
+				</view>
+			</view>
+			<scroll-view scroll-y="true" class="popup" :class="popupShow ? 'popupShow' : ''">
+				<view class="item-opt c-flex-align" :class="item.select ? 'actOpt' : ''"
+					v-for="(item, index) in navData[actNav]" :key="index" @click="handleOpt(index)">
+					{{ item.label }}
+				</view>
+			</scroll-view>
+		</view>
+	</view>
+</template>
+
+<script>
+	// import { getCurDateTime } from '@/libs/utils.js';
+	export default {
+		props: {
+			height: {
+				type: Number,
+				default: 100
+			},
+			top: {
+				type: String,
+				default: 'calc(var(--window-statsu-bar) + 44px)'
+			},
+			border: {
+				type: Boolean,
+				default: false
+			},
+			filterData: {
+				//必填
+				type: Array,
+				default: () => {
+					return [];
+				}
+				// default: () => {
+				//     return [
+				//         [{ text: '全部状态', value: '' }, { text: '状态1', value: 1 }, { text: '状态2', value: 2 }, { text: '状态3', value: 3 }],
+				//         [{ text: '全部类型', value: '' }, { text: '类型1', value: 1 }, { text: '类型2', value: 2 }, { text: '类型3', value: 3 }]
+				//     ];
+				// }
+			},
+			defaultIndex: {
+				//默认选中条件索引,超出一类时必填
+				type: Array,
+				default: () => {
+					return [0];
+				}
+			}
+		},
+		data() {
+			return {
+				navData: [],
+				popupShow: false,
+				showMask: false,
+				actNav: null,
+				selDate: '选择日期',
+				selIndex: [] //选中条件索引
+			};
+		},
+		created() {
+			this.navData = this.filterData;
+			this.selIndex = this.defaultIndex;
+			this.keepStatus();
+		},
+		mounted() {
+			// this.selDate = getCurDateTime().formatDate;
+		},
+		methods: {
+			keepStatus() {
+				this.navData.forEach(itemnavData => {
+					itemnavData.map(child => {
+						child.select = false;
+					});
+					return itemnavData;
+				});
+				for (let i = 0; i < this.selIndex.length; i++) {
+					let selindex = this.selIndex[i];
+					this.navData[i][selindex].select = true;
+				}
+			},
+			navClick(index) {
+				console.log(index)
+				if (index === this.actNav) {
+					this.tapMask();
+					return;
+				}
+				this.popupShow = true;
+				this.showMask = true;
+				this.actNav = index;
+				this.$emit('dateChange', index);
+				// if (index == 0) {
+				// 	this.popupShow = false;
+				// 	this.showMask = false;
+				// 	this.actNav = index;
+				// 	this.$emit('dateChange', index);
+				// } else {
+				// 	if (index === this.actNav) {
+				// 		this.tapMask();
+				// 		return;
+				// 	}
+				// 	this.popupShow = true;
+				// 	this.showMask = true;
+				// 	this.actNav = index;
+				// 	this.$emit('dateChange', index);
+				// }
+
+			},
+			handleOpt(index) {
+				this.selIndex[this.actNav] = index;
+				this.keepStatus();
+				setTimeout(() => {
+					this.tapMask();
+				}, 100);
+				let data = [];
+				let res = this.navData.forEach(item => {
+					let sel = item.filter(child => child.select);
+					data.push(sel);
+				});
+				console.log(data);
+				this.$emit('onSelected', data);
+			},
+			dateClick() {
+				this.tapMask();
+			},
+			tapMask() {
+				this.showMask = false;
+				this.popupShow = false;
+				this.actNav = null;
+			},
+			handleDate(e) {
+				let d = e.detail.value;
+				this.selDate = d;
+				this.$emit('dateChange', d);
+			},
+			discard() {}
+		}
+	};
+</script>
+
+<style lang="scss" scoped>
+	page {
+		font-size: 28rpx;
+	}
+
+	.c-flex-align {
+		display: flex;
+		align-items: center;
+	}
+
+	.c-flex-center {
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		flex-direction: column;
+	}
+
+	.filter-wrapper {
+		position: relative;
+		// left: 0;
+		width: 750rpx;
+		z-index: 99;
+
+		// padding: 0 20rpx;
+		.inner-wrapper {
+			position: relative;
+
+			.navs {
+				position: relative;
+				height: 80rpx;
+				// padding: 0 40rpx;
+				display: flex;
+				align-items: center;
+				justify-content: space-between;
+				background-color: #f5f5f5;
+				// background-color: #ffffff;
+				// border-bottom: 2rpx solid #f5f6f9;
+				// color: #8b9aae;
+				z-index: 999;
+				box-sizing: border-box;
+
+				// border-radius: 10rpx;
+				&>view {
+					flex: 1;
+					height: 100%;
+					flex-direction: row;
+					z-index: 999;
+				}
+
+				.date {
+					justify-content: flex-end;
+				}
+
+				.actNav {
+					color: #6696FF;
+					font-weight: bold;
+				}
+			}
+
+			.mask {
+				z-index: 666;
+				position: fixed;
+				top: 0;
+				left: 0;
+				right: 0;
+				bottom: 0;
+				background-color: rgba(0, 0, 0, 0);
+				transition: background-color 0.15s linear;
+
+				&.show {
+					background-color: rgba(0, 0, 0, 0.4);
+				}
+
+				&.hide {
+					display: none;
+				}
+			}
+
+			.popup {
+				position: relative;
+				max-height: 500rpx;
+				background-color: #ffffff;
+				border-bottom-left-radius: 20rpx;
+				border-bottom-right-radius: 20rpx;
+				overflow: scroll;
+				z-index: 999;
+				transition: all 2s linear;
+				opacity: 0;
+				display: none;
+
+				.item-opt {
+					height: 100rpx;
+					padding: 0 40rpx;
+					color: #000000;
+					border-bottom: 2rpx solid #E5E5E5;
+				}
+
+				.actOpt {
+					color: #6696FF;
+					font-weight: bold;
+					position: relative;
+
+					&::after {
+						content: '✓';
+						font-weight: bold;
+						font-size: 36rpx;
+						position: absolute;
+						right: 40rpx;
+					}
+				}
+			}
+
+			.popupShow {
+				display: block;
+				opacity: 1;
+			}
+		}
+
+		.icon-triangle {
+			width: 16rpx;
+			height: 16rpx;
+			margin-left: 10rpx;
+		}
+	}
+</style>

+ 260 - 0
components/ren-dropdown-filter/ren-dropdown-filters.vue

@@ -0,0 +1,260 @@
+<template>
+	<view class="filter-wrapper"
+		:style="{ top: top,'border-top':border?'1rpx solid #f2f2f2':'none' }"
+		@touchmove.stop.prevent="discard">
+		<view class="inner-wrapper">
+			<view class="mask" :class="showMask ? 'show' : 'hide'" @tap="tapMask"></view>
+			<view class="navs">
+				<view class="c-flex-align c-flex-center" :class="{ 'c-flex-center': index > 0, actNav: index === actNav }"
+					v-for="(item, index) in navData" :key="index" @click="navClick(index)">
+					<view v-for="(child, childx) in item" :key="childx" v-if="child.select">{{ child.label }}</view>
+					<image src="https://i.loli.net/2020/07/15/QsHxlr1gbSImvWt.png" mode="" class="icon-triangle"
+						v-if="index === actNav"></image>
+					<image src="https://i.loli.net/2020/07/15/xjVSvzWcH9NO7al.png" mode="" class="icon-triangle" v-else>
+					</image>
+				</view>
+			</view>
+			<scroll-view scroll-y="true" class="popup" :class="popupShow ? 'popupShow' : ''">
+				<view class="item-opt c-flex-align" :class="item.select ? 'actOpt' : ''"
+					v-for="(item, index) in navData[actNav]" :key="index" @click="handleOpt(index)">
+					{{ item.label }}
+				</view>
+			</scroll-view>
+		</view>
+	</view>
+</template>
+
+<script>
+	// import { getCurDateTime } from '@/libs/utils.js';
+	export default {
+		props: {
+			height: {
+				type: Number,
+				default: 100
+			},
+			top: {
+				type: String,
+				default: 'calc(var(--window-statsu-bar) + 44px)'
+			},
+			border: {
+				type: Boolean,
+				default: false
+			},
+			filterData: {
+				//必填
+				type: Array,
+				default: () => {
+					return [];
+				}
+				// default: () => {
+				//     return [
+				//         [{ text: '全部状态', value: '' }, { text: '状态1', value: 1 }, { text: '状态2', value: 2 }, { text: '状态3', value: 3 }],
+				//         [{ text: '全部类型', value: '' }, { text: '类型1', value: 1 }, { text: '类型2', value: 2 }, { text: '类型3', value: 3 }]
+				//     ];
+				// }
+			},
+			defaultIndex: {
+				//默认选中条件索引,超出一类时必填
+				type: Array,
+				default: () => {
+					return [0];
+				}
+			}
+		},
+		data() {
+			return {
+				navData: [],
+				popupShow: false,
+				showMask: false,
+				actNav: null,
+				selDate: '选择日期',
+				selIndex: [] //选中条件索引
+			};
+		},
+		created() {
+			this.navData = this.filterData;
+			this.selIndex = this.defaultIndex;
+			this.keepStatus();
+		},
+		mounted() {
+			// this.selDate = getCurDateTime().formatDate;
+		},
+		methods: {
+			keepStatus() {
+				console.log(this.navData)
+				this.navData.forEach(itemnavData => {
+					itemnavData.map(child => {
+						child.select = false;
+					});
+					return itemnavData;
+				});
+				for (let i = 0; i < this.selIndex.length; i++) {
+					let selindex = this.selIndex[i];
+					this.navData[i][selindex].select = true;
+				}
+			},
+			navClick(index) {
+				if (index === this.actNav) {
+					this.tapMask();
+					return;
+				}
+				this.popupShow = true;
+				this.showMask = true;
+				this.actNav = index;
+			},
+			handleOpt(index) {
+				this.selIndex[this.actNav] = index;
+				this.keepStatus();
+				setTimeout(() => {
+					this.tapMask();
+				}, 100);
+				let data = [];
+				let res = this.navData.forEach(item => {
+					let sel = item.filter(child => child.select);
+					data.push(sel);
+				});
+				console.log(data);
+				this.$emit('onSelected', data);
+			},
+			dateClick() {
+				this.tapMask();
+			},
+			tapMask() {
+				this.showMask = false;
+				this.popupShow = false;
+				this.actNav = null;
+			},
+			handleDate(e) {
+				let d = e.detail.value;
+				this.selDate = d;
+				this.$emit('dateChange', d);
+			},
+			discard() {}
+		}
+	};
+</script>
+
+<style lang="scss" scoped>
+	page {
+		font-size: 28rpx;
+	}
+
+	.c-flex-align {
+		display: flex;
+		align-items: center;
+	}
+
+	.c-flex-center {
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		flex-direction: column;
+	}
+
+	.filter-wrapper {
+		position: relative;
+		// left: 0;
+		width: 750rpx;
+		z-index: 99;
+
+		// padding: 0 20rpx;
+		.inner-wrapper {
+			position: relative;
+
+			.navs {
+				position: relative;
+				height: 80rpx;
+				// padding: 0 40rpx;
+				display: flex;
+				align-items: center;
+				justify-content: space-between;
+				background-color: #ffffff;
+				// border-bottom: 2rpx solid #f5f6f9;
+				// color: #8b9aae;
+				z-index: 999;
+				box-sizing: border-box;
+
+				// border-radius: 10rpx;
+				&>view {
+					flex: 1;
+					height: 100%;
+					flex-direction: row;
+					z-index: 999;
+				}
+
+				.date {
+					justify-content: flex-end;
+				}
+
+				.actNav {
+					color: #6696FF;
+					font-weight: bold;
+				}
+			}
+
+			.mask {
+				z-index: 666;
+				position: fixed;
+				top: 0;
+				left: 0;
+				right: 0;
+				bottom: 0;
+				background-color: rgba(0, 0, 0, 0);
+				transition: background-color 0.15s linear;
+
+				&.show {
+					background-color: rgba(0, 0, 0, 0.4);
+				}
+
+				&.hide {
+					display: none;
+				}
+			}
+
+			.popup {
+				position: relative;
+				max-height: 500rpx;
+				background-color: #ffffff;
+				border-bottom-left-radius: 20rpx;
+				border-bottom-right-radius: 20rpx;
+				overflow: scroll;
+				z-index: 999;
+				transition: all 2s linear;
+				opacity: 0;
+				display: none;
+
+				.item-opt {
+					height: 100rpx;
+					padding: 0 40rpx;
+					color: #000000;
+					border-bottom: 2rpx solid #E5E5E5;
+				}
+
+				.actOpt {
+					color: #6696FF;
+					font-weight: bold;
+					position: relative;
+
+					&::after {
+						content: '✓';
+						font-weight: bold;
+						font-size: 36rpx;
+						position: absolute;
+						right: 40rpx;
+					}
+				}
+			}
+
+			.popupShow {
+				display: block;
+				opacity: 1;
+			}
+		}
+
+		.icon-triangle {
+			width: 16rpx;
+			height: 16rpx;
+			margin-left: 10rpx;
+		}
+	}
+</style>

+ 274 - 0
components/ren-dropdown-filters/ren-dropdown-filter.vue

@@ -0,0 +1,274 @@
+<template>
+	<view class="filter-wrapper" :style="{ top: top,'border-top':border?'1rpx solid #f2f2f2':'none' }"
+		@touchmove.stop.prevent="discard">
+		<view class="inner-wrapper">
+			<view class="mask" :class="showMask ? 'show' : 'hide'" @tap="tapMask"></view>
+			<view class="navs">
+				<view class="c-flex-align c-flex-center"
+					:class="{ 'c-flex-center': index > 0, actNav: index === actNav }" v-for="(item, index) in navData"
+					:key="index" @click="navClick(index)">
+					<view v-for="(child, childx) in item" :key="childx" v-if="child.select">{{ child.label }}</view>
+					<image src="https://i.loli.net/2020/07/15/QsHxlr1gbSImvWt.png" mode="" class="icon-triangle"
+						v-if="index === actNav"></image>
+					<image src="https://i.loli.net/2020/07/15/xjVSvzWcH9NO7al.png" mode="" class="icon-triangle" v-else>
+					</image>
+				</view>
+			</view>
+			<scroll-view scroll-y="true" class="popup" :class="popupShow ? 'popupShow' : ''">
+				<view class="item-opt c-flex-align" :class="item.select ? 'actOpt' : ''"
+					v-for="(item, index) in navData[actNav]" :key="index" @click="handleOpt(index)">
+					{{ item.label }}
+				</view>
+			</scroll-view>
+		</view>
+	</view>
+</template>
+
+<script>
+	// import { getCurDateTime } from '@/libs/utils.js';
+	export default {
+		props: {
+			height: {
+				type: Number,
+				default: 100
+			},
+			top: {
+				type: String,
+				default: 'calc(var(--window-statsu-bar) + 44px)'
+			},
+			border: {
+				type: Boolean,
+				default: false
+			},
+			filterData: {
+				//必填
+				type: Array,
+				default: () => {
+					return [];
+				}
+				// default: () => {
+				//     return [
+				//         [{ text: '全部状态', value: '' }, { text: '状态1', value: 1 }, { text: '状态2', value: 2 }, { text: '状态3', value: 3 }],
+				//         [{ text: '全部类型', value: '' }, { text: '类型1', value: 1 }, { text: '类型2', value: 2 }, { text: '类型3', value: 3 }]
+				//     ];
+				// }
+			},
+			defaultIndex: {
+				//默认选中条件索引,超出一类时必填
+				type: Array,
+				default: () => {
+					return [0];
+				}
+			}
+		},
+		data() {
+			return {
+				navData: [],
+				popupShow: false,
+				showMask: false,
+				actNav: null,
+				selDate: '选择日期',
+				selIndex: [] //选中条件索引
+			};
+		},
+		created() {
+			this.navData = this.filterData;
+			this.selIndex = this.defaultIndex;
+			this.keepStatus();
+		},
+		mounted() {
+			// this.selDate = getCurDateTime().formatDate;
+		},
+		methods: {
+			keepStatus() {
+				this.navData.forEach(itemnavData => {
+					itemnavData.map(child => {
+						child.select = false;
+					});
+					return itemnavData;
+				});
+				for (let i = 0; i < this.selIndex.length; i++) {
+					let selindex = this.selIndex[i];
+					this.navData[i][selindex].select = true;
+				}
+			},
+			navClick(index) {
+				// // console.log(index,'00000')
+				// if (index == 2) {
+				// 	this.popupShow = false;
+				// 	this.showMask = false;
+				// 	this.actNav = index;
+
+				// 	// console.log(index,'00000')
+				// } else {
+				if (index === this.actNav) {
+					this.tapMask();
+					return;
+				}
+				this.popupShow = true;
+				this.showMask = true;
+				this.actNav = index;
+				this.$emit('dateChange', index);
+				// }
+
+			},
+			handleOpt(index) {
+				// console.log(index,'00000')
+				this.selIndex[this.actNav] = index;
+				this.keepStatus();
+				setTimeout(() => {
+					this.tapMask();
+				}, 100);
+				let data = [];
+				let res = this.navData.forEach(item => {
+					let sel = item.filter(child => child.select);
+					data.push(sel);
+				});
+				// console.log(data,'ppppppppppp');
+				this.$emit('onSelected', data);
+			},
+			dateClick() {
+				this.tapMask();
+			},
+			tapMask() {
+				this.showMask = false;
+				this.popupShow = false;
+				this.actNav = null;
+			},
+			handleDate(e) {
+				// console.log(e, '===========')
+				let d = e.detail.value;
+				this.selDate = d;
+				this.$emit('dateChange', d);
+			},
+			discard() {}
+		}
+	};
+</script>
+
+<style lang="scss" scoped>
+	page {
+		font-size: 28rpx;
+	}
+
+	.c-flex-align {
+		display: flex;
+		align-items: center;
+	}
+
+	.c-flex-center {
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		flex-direction: column;
+	}
+
+	.filter-wrapper {
+		position: relative;
+		// left: 0;
+		width: 100%;
+		z-index: 99;
+
+		// padding: 0 20rpx;
+		.inner-wrapper {
+			position: relative;
+
+			.navs {
+
+				position: relative;
+				height: 80rpx;
+				// padding: 0 40rpx;
+				display: flex;
+				align-items: center;
+				justify-content: space-between;
+				background-color: #F2F2F7;
+				color: #1E1F31; // border-bottom: 2rpx solid #f5f6f9;
+				z-index: 999;
+				box-sizing: border-box;
+
+				// border-radius: 10rpx;
+				&>view {
+					flex: 1;
+					height: 100%;
+					flex-direction: row;
+					z-index: 999;
+				}
+
+				.date {
+					justify-content: flex-end;
+				}
+
+				.actNav {
+					color: #2B7DE2;
+					font-weight: bold;
+				}
+			}
+
+			.mask {
+				z-index: 666;
+				position: fixed;
+				top: 0;
+				left: 0;
+				right: 0;
+				bottom: 0;
+				background-color: rgba(0, 0, 0, 0);
+				transition: background-color 0.15s linear;
+
+				&.show {
+					background-color: rgba(0, 0, 0, 0.4);
+				}
+
+				&.hide {
+					display: none;
+				}
+			}
+
+			.popup {
+				position: relative;
+				max-height: 500rpx;
+				background-color: #F4F6F9;
+				// background-color: #111224;
+
+				border-bottom-left-radius: 20rpx;
+				border-bottom-right-radius: 20rpx;
+				overflow: scroll;
+				z-index: 999;
+				transition: all 2s linear;
+				opacity: 0;
+				display: none;
+
+				.item-opt {
+					height: 100rpx;
+					padding: 0 40rpx;
+					color: #8b9aae;
+					border-bottom: 2rpx solid #f5f5f5
+				}
+
+				.actOpt {
+					color: #557EFD;
+					font-weight: bold;
+					position: relative;
+
+					&::after {
+						content: '✓';
+						font-weight: bold;
+						font-size: 36rpx;
+						position: absolute;
+						right: 40rpx;
+					}
+				}
+			}
+
+			.popupShow {
+				display: block;
+				opacity: 1;
+			}
+		}
+
+		.icon-triangle {
+			width: 16rpx;
+			height: 16rpx;
+			margin-left: 10rpx;
+		}
+	}
+</style>

+ 131 - 0
components/smh-timer/smh-timer.vue

@@ -0,0 +1,131 @@
+<template>
+	<view class="count_down">
+		<text class="tits">{{minutes1}}</text>
+		<text class="tits">{{minutes}}</text>
+		<text class="tits">:</text>
+		<text class="tits">{{second1}}</text>
+		<text class="tits">{{second}}</text>
+	</view>
+</template>
+
+<script>
+	export default {
+		name: "timer",
+		props: {
+			auto: {
+				type: Boolean,
+				default: true
+			}
+		},
+		watch: {
+			Number: function(val) {
+				this.countDown1 = this.Number
+				this.countDown(this.Number)
+				this.$emit('timing', this.Number)
+			}
+		},
+		created() {
+			if (this.auto) {
+				this.interval = setInterval(() => {
+					this.Number++
+				}, 1000)
+			}
+
+		},
+		data() {
+			return {
+				Number: 0,
+				minutes: 0,
+				minutes1: 0,
+				second: 0,
+				second1: 0,
+				countDown1: 0,
+				interval: null
+			};
+		},
+		methods: {
+			reset() {
+				clearInterval(this.interval)
+				this.Number = 0
+				this.minutes = 0
+				this.minutes1 = 0
+				this.second = 0
+				this.second1 = 0
+				this.countDown1 = 0
+				this.interval = setInterval(() => {
+					this.Number++
+				}, 1000)
+			},
+			start() {
+				this.interval = setInterval(() => {
+					this.Number++
+				}, 1000)
+			},
+			clear() {
+				clearInterval(this.interval)
+			},
+			countDown(countDown) {
+				if (countDown > 59) {
+					let d = parseInt(countDown / 60)
+					let minute = d.toString().split('')
+					if (minute.length == 1) {
+						this.minutes = minute[0]
+						this.minutes1 = 0
+					} else {
+						this.minutes1 = minute[0]
+						this.minutes = minute[1]
+					}
+
+					let dd = countDown % 60
+					let numbers = dd.toString().split('')
+					if (numbers.length == 1) {
+						this.second1 = 0
+						this.second = numbers[0]
+					} else {
+						this.second1 = numbers[0]
+						this.second = numbers[1]
+					}
+				} else {
+					this.minutes = 0
+					this.minutes1 = 0
+					let numbers = countDown.toString().split('')
+					if (numbers.length == 1) {
+						this.second = numbers[0]
+						this.second1 = 0
+					} else {
+						this.second1 = numbers[0]
+						this.second = numbers[1]
+					}
+
+				}
+			},
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	.count_down {
+		display: flex;
+		align-items: center;
+		// margin-bottom: 60rpx;
+		flex-direction: row;
+		justify-content: center;
+		margin-left:5rpx ;
+		
+
+		
+	}
+	.tits {
+		display: block;
+		color:#FFFFFF;
+		margin-right: 6rpx;
+		font-size: 32rpx;
+		// font-weight: bold;
+	}
+	
+	.maohao {
+		padding: 0 10rpx;
+		font-size: 30rpx;
+		font-weight: bold;
+	}
+</style>

+ 1206 - 0
components/tki-qrcode/qrcode.js

@@ -0,0 +1,1206 @@
+let QRCode = {};
+(function () {
+    /**
+     * 获取单个字符的utf8编码
+     * unicode BMP平面约65535个字符
+     * @param {num} code
+     * return {array}
+     */
+    function unicodeFormat8(code) {
+        // 1 byte
+        var c0, c1, c2;
+        if (code < 128) {
+            return [code];
+            // 2 bytes
+        } else if (code < 2048) {
+            c0 = 192 + (code >> 6);
+            c1 = 128 + (code & 63);
+            return [c0, c1];
+            // 3 bytes
+        } else {
+            c0 = 224 + (code >> 12);
+            c1 = 128 + (code >> 6 & 63);
+            c2 = 128 + (code & 63);
+            return [c0, c1, c2];
+        }
+    }
+    /**
+     * 获取字符串的utf8编码字节串
+     * @param {string} string
+     * @return {array}
+     */
+    function getUTF8Bytes(string) {
+        var utf8codes = [];
+        for (var i = 0; i < string.length; i++) {
+            var code = string.charCodeAt(i);
+            var utf8 = unicodeFormat8(code);
+            for (var j = 0; j < utf8.length; j++) {
+                utf8codes.push(utf8[j]);
+            }
+        }
+        return utf8codes;
+    }
+    /**
+     * 二维码算法实现
+     * @param {string} data              要编码的信息字符串
+     * @param {num} errorCorrectLevel 纠错等级
+     */
+    function QRCodeAlg(data, errorCorrectLevel) {
+        this.typeNumber = -1; //版本
+        this.errorCorrectLevel = errorCorrectLevel;
+        this.modules = null; //二维矩阵,存放最终结果
+        this.moduleCount = 0; //矩阵大小
+        this.dataCache = null; //数据缓存
+        this.rsBlocks = null; //版本数据信息
+        this.totalDataCount = -1; //可使用的数据量
+        this.data = data;
+        this.utf8bytes = getUTF8Bytes(data);
+        this.make();
+    }
+    QRCodeAlg.prototype = {
+        constructor: QRCodeAlg,
+        /**
+         * 获取二维码矩阵大小
+         * @return {num} 矩阵大小
+         */
+        getModuleCount: function () {
+            return this.moduleCount;
+        },
+        /**
+         * 编码
+         */
+        make: function () {
+            this.getRightType();
+            this.dataCache = this.createData();
+            this.createQrcode();
+        },
+        /**
+         * 设置二位矩阵功能图形
+         * @param  {bool} test 表示是否在寻找最好掩膜阶段
+         * @param  {num} maskPattern 掩膜的版本
+         */
+        makeImpl: function (maskPattern) {
+            this.moduleCount = this.typeNumber * 4 + 17;
+            this.modules = new Array(this.moduleCount);
+            for (var row = 0; row < this.moduleCount; row++) {
+                this.modules[row] = new Array(this.moduleCount);
+            }
+            this.setupPositionProbePattern(0, 0);
+            this.setupPositionProbePattern(this.moduleCount - 7, 0);
+            this.setupPositionProbePattern(0, this.moduleCount - 7);
+            this.setupPositionAdjustPattern();
+            this.setupTimingPattern();
+            this.setupTypeInfo(true, maskPattern);
+            if (this.typeNumber >= 7) {
+                this.setupTypeNumber(true);
+            }
+            this.mapData(this.dataCache, maskPattern);
+        },
+        /**
+         * 设置二维码的位置探测图形
+         * @param  {num} row 探测图形的中心横坐标
+         * @param  {num} col 探测图形的中心纵坐标
+         */
+        setupPositionProbePattern: function (row, col) {
+            for (var r = -1; r <= 7; r++) {
+                if (row + r <= -1 || this.moduleCount <= row + r) continue;
+                for (var c = -1; c <= 7; c++) {
+                    if (col + c <= -1 || this.moduleCount <= col + c) continue;
+                    if ((0 <= r && r <= 6 && (c == 0 || c == 6)) || (0 <= c && c <= 6 && (r == 0 || r == 6)) || (2 <= r && r <= 4 && 2 <= c && c <= 4)) {
+                        this.modules[row + r][col + c] = true;
+                    } else {
+                        this.modules[row + r][col + c] = false;
+                    }
+                }
+            }
+        },
+        /**
+         * 创建二维码
+         * @return {[type]} [description]
+         */
+        createQrcode: function () {
+            var minLostPoint = 0;
+            var pattern = 0;
+            var bestModules = null;
+            for (var i = 0; i < 8; i++) {
+                this.makeImpl(i);
+                var lostPoint = QRUtil.getLostPoint(this);
+                if (i == 0 || minLostPoint > lostPoint) {
+                    minLostPoint = lostPoint;
+                    pattern = i;
+                    bestModules = this.modules;
+                }
+            }
+            this.modules = bestModules;
+            this.setupTypeInfo(false, pattern);
+            if (this.typeNumber >= 7) {
+                this.setupTypeNumber(false);
+            }
+        },
+        /**
+         * 设置定位图形
+         * @return {[type]} [description]
+         */
+        setupTimingPattern: function () {
+            for (var r = 8; r < this.moduleCount - 8; r++) {
+                if (this.modules[r][6] != null) {
+                    continue;
+                }
+                this.modules[r][6] = (r % 2 == 0);
+                if (this.modules[6][r] != null) {
+                    continue;
+                }
+                this.modules[6][r] = (r % 2 == 0);
+            }
+        },
+        /**
+         * 设置矫正图形
+         * @return {[type]} [description]
+         */
+        setupPositionAdjustPattern: function () {
+            var pos = QRUtil.getPatternPosition(this.typeNumber);
+            for (var i = 0; i < pos.length; i++) {
+                for (var j = 0; j < pos.length; j++) {
+                    var row = pos[i];
+                    var col = pos[j];
+                    if (this.modules[row][col] != null) {
+                        continue;
+                    }
+                    for (var r = -2; r <= 2; r++) {
+                        for (var c = -2; c <= 2; c++) {
+                            if (r == -2 || r == 2 || c == -2 || c == 2 || (r == 0 && c == 0)) {
+                                this.modules[row + r][col + c] = true;
+                            } else {
+                                this.modules[row + r][col + c] = false;
+                            }
+                        }
+                    }
+                }
+            }
+        },
+        /**
+         * 设置版本信息(7以上版本才有)
+         * @param  {bool} test 是否处于判断最佳掩膜阶段
+         * @return {[type]}      [description]
+         */
+        setupTypeNumber: function (test) {
+            var bits = QRUtil.getBCHTypeNumber(this.typeNumber);
+            for (var i = 0; i < 18; i++) {
+                var mod = (!test && ((bits >> i) & 1) == 1);
+                this.modules[Math.floor(i / 3)][i % 3 + this.moduleCount - 8 - 3] = mod;
+                this.modules[i % 3 + this.moduleCount - 8 - 3][Math.floor(i / 3)] = mod;
+            }
+        },
+        /**
+         * 设置格式信息(纠错等级和掩膜版本)
+         * @param  {bool} test
+         * @param  {num} maskPattern 掩膜版本
+         * @return {}
+         */
+        setupTypeInfo: function (test, maskPattern) {
+            var data = (QRErrorCorrectLevel[this.errorCorrectLevel] << 3) | maskPattern;
+            var bits = QRUtil.getBCHTypeInfo(data);
+            // vertical
+            for (var i = 0; i < 15; i++) {
+                var mod = (!test && ((bits >> i) & 1) == 1);
+                if (i < 6) {
+                    this.modules[i][8] = mod;
+                } else if (i < 8) {
+                    this.modules[i + 1][8] = mod;
+                } else {
+                    this.modules[this.moduleCount - 15 + i][8] = mod;
+                }
+                // horizontal
+                var mod = (!test && ((bits >> i) & 1) == 1);
+                if (i < 8) {
+                    this.modules[8][this.moduleCount - i - 1] = mod;
+                } else if (i < 9) {
+                    this.modules[8][15 - i - 1 + 1] = mod;
+                } else {
+                    this.modules[8][15 - i - 1] = mod;
+                }
+            }
+            // fixed module
+            this.modules[this.moduleCount - 8][8] = (!test);
+        },
+        /**
+         * 数据编码
+         * @return {[type]} [description]
+         */
+        createData: function () {
+            var buffer = new QRBitBuffer();
+            var lengthBits = this.typeNumber > 9 ? 16 : 8;
+            buffer.put(4, 4); //添加模式
+            buffer.put(this.utf8bytes.length, lengthBits);
+            for (var i = 0, l = this.utf8bytes.length; i < l; i++) {
+                buffer.put(this.utf8bytes[i], 8);
+            }
+            if (buffer.length + 4 <= this.totalDataCount * 8) {
+                buffer.put(0, 4);
+            }
+            // padding
+            while (buffer.length % 8 != 0) {
+                buffer.putBit(false);
+            }
+            // padding
+            while (true) {
+                if (buffer.length >= this.totalDataCount * 8) {
+                    break;
+                }
+                buffer.put(QRCodeAlg.PAD0, 8);
+                if (buffer.length >= this.totalDataCount * 8) {
+                    break;
+                }
+                buffer.put(QRCodeAlg.PAD1, 8);
+            }
+            return this.createBytes(buffer);
+        },
+        /**
+         * 纠错码编码
+         * @param  {buffer} buffer 数据编码
+         * @return {[type]}
+         */
+        createBytes: function (buffer) {
+            var offset = 0;
+            var maxDcCount = 0;
+            var maxEcCount = 0;
+            var length = this.rsBlock.length / 3;
+            var rsBlocks = new Array();
+            for (var i = 0; i < length; i++) {
+                var count = this.rsBlock[i * 3 + 0];
+                var totalCount = this.rsBlock[i * 3 + 1];
+                var dataCount = this.rsBlock[i * 3 + 2];
+                for (var j = 0; j < count; j++) {
+                    rsBlocks.push([dataCount, totalCount]);
+                }
+            }
+            var dcdata = new Array(rsBlocks.length);
+            var ecdata = new Array(rsBlocks.length);
+            for (var r = 0; r < rsBlocks.length; r++) {
+                var dcCount = rsBlocks[r][0];
+                var ecCount = rsBlocks[r][1] - dcCount;
+                maxDcCount = Math.max(maxDcCount, dcCount);
+                maxEcCount = Math.max(maxEcCount, ecCount);
+                dcdata[r] = new Array(dcCount);
+                for (var i = 0; i < dcdata[r].length; i++) {
+                    dcdata[r][i] = 0xff & buffer.buffer[i + offset];
+                }
+                offset += dcCount;
+                var rsPoly = QRUtil.getErrorCorrectPolynomial(ecCount);
+                var rawPoly = new QRPolynomial(dcdata[r], rsPoly.getLength() - 1);
+                var modPoly = rawPoly.mod(rsPoly);
+                ecdata[r] = new Array(rsPoly.getLength() - 1);
+                for (var i = 0; i < ecdata[r].length; i++) {
+                    var modIndex = i + modPoly.getLength() - ecdata[r].length;
+                    ecdata[r][i] = (modIndex >= 0) ? modPoly.get(modIndex) : 0;
+                }
+            }
+            var data = new Array(this.totalDataCount);
+            var index = 0;
+            for (var i = 0; i < maxDcCount; i++) {
+                for (var r = 0; r < rsBlocks.length; r++) {
+                    if (i < dcdata[r].length) {
+                        data[index++] = dcdata[r][i];
+                    }
+                }
+            }
+            for (var i = 0; i < maxEcCount; i++) {
+                for (var r = 0; r < rsBlocks.length; r++) {
+                    if (i < ecdata[r].length) {
+                        data[index++] = ecdata[r][i];
+                    }
+                }
+            }
+            return data;
+
+        },
+        /**
+         * 布置模块,构建最终信息
+         * @param  {} data
+         * @param  {} maskPattern
+         * @return {}
+         */
+        mapData: function (data, maskPattern) {
+            var inc = -1;
+            var row = this.moduleCount - 1;
+            var bitIndex = 7;
+            var byteIndex = 0;
+            for (var col = this.moduleCount - 1; col > 0; col -= 2) {
+                if (col == 6) col--;
+                while (true) {
+                    for (var c = 0; c < 2; c++) {
+                        if (this.modules[row][col - c] == null) {
+                            var dark = false;
+                            if (byteIndex < data.length) {
+                                dark = (((data[byteIndex] >>> bitIndex) & 1) == 1);
+                            }
+                            var mask = QRUtil.getMask(maskPattern, row, col - c);
+                            if (mask) {
+                                dark = !dark;
+                            }
+                            this.modules[row][col - c] = dark;
+                            bitIndex--;
+                            if (bitIndex == -1) {
+                                byteIndex++;
+                                bitIndex = 7;
+                            }
+                        }
+                    }
+                    row += inc;
+                    if (row < 0 || this.moduleCount <= row) {
+                        row -= inc;
+                        inc = -inc;
+                        break;
+                    }
+                }
+            }
+        }
+    };
+    /**
+     * 填充字段
+     */
+    QRCodeAlg.PAD0 = 0xEC;
+    QRCodeAlg.PAD1 = 0x11;
+    //---------------------------------------------------------------------
+    // 纠错等级对应的编码
+    //---------------------------------------------------------------------
+    var QRErrorCorrectLevel = [1, 0, 3, 2];
+    //---------------------------------------------------------------------
+    // 掩膜版本
+    //---------------------------------------------------------------------
+    var QRMaskPattern = {
+        PATTERN000: 0,
+        PATTERN001: 1,
+        PATTERN010: 2,
+        PATTERN011: 3,
+        PATTERN100: 4,
+        PATTERN101: 5,
+        PATTERN110: 6,
+        PATTERN111: 7
+    };
+    //---------------------------------------------------------------------
+    // 工具类
+    //---------------------------------------------------------------------
+    var QRUtil = {
+        /*
+        每个版本矫正图形的位置
+         */
+        PATTERN_POSITION_TABLE: [
+            [],
+            [6, 18],
+            [6, 22],
+            [6, 26],
+            [6, 30],
+            [6, 34],
+            [6, 22, 38],
+            [6, 24, 42],
+            [6, 26, 46],
+            [6, 28, 50],
+            [6, 30, 54],
+            [6, 32, 58],
+            [6, 34, 62],
+            [6, 26, 46, 66],
+            [6, 26, 48, 70],
+            [6, 26, 50, 74],
+            [6, 30, 54, 78],
+            [6, 30, 56, 82],
+            [6, 30, 58, 86],
+            [6, 34, 62, 90],
+            [6, 28, 50, 72, 94],
+            [6, 26, 50, 74, 98],
+            [6, 30, 54, 78, 102],
+            [6, 28, 54, 80, 106],
+            [6, 32, 58, 84, 110],
+            [6, 30, 58, 86, 114],
+            [6, 34, 62, 90, 118],
+            [6, 26, 50, 74, 98, 122],
+            [6, 30, 54, 78, 102, 126],
+            [6, 26, 52, 78, 104, 130],
+            [6, 30, 56, 82, 108, 134],
+            [6, 34, 60, 86, 112, 138],
+            [6, 30, 58, 86, 114, 142],
+            [6, 34, 62, 90, 118, 146],
+            [6, 30, 54, 78, 102, 126, 150],
+            [6, 24, 50, 76, 102, 128, 154],
+            [6, 28, 54, 80, 106, 132, 158],
+            [6, 32, 58, 84, 110, 136, 162],
+            [6, 26, 54, 82, 110, 138, 166],
+            [6, 30, 58, 86, 114, 142, 170]
+        ],
+        G15: (1 << 10) | (1 << 8) | (1 << 5) | (1 << 4) | (1 << 2) | (1 << 1) | (1 << 0),
+        G18: (1 << 12) | (1 << 11) | (1 << 10) | (1 << 9) | (1 << 8) | (1 << 5) | (1 << 2) | (1 << 0),
+        G15_MASK: (1 << 14) | (1 << 12) | (1 << 10) | (1 << 4) | (1 << 1),
+        /*
+        BCH编码格式信息
+         */
+        getBCHTypeInfo: function (data) {
+            var d = data << 10;
+            while (QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G15) >= 0) {
+                d ^= (QRUtil.G15 << (QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G15)));
+            }
+            return ((data << 10) | d) ^ QRUtil.G15_MASK;
+        },
+        /*
+        BCH编码版本信息
+         */
+        getBCHTypeNumber: function (data) {
+            var d = data << 12;
+            while (QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G18) >= 0) {
+                d ^= (QRUtil.G18 << (QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G18)));
+            }
+            return (data << 12) | d;
+        },
+        /*
+        获取BCH位信息
+         */
+        getBCHDigit: function (data) {
+            var digit = 0;
+            while (data != 0) {
+                digit++;
+                data >>>= 1;
+            }
+            return digit;
+        },
+        /*
+        获取版本对应的矫正图形位置
+         */
+        getPatternPosition: function (typeNumber) {
+            return QRUtil.PATTERN_POSITION_TABLE[typeNumber - 1];
+        },
+        /*
+        掩膜算法
+         */
+        getMask: function (maskPattern, i, j) {
+            switch (maskPattern) {
+                case QRMaskPattern.PATTERN000:
+                    return (i + j) % 2 == 0;
+                case QRMaskPattern.PATTERN001:
+                    return i % 2 == 0;
+                case QRMaskPattern.PATTERN010:
+                    return j % 3 == 0;
+                case QRMaskPattern.PATTERN011:
+                    return (i + j) % 3 == 0;
+                case QRMaskPattern.PATTERN100:
+                    return (Math.floor(i / 2) + Math.floor(j / 3)) % 2 == 0;
+                case QRMaskPattern.PATTERN101:
+                    return (i * j) % 2 + (i * j) % 3 == 0;
+                case QRMaskPattern.PATTERN110:
+                    return ((i * j) % 2 + (i * j) % 3) % 2 == 0;
+                case QRMaskPattern.PATTERN111:
+                    return ((i * j) % 3 + (i + j) % 2) % 2 == 0;
+                default:
+                    throw new Error("bad maskPattern:" + maskPattern);
+            }
+        },
+        /*
+        获取RS的纠错多项式
+         */
+        getErrorCorrectPolynomial: function (errorCorrectLength) {
+            var a = new QRPolynomial([1], 0);
+            for (var i = 0; i < errorCorrectLength; i++) {
+                a = a.multiply(new QRPolynomial([1, QRMath.gexp(i)], 0));
+            }
+            return a;
+        },
+        /*
+        获取评价
+         */
+        getLostPoint: function (qrCode) {
+            var moduleCount = qrCode.getModuleCount(),
+                lostPoint = 0,
+                darkCount = 0;
+            for (var row = 0; row < moduleCount; row++) {
+                var sameCount = 0;
+                var head = qrCode.modules[row][0];
+                for (var col = 0; col < moduleCount; col++) {
+                    var current = qrCode.modules[row][col];
+                    //level 3 评价
+                    if (col < moduleCount - 6) {
+                        if (current && !qrCode.modules[row][col + 1] && qrCode.modules[row][col + 2] && qrCode.modules[row][col + 3] && qrCode.modules[row][col + 4] && !qrCode.modules[row][col + 5] && qrCode.modules[row][col + 6]) {
+                            if (col < moduleCount - 10) {
+                                if (qrCode.modules[row][col + 7] && qrCode.modules[row][col + 8] && qrCode.modules[row][col + 9] && qrCode.modules[row][col + 10]) {
+                                    lostPoint += 40;
+                                }
+                            } else if (col > 3) {
+                                if (qrCode.modules[row][col - 1] && qrCode.modules[row][col - 2] && qrCode.modules[row][col - 3] && qrCode.modules[row][col - 4]) {
+                                    lostPoint += 40;
+                                }
+                            }
+                        }
+                    }
+                    //level 2 评价
+                    if ((row < moduleCount - 1) && (col < moduleCount - 1)) {
+                        var count = 0;
+                        if (current) count++;
+                        if (qrCode.modules[row + 1][col]) count++;
+                        if (qrCode.modules[row][col + 1]) count++;
+                        if (qrCode.modules[row + 1][col + 1]) count++;
+                        if (count == 0 || count == 4) {
+                            lostPoint += 3;
+                        }
+                    }
+                    //level 1 评价
+                    if (head ^ current) {
+                        sameCount++;
+                    } else {
+                        head = current;
+                        if (sameCount >= 5) {
+                            lostPoint += (3 + sameCount - 5);
+                        }
+                        sameCount = 1;
+                    }
+                    //level 4 评价
+                    if (current) {
+                        darkCount++;
+                    }
+                }
+            }
+            for (var col = 0; col < moduleCount; col++) {
+                var sameCount = 0;
+                var head = qrCode.modules[0][col];
+                for (var row = 0; row < moduleCount; row++) {
+                    var current = qrCode.modules[row][col];
+                    //level 3 评价
+                    if (row < moduleCount - 6) {
+                        if (current && !qrCode.modules[row + 1][col] && qrCode.modules[row + 2][col] && qrCode.modules[row + 3][col] && qrCode.modules[row + 4][col] && !qrCode.modules[row + 5][col] && qrCode.modules[row + 6][col]) {
+                            if (row < moduleCount - 10) {
+                                if (qrCode.modules[row + 7][col] && qrCode.modules[row + 8][col] && qrCode.modules[row + 9][col] && qrCode.modules[row + 10][col]) {
+                                    lostPoint += 40;
+                                }
+                            } else if (row > 3) {
+                                if (qrCode.modules[row - 1][col] && qrCode.modules[row - 2][col] && qrCode.modules[row - 3][col] && qrCode.modules[row - 4][col]) {
+                                    lostPoint += 40;
+                                }
+                            }
+                        }
+                    }
+                    //level 1 评价
+                    if (head ^ current) {
+                        sameCount++;
+                    } else {
+                        head = current;
+                        if (sameCount >= 5) {
+                            lostPoint += (3 + sameCount - 5);
+                        }
+                        sameCount = 1;
+                    }
+                }
+            }
+            // LEVEL4
+            var ratio = Math.abs(100 * darkCount / moduleCount / moduleCount - 50) / 5;
+            lostPoint += ratio * 10;
+            return lostPoint;
+        }
+
+    };
+    //---------------------------------------------------------------------
+    // QRMath使用的数学工具
+    //---------------------------------------------------------------------
+    var QRMath = {
+        /*
+        将n转化为a^m
+         */
+        glog: function (n) {
+            if (n < 1) {
+                throw new Error("glog(" + n + ")");
+            }
+            return QRMath.LOG_TABLE[n];
+        },
+        /*
+        将a^m转化为n
+         */
+        gexp: function (n) {
+            while (n < 0) {
+                n += 255;
+            }
+            while (n >= 256) {
+                n -= 255;
+            }
+            return QRMath.EXP_TABLE[n];
+        },
+        EXP_TABLE: new Array(256),
+        LOG_TABLE: new Array(256)
+
+    };
+    for (var i = 0; i < 8; i++) {
+        QRMath.EXP_TABLE[i] = 1 << i;
+    }
+    for (var i = 8; i < 256; i++) {
+        QRMath.EXP_TABLE[i] = QRMath.EXP_TABLE[i - 4] ^ QRMath.EXP_TABLE[i - 5] ^ QRMath.EXP_TABLE[i - 6] ^ QRMath.EXP_TABLE[i - 8];
+    }
+    for (var i = 0; i < 255; i++) {
+        QRMath.LOG_TABLE[QRMath.EXP_TABLE[i]] = i;
+    }
+    //---------------------------------------------------------------------
+    // QRPolynomial 多项式
+    //---------------------------------------------------------------------
+    /**
+     * 多项式类
+     * @param {Array} num   系数
+     * @param {num} shift a^shift
+     */
+    function QRPolynomial(num, shift) {
+        if (num.length == undefined) {
+            throw new Error(num.length + "/" + shift);
+        }
+        var offset = 0;
+        while (offset < num.length && num[offset] == 0) {
+            offset++;
+        }
+        this.num = new Array(num.length - offset + shift);
+        for (var i = 0; i < num.length - offset; i++) {
+            this.num[i] = num[i + offset];
+        }
+    }
+    QRPolynomial.prototype = {
+        get: function (index) {
+            return this.num[index];
+        },
+        getLength: function () {
+            return this.num.length;
+        },
+        /**
+         * 多项式乘法
+         * @param  {QRPolynomial} e 被乘多项式
+         * @return {[type]}   [description]
+         */
+        multiply: function (e) {
+            var num = new Array(this.getLength() + e.getLength() - 1);
+            for (var i = 0; i < this.getLength(); i++) {
+                for (var j = 0; j < e.getLength(); j++) {
+                    num[i + j] ^= QRMath.gexp(QRMath.glog(this.get(i)) + QRMath.glog(e.get(j)));
+                }
+            }
+            return new QRPolynomial(num, 0);
+        },
+        /**
+         * 多项式模运算
+         * @param  {QRPolynomial} e 模多项式
+         * @return {}
+         */
+        mod: function (e) {
+            var tl = this.getLength(),
+                el = e.getLength();
+            if (tl - el < 0) {
+                return this;
+            }
+            var num = new Array(tl);
+            for (var i = 0; i < tl; i++) {
+                num[i] = this.get(i);
+            }
+            while (num.length >= el) {
+                var ratio = QRMath.glog(num[0]) - QRMath.glog(e.get(0));
+
+                for (var i = 0; i < e.getLength(); i++) {
+                    num[i] ^= QRMath.gexp(QRMath.glog(e.get(i)) + ratio);
+                }
+                while (num[0] == 0) {
+                    num.shift();
+                }
+            }
+            return new QRPolynomial(num, 0);
+        }
+    };
+
+    //---------------------------------------------------------------------
+    // RS_BLOCK_TABLE
+    //---------------------------------------------------------------------
+    /*
+    二维码各个版本信息[块数, 每块中的数据块数, 每块中的信息块数]
+     */
+    var RS_BLOCK_TABLE = [
+        // L
+        // M
+        // Q
+        // H
+        // 1
+        [1, 26, 19],
+        [1, 26, 16],
+        [1, 26, 13],
+        [1, 26, 9],
+
+        // 2
+        [1, 44, 34],
+        [1, 44, 28],
+        [1, 44, 22],
+        [1, 44, 16],
+
+        // 3
+        [1, 70, 55],
+        [1, 70, 44],
+        [2, 35, 17],
+        [2, 35, 13],
+
+        // 4
+        [1, 100, 80],
+        [2, 50, 32],
+        [2, 50, 24],
+        [4, 25, 9],
+
+        // 5
+        [1, 134, 108],
+        [2, 67, 43],
+        [2, 33, 15, 2, 34, 16],
+        [2, 33, 11, 2, 34, 12],
+
+        // 6
+        [2, 86, 68],
+        [4, 43, 27],
+        [4, 43, 19],
+        [4, 43, 15],
+
+        // 7
+        [2, 98, 78],
+        [4, 49, 31],
+        [2, 32, 14, 4, 33, 15],
+        [4, 39, 13, 1, 40, 14],
+
+        // 8
+        [2, 121, 97],
+        [2, 60, 38, 2, 61, 39],
+        [4, 40, 18, 2, 41, 19],
+        [4, 40, 14, 2, 41, 15],
+
+        // 9
+        [2, 146, 116],
+        [3, 58, 36, 2, 59, 37],
+        [4, 36, 16, 4, 37, 17],
+        [4, 36, 12, 4, 37, 13],
+
+        // 10
+        [2, 86, 68, 2, 87, 69],
+        [4, 69, 43, 1, 70, 44],
+        [6, 43, 19, 2, 44, 20],
+        [6, 43, 15, 2, 44, 16],
+
+        // 11
+        [4, 101, 81],
+        [1, 80, 50, 4, 81, 51],
+        [4, 50, 22, 4, 51, 23],
+        [3, 36, 12, 8, 37, 13],
+
+        // 12
+        [2, 116, 92, 2, 117, 93],
+        [6, 58, 36, 2, 59, 37],
+        [4, 46, 20, 6, 47, 21],
+        [7, 42, 14, 4, 43, 15],
+
+        // 13
+        [4, 133, 107],
+        [8, 59, 37, 1, 60, 38],
+        [8, 44, 20, 4, 45, 21],
+        [12, 33, 11, 4, 34, 12],
+
+        // 14
+        [3, 145, 115, 1, 146, 116],
+        [4, 64, 40, 5, 65, 41],
+        [11, 36, 16, 5, 37, 17],
+        [11, 36, 12, 5, 37, 13],
+
+        // 15
+        [5, 109, 87, 1, 110, 88],
+        [5, 65, 41, 5, 66, 42],
+        [5, 54, 24, 7, 55, 25],
+        [11, 36, 12],
+
+        // 16
+        [5, 122, 98, 1, 123, 99],
+        [7, 73, 45, 3, 74, 46],
+        [15, 43, 19, 2, 44, 20],
+        [3, 45, 15, 13, 46, 16],
+
+        // 17
+        [1, 135, 107, 5, 136, 108],
+        [10, 74, 46, 1, 75, 47],
+        [1, 50, 22, 15, 51, 23],
+        [2, 42, 14, 17, 43, 15],
+
+        // 18
+        [5, 150, 120, 1, 151, 121],
+        [9, 69, 43, 4, 70, 44],
+        [17, 50, 22, 1, 51, 23],
+        [2, 42, 14, 19, 43, 15],
+
+        // 19
+        [3, 141, 113, 4, 142, 114],
+        [3, 70, 44, 11, 71, 45],
+        [17, 47, 21, 4, 48, 22],
+        [9, 39, 13, 16, 40, 14],
+
+        // 20
+        [3, 135, 107, 5, 136, 108],
+        [3, 67, 41, 13, 68, 42],
+        [15, 54, 24, 5, 55, 25],
+        [15, 43, 15, 10, 44, 16],
+
+        // 21
+        [4, 144, 116, 4, 145, 117],
+        [17, 68, 42],
+        [17, 50, 22, 6, 51, 23],
+        [19, 46, 16, 6, 47, 17],
+
+        // 22
+        [2, 139, 111, 7, 140, 112],
+        [17, 74, 46],
+        [7, 54, 24, 16, 55, 25],
+        [34, 37, 13],
+
+        // 23
+        [4, 151, 121, 5, 152, 122],
+        [4, 75, 47, 14, 76, 48],
+        [11, 54, 24, 14, 55, 25],
+        [16, 45, 15, 14, 46, 16],
+
+        // 24
+        [6, 147, 117, 4, 148, 118],
+        [6, 73, 45, 14, 74, 46],
+        [11, 54, 24, 16, 55, 25],
+        [30, 46, 16, 2, 47, 17],
+
+        // 25
+        [8, 132, 106, 4, 133, 107],
+        [8, 75, 47, 13, 76, 48],
+        [7, 54, 24, 22, 55, 25],
+        [22, 45, 15, 13, 46, 16],
+
+        // 26
+        [10, 142, 114, 2, 143, 115],
+        [19, 74, 46, 4, 75, 47],
+        [28, 50, 22, 6, 51, 23],
+        [33, 46, 16, 4, 47, 17],
+
+        // 27
+        [8, 152, 122, 4, 153, 123],
+        [22, 73, 45, 3, 74, 46],
+        [8, 53, 23, 26, 54, 24],
+        [12, 45, 15, 28, 46, 16],
+
+        // 28
+        [3, 147, 117, 10, 148, 118],
+        [3, 73, 45, 23, 74, 46],
+        [4, 54, 24, 31, 55, 25],
+        [11, 45, 15, 31, 46, 16],
+
+        // 29
+        [7, 146, 116, 7, 147, 117],
+        [21, 73, 45, 7, 74, 46],
+        [1, 53, 23, 37, 54, 24],
+        [19, 45, 15, 26, 46, 16],
+
+        // 30
+        [5, 145, 115, 10, 146, 116],
+        [19, 75, 47, 10, 76, 48],
+        [15, 54, 24, 25, 55, 25],
+        [23, 45, 15, 25, 46, 16],
+
+        // 31
+        [13, 145, 115, 3, 146, 116],
+        [2, 74, 46, 29, 75, 47],
+        [42, 54, 24, 1, 55, 25],
+        [23, 45, 15, 28, 46, 16],
+
+        // 32
+        [17, 145, 115],
+        [10, 74, 46, 23, 75, 47],
+        [10, 54, 24, 35, 55, 25],
+        [19, 45, 15, 35, 46, 16],
+
+        // 33
+        [17, 145, 115, 1, 146, 116],
+        [14, 74, 46, 21, 75, 47],
+        [29, 54, 24, 19, 55, 25],
+        [11, 45, 15, 46, 46, 16],
+
+        // 34
+        [13, 145, 115, 6, 146, 116],
+        [14, 74, 46, 23, 75, 47],
+        [44, 54, 24, 7, 55, 25],
+        [59, 46, 16, 1, 47, 17],
+
+        // 35
+        [12, 151, 121, 7, 152, 122],
+        [12, 75, 47, 26, 76, 48],
+        [39, 54, 24, 14, 55, 25],
+        [22, 45, 15, 41, 46, 16],
+
+        // 36
+        [6, 151, 121, 14, 152, 122],
+        [6, 75, 47, 34, 76, 48],
+        [46, 54, 24, 10, 55, 25],
+        [2, 45, 15, 64, 46, 16],
+
+        // 37
+        [17, 152, 122, 4, 153, 123],
+        [29, 74, 46, 14, 75, 47],
+        [49, 54, 24, 10, 55, 25],
+        [24, 45, 15, 46, 46, 16],
+
+        // 38
+        [4, 152, 122, 18, 153, 123],
+        [13, 74, 46, 32, 75, 47],
+        [48, 54, 24, 14, 55, 25],
+        [42, 45, 15, 32, 46, 16],
+
+        // 39
+        [20, 147, 117, 4, 148, 118],
+        [40, 75, 47, 7, 76, 48],
+        [43, 54, 24, 22, 55, 25],
+        [10, 45, 15, 67, 46, 16],
+
+        // 40
+        [19, 148, 118, 6, 149, 119],
+        [18, 75, 47, 31, 76, 48],
+        [34, 54, 24, 34, 55, 25],
+        [20, 45, 15, 61, 46, 16]
+    ];
+
+    /**
+     * 根据数据获取对应版本
+     * @return {[type]} [description]
+     */
+    QRCodeAlg.prototype.getRightType = function () {
+        for (var typeNumber = 1; typeNumber < 41; typeNumber++) {
+            var rsBlock = RS_BLOCK_TABLE[(typeNumber - 1) * 4 + this.errorCorrectLevel];
+            if (rsBlock == undefined) {
+                throw new Error("bad rs block @ typeNumber:" + typeNumber + "/errorCorrectLevel:" + this.errorCorrectLevel);
+            }
+            var length = rsBlock.length / 3;
+            var totalDataCount = 0;
+            for (var i = 0; i < length; i++) {
+                var count = rsBlock[i * 3 + 0];
+                var dataCount = rsBlock[i * 3 + 2];
+                totalDataCount += dataCount * count;
+            }
+            var lengthBytes = typeNumber > 9 ? 2 : 1;
+            if (this.utf8bytes.length + lengthBytes < totalDataCount || typeNumber == 40) {
+                this.typeNumber = typeNumber;
+                this.rsBlock = rsBlock;
+                this.totalDataCount = totalDataCount;
+                break;
+            }
+        }
+    };
+
+    //---------------------------------------------------------------------
+    // QRBitBuffer
+    //---------------------------------------------------------------------
+    function QRBitBuffer() {
+        this.buffer = new Array();
+        this.length = 0;
+    }
+    QRBitBuffer.prototype = {
+        get: function (index) {
+            var bufIndex = Math.floor(index / 8);
+            return ((this.buffer[bufIndex] >>> (7 - index % 8)) & 1);
+        },
+        put: function (num, length) {
+            for (var i = 0; i < length; i++) {
+                this.putBit(((num >>> (length - i - 1)) & 1));
+            }
+        },
+        putBit: function (bit) {
+            var bufIndex = Math.floor(this.length / 8);
+            if (this.buffer.length <= bufIndex) {
+                this.buffer.push(0);
+            }
+            if (bit) {
+                this.buffer[bufIndex] |= (0x80 >>> (this.length % 8));
+            }
+            this.length++;
+        }
+    };
+
+
+
+    // xzedit
+    let qrcodeAlgObjCache = [];
+    /**
+     * 二维码构造函数,主要用于绘制
+     * @param  {参数列表} opt 传递参数
+     * @return {}
+     */
+    QRCode = function (opt) {
+        //设置默认参数
+        this.options = {
+            text: '',
+            size: 256,
+            correctLevel: 3,
+            background: '#ffffff',
+            foreground: '#000000',
+            pdground: '#000000',
+            image: '',
+            imageSize: 30,
+            canvasId: opt.canvasId,
+            context: opt.context,
+            usingComponents: opt.usingComponents,
+            showLoading: opt.showLoading,
+            loadingText: opt.loadingText,
+        };
+        if (typeof opt === 'string') { // 只编码ASCII字符串
+            opt = {
+                text: opt
+            };
+        }
+        if (opt) {
+            for (var i in opt) {
+                this.options[i] = opt[i];
+            }
+        }
+        //使用QRCodeAlg创建二维码结构
+        var qrCodeAlg = null;
+        for (var i = 0, l = qrcodeAlgObjCache.length; i < l; i++) {
+            if (qrcodeAlgObjCache[i].text == this.options.text && qrcodeAlgObjCache[i].text.correctLevel == this.options.correctLevel) {
+                qrCodeAlg = qrcodeAlgObjCache[i].obj;
+                break;
+            }
+        }
+        if (i == l) {
+            qrCodeAlg = new QRCodeAlg(this.options.text, this.options.correctLevel);
+            qrcodeAlgObjCache.push({
+                text: this.options.text,
+                correctLevel: this.options.correctLevel,
+                obj: qrCodeAlg
+            });
+        }
+        /**
+         * 计算矩阵点的前景色
+         * @param {Obj} config
+         * @param {Number} config.row 点x坐标
+         * @param {Number} config.col 点y坐标
+         * @param {Number} config.count 矩阵大小
+         * @param {Number} config.options 组件的options
+         * @return {String}
+         */
+        let getForeGround = function (config) {
+            var options = config.options;
+            if (options.pdground && (
+                (config.row > 1 && config.row < 5 && config.col > 1 && config.col < 5) ||
+                (config.row > (config.count - 6) && config.row < (config.count - 2) && config.col > 1 && config.col < 5) ||
+                (config.row > 1 && config.row < 5 && config.col > (config.count - 6) && config.col < (config.count - 2))
+            )) {
+                return options.pdground;
+            }
+            return options.foreground;
+        }
+        // 创建canvas
+        let createCanvas = function (options) {
+            if(options.showLoading){
+                uni.showLoading({
+                    title: options.loadingText,
+                    mask: true
+                });
+            }
+            var ctx = uni.createCanvasContext(options.canvasId, options.context);
+            var count = qrCodeAlg.getModuleCount();
+            var ratioSize = options.size;
+            var ratioImgSize = options.imageSize;
+            //计算每个点的长宽
+            var tileW = (ratioSize / count).toPrecision(4);
+            var tileH = (ratioSize / count).toPrecision(4);
+            //绘制
+            for (var row = 0; row < count; row++) {
+                for (var col = 0; col < count; col++) {
+                    var w = (Math.ceil((col + 1) * tileW) - Math.floor(col * tileW));
+                    var h = (Math.ceil((row + 1) * tileW) - Math.floor(row * tileW));
+                    var foreground = getForeGround({
+                        row: row,
+                        col: col,
+                        count: count,
+                        options: options
+                    });
+                    ctx.setFillStyle(qrCodeAlg.modules[row][col] ? foreground : options.background);
+                    ctx.fillRect(Math.round(col * tileW), Math.round(row * tileH), w, h);
+                }
+            }
+            if (options.image) {
+                var x = Number(((ratioSize - ratioImgSize) / 2).toFixed(2));
+                var y = Number(((ratioSize - ratioImgSize) / 2).toFixed(2));
+                drawRoundedRect(ctx, x, y, ratioImgSize, ratioImgSize, 2, 6, true, true)
+                ctx.drawImage(options.image, x, y, ratioImgSize, ratioImgSize);
+                // 画圆角矩形
+                function drawRoundedRect(ctxi, x, y, width, height, r, lineWidth, fill, stroke) {
+                    ctxi.setLineWidth(lineWidth);
+                    ctxi.setFillStyle(options.background);
+                    ctxi.setStrokeStyle(options.background);
+                    ctxi.beginPath(); // draw top and top right corner 
+                    ctxi.moveTo(x + r, y);
+                    ctxi.arcTo(x + width, y, x + width, y + r, r); // draw right side and bottom right corner 
+                    ctxi.arcTo(x + width, y + height, x + width - r, y + height, r); // draw bottom and bottom left corner 
+                    ctxi.arcTo(x, y + height, x, y + height - r, r); // draw left and top left corner 
+                    ctxi.arcTo(x, y, x + r, y, r);
+                    ctxi.closePath();
+                    if (fill) {
+                        ctxi.fill();
+                    }
+                    if (stroke) {
+                        ctxi.stroke();
+                    }
+                }
+            }
+            setTimeout(() => {
+                ctx.draw(true, () => {
+                    // 保存到临时区域
+                    setTimeout(() => {
+                        uni.canvasToTempFilePath({
+                            width: options.width,
+                            height: options.height,
+                            destWidth: options.width,
+                            destHeight: options.height,
+                            canvasId: options.canvasId,
+                            quality: Number(1),
+                            success: function (res) {
+                                if (options.cbResult) {
+                                    // 由于官方还没有统一此接口的输出字段,所以先判定下  支付宝为 res.apFilePath
+                                    if (!empty(res.tempFilePath)) {
+                                        options.cbResult(res.tempFilePath)
+                                    } else if (!empty(res.apFilePath)) {
+                                        options.cbResult(res.apFilePath)
+                                    } else {
+                                        options.cbResult(res.tempFilePath)
+                                    }
+                                }
+                            },
+                            fail: function (res) {
+                                if (options.cbResult) {
+                                    options.cbResult(res)
+                                }
+                            },
+                            complete: function () {
+                                uni.hideLoading();
+                            },
+                        }, options.context);
+                    }, options.text.length + 100);
+                });
+            }, options.usingComponents ? 0 : 150);
+        }
+        createCanvas(this.options);
+        // 空判定
+        let empty = function (v) {
+            let tp = typeof v,
+                rt = false;
+            if (tp == "number" && String(v) == "") {
+                rt = true
+            } else if (tp == "undefined") {
+                rt = true
+            } else if (tp == "object") {
+                if (JSON.stringify(v) == "{}" || JSON.stringify(v) == "[]" || v == null) rt = true
+            } else if (tp == "string") {
+                if (v == "" || v == "undefined" || v == "null" || v == "{}" || v == "[]") rt = true
+            } else if (tp == "function") {
+                rt = false
+            }
+            return rt
+        }
+    };
+    QRCode.prototype.clear = function (fn) {
+        var ctx = uni.createCanvasContext(this.options.canvasId, this.options.context)
+        ctx.clearRect(0, 0, this.options.size, this.options.size)
+        ctx.draw(false, () => {
+            if (fn) {
+                fn()
+            }
+        })
+    };
+})()
+
+export default QRCode

+ 204 - 0
components/tki-qrcode/tki-qrcode.vue

@@ -0,0 +1,204 @@
+<template xlang="wxml" minapp="mpvue">
+	<view class="tki-qrcode">
+		<canvas class="tki-qrcode-canvas" :canvas-id="cid" :style="{width:cpSize+'px',height:cpSize+'px'}" />
+		<image v-show="show" :src="result" :style="{width:cpSize+'px',height:cpSize+'px'}" />
+	</view>
+</template>
+
+<script>
+import QRCode from "./qrcode.js"
+let qrcode
+export default {
+	name: "tki-qrcode",
+	props: {
+		cid: {
+			type: String,
+			default: 'tki-qrcode-canvas'
+		},
+		size: {
+			type: Number,
+			default: 200
+		},
+		unit: {
+			type: String,
+			default: 'upx'
+		},
+		show: {
+			type: Boolean,
+			default: true
+		},
+		val: {
+			type: String,
+			default: ''
+		},
+		background: {
+			type: String,
+			default: '#ffffff'
+		},
+		foreground: {
+			type: String,
+			default: '#000000'
+		},
+		pdground: {
+			type: String,
+			default: '#000000'
+		},
+		icon: {
+			type: String,
+			default: ''
+		},
+		iconSize: {
+			type: Number,
+			default: 40
+		},
+		lv: {
+			type: Number,
+			default: 3
+		},
+		onval: {
+			type: Boolean,
+			default: false
+		},
+		loadMake: {
+			type: Boolean,
+			default: false
+		},
+		usingComponents: {
+			type: Boolean,
+			default: true
+		},
+		showLoading: {
+			type: Boolean,
+			default: true
+		},
+		loadingText: {
+			type: String,
+			default: '二维码生成中'
+		},
+	},
+	data() {
+		return {
+			result: '',
+		}
+	},
+	methods: {
+		_makeCode() {
+			let that = this
+			if (!this._empty(this.val)) {
+				qrcode = new QRCode({
+					context: that, // 上下文环境
+					canvasId:that.cid, // canvas-id
+					usingComponents: that.usingComponents, // 是否是自定义组件
+					loadingText: that.loadingText, // loading文字
+					text: that.val, // 生成内容
+					size: that.cpSize, // 二维码大小
+					background: that.background, // 背景色
+					foreground: that.foreground, // 前景色
+					pdground: that.pdground, // 定位角点颜色
+					correctLevel: that.lv, // 容错级别
+					image: that.icon, // 二维码图标
+					imageSize: that.iconSize,// 二维码图标大小
+					cbResult: function (res) { // 生成二维码的回调
+						that._result(res)
+					},
+				});
+			} else {
+				uni.showToast({
+					title: '二维码内容不能为空',
+					icon: 'none',
+					duration: 2000
+				});
+			}
+		},
+		_clearCode() {
+			this._result('')
+			qrcode.clear()
+		},
+		_saveCode() {
+			let that = this;
+			if (this.result != "") {
+				uni.saveImageToPhotosAlbum({
+					filePath: that.result,
+					success: function () {
+						uni.showToast({
+							title: '二维码保存成功',
+							icon: 'success',
+							duration: 2000
+						});
+					}
+				});
+			}
+		},
+		_result(res) {
+			this.result = res;
+			this.$emit('result', res)
+		},
+		_empty(v) {
+			let tp = typeof v,
+				rt = false;
+			if (tp == "number" && String(v) == "") {
+				rt = true
+			} else if (tp == "undefined") {
+				rt = true
+			} else if (tp == "object") {
+				if (JSON.stringify(v) == "{}" || JSON.stringify(v) == "[]" || v == null) rt = true
+			} else if (tp == "string") {
+				if (v == "" || v == "undefined" || v == "null" || v == "{}" || v == "[]") rt = true
+			} else if (tp == "function") {
+				rt = false
+			}
+			return rt
+		}
+	},
+	watch: {
+		size: function (n, o) {
+			if (n != o && !this._empty(n)) {
+				this.cSize = n
+				if (!this._empty(this.val)) {
+					setTimeout(() => {
+						this._makeCode()
+					}, 100);
+				}
+			}
+		},
+		val: function (n, o) {
+			if (this.onval) {
+				if (n != o && !this._empty(n)) {
+					setTimeout(() => {
+						this._makeCode()
+					}, 0);
+				}
+			}
+		}
+	},
+	computed: {
+		cpSize() {
+			if(this.unit == "upx"){
+				return uni.upx2px(this.size)
+			}else{
+				return this.size
+			}
+		}
+	},
+	mounted: function () {
+		if (this.loadMake) {
+			if (!this._empty(this.val)) {
+				setTimeout(() => {
+					this._makeCode()
+				}, 0);
+			}
+		}
+	},
+}
+</script>
+<style>
+.tki-qrcode {
+  position: relative;
+}
+.tki-qrcode-canvas {
+  position: fixed;
+  top: -99999upx;
+  left: -99999upx;
+  z-index: -99999;
+}
+</style>

+ 132 - 0
components/uni-icons/icons.js

@@ -0,0 +1,132 @@
+export default {
+	"pulldown": "\ue588",
+	"refreshempty": "\ue461",
+	"back": "\ue471",
+	"forward": "\ue470",
+	"more": "\ue507",
+	"more-filled": "\ue537",
+	"scan": "\ue612",
+	"qq": "\ue264",
+	"weibo": "\ue260",
+	"weixin": "\ue261",
+	"pengyouquan": "\ue262",
+	"loop": "\ue565",
+	"refresh": "\ue407",
+	"refresh-filled": "\ue437",
+	"arrowthindown": "\ue585",
+	"arrowthinleft": "\ue586",
+	"arrowthinright": "\ue587",
+	"arrowthinup": "\ue584",
+	"undo-filled": "\ue7d6",
+	"undo": "\ue406",
+	"redo": "\ue405",
+	"redo-filled": "\ue7d9",
+	"bars": "\ue563",
+	"chatboxes": "\ue203",
+	"camera": "\ue301",
+	"chatboxes-filled": "\ue233",
+	"camera-filled": "\ue7ef",
+	"cart-filled": "\ue7f4",
+	"cart": "\ue7f5",
+	"checkbox-filled": "\ue442",
+	"checkbox": "\ue7fa",
+	"arrowleft": "\ue582",
+	"arrowdown": "\ue581",
+	"arrowright": "\ue583",
+	"smallcircle-filled": "\ue801",
+	"arrowup": "\ue580",
+	"circle": "\ue411",
+	"eye-filled": "\ue568",
+	"eye-slash-filled": "\ue822",
+	"eye-slash": "\ue823",
+	"eye": "\ue824",
+	"flag-filled": "\ue825",
+	"flag": "\ue508",
+	"gear-filled": "\ue532",
+	"reload": "\ue462",
+	"gear": "\ue502",
+	"hand-thumbsdown-filled": "\ue83b",
+	"hand-thumbsdown": "\ue83c",
+	"hand-thumbsup-filled": "\ue83d",
+	"heart-filled": "\ue83e",
+	"hand-thumbsup": "\ue83f",
+	"heart": "\ue840",
+	"home": "\ue500",
+	"info": "\ue504",
+	"home-filled": "\ue530",
+	"info-filled": "\ue534",
+	"circle-filled": "\ue441",
+	"chat-filled": "\ue847",
+	"chat": "\ue263",
+	"mail-open-filled": "\ue84d",
+	"email-filled": "\ue231",
+	"mail-open": "\ue84e",
+	"email": "\ue201",
+	"checkmarkempty": "\ue472",
+	"list": "\ue562",
+	"locked-filled": "\ue856",
+	"locked": "\ue506",
+	"map-filled": "\ue85c",
+	"map-pin": "\ue85e",
+	"map-pin-ellipse": "\ue864",
+	"map": "\ue364",
+	"minus-filled": "\ue440",
+	"mic-filled": "\ue332",
+	"minus": "\ue410",
+	"micoff": "\ue360",
+	"mic": "\ue302",
+	"clear": "\ue434",
+	"smallcircle": "\ue868",
+	"close": "\ue404",
+	"closeempty": "\ue460",
+	"paperclip": "\ue567",
+	"paperplane": "\ue503",
+	"paperplane-filled": "\ue86e",
+	"person-filled": "\ue131",
+	"contact-filled": "\ue130",
+	"person": "\ue101",
+	"contact": "\ue100",
+	"images-filled": "\ue87a",
+	"phone": "\ue200",
+	"images": "\ue87b",
+	"image": "\ue363",
+	"image-filled": "\ue877",
+	"location-filled": "\ue333",
+	"location": "\ue303",
+	"plus-filled": "\ue439",
+	"plus": "\ue409",
+	"plusempty": "\ue468",
+	"help-filled": "\ue535",
+	"help": "\ue505",
+	"navigate-filled": "\ue884",
+	"navigate": "\ue501",
+	"mic-slash-filled": "\ue892",
+	"search": "\ue466",
+	"settings": "\ue560",
+	"sound": "\ue590",
+	"sound-filled": "\ue8a1",
+	"spinner-cycle": "\ue465",
+	"download-filled": "\ue8a4",
+	"personadd-filled": "\ue132",
+	"videocam-filled": "\ue8af",
+	"personadd": "\ue102",
+	"upload": "\ue402",
+	"upload-filled": "\ue8b1",
+	"starhalf": "\ue463",
+	"star-filled": "\ue438",
+	"star": "\ue408",
+	"trash": "\ue401",
+	"phone-filled": "\ue230",
+	"compose": "\ue400",
+	"videocam": "\ue300",
+	"trash-filled": "\ue8dc",
+	"download": "\ue403",
+	"chatbubble-filled": "\ue232",
+	"chatbubble": "\ue202",
+	"cloud-download": "\ue8e4",
+	"cloud-upload-filled": "\ue8e5",
+	"cloud-upload": "\ue8e6",
+	"cloud-download-filled": "\ue8e9",
+	"headphones":"\ue8bf",
+	"shop":"\ue609"
+}

File diff suppressed because it is too large
+ 10 - 0
components/uni-icons/uni-icons.vue


+ 194 - 0
components/uni-load-more/uni-load-more.vue

@@ -0,0 +1,194 @@
+<template>
+	<view class="uni-load-more">
+		<view class="uni-load-more__img" v-show="status === 'loading' && showIcon">
+			<view class="load1">
+				<view :style="{background:color}"></view>
+				<view :style="{background:color}"></view>
+				<view :style="{background:color}"></view>
+				<view :style="{background:color}"></view>
+			</view>
+			<view class="load2">
+				<view :style="{background:color}"></view>
+				<view :style="{background:color}"></view>
+				<view :style="{background:color}"></view>
+				<view :style="{background:color}"></view>
+			</view>
+			<view class="load3">
+				<view :style="{background:color}"></view>
+				<view :style="{background:color}"></view>
+				<view :style="{background:color}"></view>
+				<view :style="{background:color}"></view>
+			</view>
+		</view>
+		<text class="uni-load-more__text" :style="{color:color}">{{status === 'more' ? contentText.contentdown : (status === 'loading' ? contentText.contentrefresh : contentText.contentnomore)}}</text>
+	</view>
+</template>
+
+<script>
+	export default {
+		name: "uni-load-more",
+		props: {
+			status: {
+				//上拉的状态:more-loading前;loading-loading中;noMore-没有更多了
+				type: String,
+				default: 'more'
+			},
+			showIcon: {
+				type: Boolean,
+				default: true
+			},
+			color: {
+				type: String,
+				default: "#777777"
+			},
+			contentText: {
+				type: Object,
+				default () {
+					return {
+						contentdown: "上拉显示更多",
+						contentrefresh: "正在加载...",
+						contentnomore: "没有更多数据了"
+					};
+				}
+			}
+		},
+		data() {
+			return {}
+		}
+	}
+</script>
+
+<style>
+	@charset "UTF-8";
+
+	.uni-load-more {
+		display: flex;
+		flex-direction: row;
+		height: 80upx;
+		align-items: center;
+		justify-content: center
+	}
+
+	.uni-load-more__text {
+		font-size: 28upx;
+		color: #999
+	}
+
+	.uni-load-more__img {
+		height: 24px;
+		width: 24px;
+		margin-right: 10px
+	}
+
+	.uni-load-more__img>view {
+		position: absolute
+	}
+
+	.uni-load-more__img>view view {
+		width: 6px;
+		height: 2px;
+		border-top-left-radius: 1px;
+		border-bottom-left-radius: 1px;
+		background: #999;
+		position: absolute;
+		opacity: .2;
+		transform-origin: 50%;
+		animation: load 1.56s ease infinite
+	}
+
+	.uni-load-more__img>view view:nth-child(1) {
+		transform: rotate(90deg);
+		top: 2px;
+		left: 9px
+	}
+
+	.uni-load-more__img>view view:nth-child(2) {
+		transform: rotate(180deg);
+		top: 11px;
+		right: 0
+	}
+
+	.uni-load-more__img>view view:nth-child(3) {
+		transform: rotate(270deg);
+		bottom: 2px;
+		left: 9px
+	}
+
+	.uni-load-more__img>view view:nth-child(4) {
+		top: 11px;
+		left: 0
+	}
+
+	.load1,
+	.load2,
+	.load3 {
+		height: 24px;
+		width: 24px
+	}
+
+	.load2 {
+		transform: rotate(30deg)
+	}
+
+	.load3 {
+		transform: rotate(60deg)
+	}
+
+	.load1 view:nth-child(1) {
+		animation-delay: 0s
+	}
+
+	.load2 view:nth-child(1) {
+		animation-delay: .13s
+	}
+
+	.load3 view:nth-child(1) {
+		animation-delay: .26s
+	}
+
+	.load1 view:nth-child(2) {
+		animation-delay: .39s
+	}
+
+	.load2 view:nth-child(2) {
+		animation-delay: .52s
+	}
+
+	.load3 view:nth-child(2) {
+		animation-delay: .65s
+	}
+
+	.load1 view:nth-child(3) {
+		animation-delay: .78s
+	}
+
+	.load2 view:nth-child(3) {
+		animation-delay: .91s
+	}
+
+	.load3 view:nth-child(3) {
+		animation-delay: 1.04s
+	}
+
+	.load1 view:nth-child(4) {
+		animation-delay: 1.17s
+	}
+
+	.load2 view:nth-child(4) {
+		animation-delay: 1.3s
+	}
+
+	.load3 view:nth-child(4) {
+		animation-delay: 1.43s
+	}
+
+	@-webkit-keyframes load {
+		0% {
+			opacity: 1
+		}
+
+		100% {
+			opacity: .2
+		}
+	}
+</style>

+ 139 - 0
components/uni-section/uni-section.vue

@@ -0,0 +1,139 @@
+<template>
+	<view class="uni-section" nvue>
+		<view v-if="type" class="uni-section__head">
+			<view :class="type" class="uni-section__head-tag" />
+		</view>
+		<view class="uni-section__content">
+			<text :class="{'distraction':!subTitle}" class="uni-section__content-title">{{ title }}</text>
+			<text v-if="subTitle" class="uni-section__content-sub">{{ subTitle }}</text>
+		</view>
+		<slot />
+	</view>
+</template>
+
+<script>
+
+	/**
+	 * Section 标题栏
+	 * @description 标题栏
+	 * @property {String} type = [line|circle] 标题装饰类型
+	 * 	@value line 竖线
+	 * 	@value circle 圆形
+	 * @property {String} title 主标题
+	 * @property {String} subTitle 副标题
+	 */
+
+	export default {
+		name: 'UniSection',
+		props: {
+			type: {
+				type: String,
+				default: ''
+			},
+			title: {
+				type: String,
+				default: ''
+			},
+			subTitle: {
+				type: String,
+				default: ''
+			}
+		},
+		data() {
+			return {}
+		},
+		watch: {
+			title(newVal) {
+				if (uni.report && newVal !== '') {
+					uni.report('title', newVal)
+				}
+			}
+		},
+		methods: {
+			onClick() {
+				this.$emit('click')
+			}
+		}
+	}
+</script>
+<style lang="scss" scoped>
+	.uni-section {
+		position: relative;
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		margin-top: 10px;
+		flex-direction: row;
+		align-items: center;
+		padding: 0 10px;
+		height: 50px;
+		background-color: $uni-bg-color-grey;
+		/* #ifdef APP-NVUE */
+		// border-bottom-color: $uni-border-color;
+		// border-bottom-style: solid;
+		// border-bottom-width: 0.5px;
+		/* #endif */
+		font-weight: normal;
+	}
+	/* #ifndef APP-NVUE */
+	// .uni-section:after {
+	// 	position: absolute;
+	// 	bottom: 0;
+	// 	right: 0;
+	// 	left: 0;
+	// 	height: 1px;
+	// 	content: '';
+	// 	-webkit-transform: scaleY(.5);
+	// 	transform: scaleY(.5);
+	// 	background-color: $uni-border-color;
+	// }
+	/* #endif */
+
+	.uni-section__head {
+		flex-direction: row;
+		justify-content: center;
+		align-items: center;
+		margin-right: 10px;
+	}
+
+	.line {
+		height: 15px;
+		background-color: $uni-text-color-disable;
+		border-radius: 5px;
+		width: 3px;
+	}
+
+	.circle {
+		width: 8px;
+		height: 8px;
+		border-top-right-radius: 50px;
+		border-top-left-radius: 50px;
+		border-bottom-left-radius: 50px;
+		border-bottom-right-radius: 50px;
+		background-color: $uni-text-color-disable;
+	}
+
+	.uni-section__content {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: column;
+		flex: 1;
+		color: $uni-text-color;
+	}
+
+	.uni-section__content-title {
+		font-size: $uni-font-size-base;
+		color: $uni-text-color;
+	}
+
+	.distraction {
+		flex-direction: row;
+		align-items: center;
+	}
+
+	.uni-section__content-sub {
+		font-size: $uni-font-size-sm;
+		color: $uni-text-color-grey;
+	}
+</style>

+ 273 - 0
components/wm-poster/wm-poster.vue

@@ -0,0 +1,273 @@
+<template>
+	<view style="background: #FFFFFF;">
+		<!-- <view v-if="loading"></view> -->
+		<canvas v-if="!tempFilePath" :canvas-id="CanvasID" :style="{ width: canvasW + 'px', height: canvasH + 'px' }"></canvas>
+		<image v-else lazy-load :src="tempFilePath" mode="widthFix" class="is-response" @longpress="toSave(tempFilePath)"></image>
+	</view>
+</template>
+
+<script>
+	var _this;
+	export default {
+		name: 'wm-poster',
+		props: {
+			CanvasID: {
+				//CanvasID 等同于 canvas-id
+				Type: String,
+				default: 'PosterCanvas'
+			},
+			imgSrc: {
+				//展示图
+				Type: String,
+				default: ''
+			},
+			QrSrc: {
+				//二维码
+				Type: String,
+				default: ''
+			},
+			Title: {
+				//文本内容
+				Type: String,
+				default: '省钱兄电竞'
+			},
+			TitleColor: {
+				//标题颜色
+				Type: String,
+				default: '#000000'
+			},
+			LineType: {
+				//标题显示行数		(注超出2行显示会导致画布布局絮乱)
+				Type: [String, Boolean],
+				default: true
+			},
+			PriceTxt: {
+				//价格值
+				Type: String,
+				default: ''
+			},
+			PriceColor: {
+				//价格颜色
+				Type: String,
+				default: '#e31d1a'
+			},
+			OriginalTxt: {
+				//原价值
+				Type: String,
+				default: ''
+			},
+			OriginalColor: {
+				//默认颜色(如原价与扫描二维码颜色)
+				Type: String,
+				default: '#b8b8b8'
+			},
+			Width: {
+				//画布宽度  (高度根据图片比例计算 单位upx)
+				Type: String,
+				default: 700
+			},
+			CanvasBg: {
+				//canvas画布背景色
+				Type: String,
+				default: '#ffffff'
+			},
+			Referrer: {
+				//推荐人信息
+				Type: String,
+				default: '省钱兄电竞精选好物'
+			},
+			ViewDetails: {
+				//描述提示文字
+				Type: String,
+				default: '长按或扫描识别二维码领券'
+			}
+		},
+		data() {
+			return {
+				loading: false,
+				tempFilePath: '',
+				canvasW: 0,
+				canvasH: 0,
+				canvasImgSrc: '',
+				ctx: null
+			};
+		},
+		methods: {
+			toSave(url) {
+				//#ifndef H5
+				uni.getImageInfo({
+					src: url,
+					success: function(image) {
+						console.log('图片信息:', JSON.stringify(image));
+						uni.saveImageToPhotosAlbum({
+							filePath: image.path,
+							success: function() {
+								console.log('save success');
+								uni.showToast({
+									title: '海报已保存相册',
+									icon: 'success',
+									duration: 2000
+								});
+							}
+						});
+					}
+				});
+				//#endif
+			},
+			async OnCanvas() {
+				this.loading = true;
+				// this.$queue.showLoading('海报生成中...');
+				_this.ctx = uni.createCanvasContext(_this.CanvasID, this);
+				const C_W = uni.upx2px(_this.Width), //canvas宽度
+					C_P = uni.upx2px(30), //canvas Paddng 间距
+					C_Q = uni.upx2px(150); //二维码或太阳码宽高
+				let _strlineW = 0; //文本宽度
+				let _imgInfo = await _this.getImageInfo({
+					imgSrc: _this.imgSrc
+				}); //广告图
+				let _QrCode = await _this.getImageInfo({
+					imgSrc: _this.QrSrc
+				}); //二维码或太阳码
+				let r = [_imgInfo.width, _imgInfo.height];
+				let q = [_QrCode.width, _QrCode.height];
+				let imgW = C_W - C_P * 2;
+				if (r[0] != imgW) {
+					r[1] = Math.floor((imgW / r[0]) * r[1]);
+					r[0] = imgW;
+				}
+				if (q[0] != C_Q) {
+					q[1] = Math.floor((C_Q / q[0]) * q[1]);
+					q[0] = C_Q;
+				}
+				_this.canvasW = C_W;
+				_this.canvasH = r[1] + q[1] + 128;
+				_this.ctx.setFillStyle(_this.CanvasBg); //canvas背景颜色
+				_this.ctx.fillRect(0, 0, C_W, _this.canvasH); //canvas画布大小
+
+				//添加图片展示
+				_this.ctx.drawImage(_imgInfo.path, C_P, C_P, r[0], r[1]);
+				//添加图片展示 end
+
+				//设置文本
+				//#ifdef H5
+				_this.ctx.setFontSize(uni.upx2px(32)); //设置标题字体大小
+				//#endif
+				//#ifdef APP-PLUS
+				_this.ctx.setFontSize(uni.upx2px(36)); //设置标题字体大小
+				_this.Title=_this.Title.substring(0,20)
+				//#endif
+				_this.ctx.setFillStyle(_this.TitleColor); //设置标题文本颜色
+				let _strLastIndex = 0; //每次开始截取的字符串的索引
+				let _strHeight = r[1] + C_P * 2 + 10; //绘制字体距离canvas顶部的初始高度
+				let _num = 1;
+				for (let i = 0; i < _this.Title.length; i++) {
+					_strlineW += _this.ctx.measureText(_this.Title[i]).width;
+					if (_strlineW > r[0]) {
+						if (_num == 2 && _this.LineType) {
+							//文字换行数量大于二进行省略号处理
+							_this.ctx.fillText(_this.Title.substring(_strLastIndex, i - 8) + '...', C_P, _strHeight);
+							_strlineW = 0;
+							_strLastIndex = i;
+							_num++;
+							break;
+						} else {
+							_this.ctx.fillText(_this.Title.substring(_strLastIndex, i), C_P, _strHeight);
+							_strlineW = 0;
+							_strHeight += 20;
+							_strLastIndex = i;
+							_num++;
+						}
+					} else if (i == _this.Title.length - 1) {
+						_this.ctx.fillText(_this.Title.substring(_strLastIndex, i + 1), C_P, _strHeight);
+						_strlineW = 0;
+					}
+				}
+				//设置文本 end
+				//设置价格
+				_strlineW = C_P;
+				_strHeight += uni.upx2px(60);
+				if (_num == 1) {
+					_strHeight += 20; //单行标题时占位符
+				}
+				if (_this.PriceTxt != '') {
+					//判断是否有销售价格
+					_this.ctx.setFillStyle(_this.PriceColor);
+					_this.ctx.setFontSize(uni.upx2px(38));
+					_this.ctx.fillText('券后价 ¥' + _this.PriceTxt, _strlineW, _strHeight); //商品价格
+					_strlineW += _this.ctx.measureText('券后价 ¥' + _this.PriceTxt).width + uni.upx2px(10);
+				}
+				// #ifdef H5
+				if (_this.PriceTxt != '' && _this.OriginalTxt != '') {
+					//判断是否有销售价格且原价
+					_this.ctx.setFillStyle(_this.OriginalColor);
+					_this.ctx.setFontSize(uni.upx2px(24));
+					_this.ctx.fillText(_this.OriginalTxt, _strlineW, _strHeight); //商品原价
+				}
+				// #endif
+				_this.ctx.strokeStyle = _this.OriginalColor;
+				_this.ctx.moveTo(_strlineW, _strHeight - uni.upx2px(10)); //起点
+				_this.ctx.lineTo(_strlineW + _this.ctx.measureText(_this.OriginalTxt).width, _strHeight - uni.upx2px(10)); //终点
+				_this.ctx.stroke();
+				//设置价格 end
+
+				//添加二维码
+				_strHeight += uni.upx2px(20);
+				_this.ctx.drawImage(_QrCode.path, r[0] - q[0] + C_P, _strHeight, q[0], q[1]);
+				//添加二维码 end
+
+				//添加推荐人与描述
+				_this.ctx.setFillStyle(_this.TitleColor);
+				_this.ctx.setFontSize(uni.upx2px(30));
+				_this.ctx.fillText(_this.Referrer, C_P, _strHeight + q[1] / 2);
+				_this.ctx.setFillStyle(_this.OriginalColor);
+				_this.ctx.setFontSize(uni.upx2px(24));
+				_this.ctx.fillText(_this.ViewDetails, C_P, _strHeight + q[1] / 2 + 20);
+				//添加推荐人与描述 end
+				//延迟后渲染至canvas上
+				setTimeout(function() {
+					_this.ctx.draw(true, ret => {
+						_this.getNewImage();
+					});
+				}, 600);
+			},
+			async getImageInfo({
+				imgSrc
+			}) {
+				return new Promise((resolve, errs) => {
+					uni.getImageInfo({
+						src: imgSrc,
+						success: function(image) {
+							resolve(image);
+						},
+						fail(err) {
+							errs(err);
+							_this.$queue.showToast('海报生成失败');
+							uni.hideLoading()
+						}
+					});
+				});
+			},
+			getNewImage() {
+				uni.canvasToTempFilePath({
+						canvasId: _this.CanvasID,
+						quality: 1,
+						complete: res => {
+							_this.tempFilePath = res.tempFilePath;
+							_this.$emit('success', res);
+							_this.loading = false;
+							_this.$queue.showToast('长按图片保存海报');
+							uni.hideLoading()
+						}
+					},
+					this
+				);
+			}
+		},
+		mounted() {
+			_this = this;
+			this.OnCanvas();
+		}
+	};
+</script>
+
+<style></style>

+ 248 - 0
components/wm-poster/wm-posterorders.vue

@@ -0,0 +1,248 @@
+<template>
+	<view style="background: #FFFFFF;">
+		<!-- <view v-if="loading"></view> -->
+		<canvas v-if="!tempFilePath" :canvas-id="CanvasID" :style="{ width: canvasW + 'px', height: canvasH + 'px' }"></canvas>
+		<image v-else lazy-load :src="tempFilePath" mode="widthFix" class="is-response" @longpress="toSave(tempFilePath)"></image>
+	</view>
+</template>
+
+<script>
+	var _this;
+	export default {
+		name: 'wm-poster',
+		props: {
+			CanvasID: {
+				//CanvasID 等同于 canvas-id
+				Type: String,
+				default: 'PosterCanvas'
+			},
+			imgSrc: {
+				//展示图
+				Type: String,
+				default: ''
+			},
+			QrSrc: {
+				//二维码
+				Type: String,
+				default: ''
+			},
+			Title: {
+				//文本内容
+				Type: String,
+				default: ''
+			},
+			TitleColor: {
+				//标题颜色
+				Type: String,
+				default: '#000000'
+			},
+			LineType: {
+				//标题显示行数		(注超出2行显示会导致画布布局絮乱)
+				Type: [String, Boolean],
+				default: true
+			},
+			PriceTxt: {
+				//价格值
+				Type: String,
+				default: ''
+			},
+			PriceColor: {
+				//价格颜色
+				Type: String,
+				default: '#e31d1a'
+			},
+			OriginalTxt: {
+				//原价值
+				Type: String,
+				default: ''
+			},
+			OriginalColor: {
+				//默认颜色(如原价与扫描二维码颜色)
+				Type: String,
+				default: '#b8b8b8'
+			},
+			Width: {
+				//画布宽度  (高度根据图片比例计算 单位upx)
+				Type: String,
+				default: 700
+			},
+			CanvasBg: {
+				//canvas画布背景色
+				Type: String,
+				default: '#ffffff'
+			},
+			Referrer: {
+				//推荐人信息
+				Type: String,
+				default: ''
+			},
+			ViewDetails: {
+				//描述提示文字
+				Type: String,
+				default: '长按或扫描识别二维码'
+			}
+		},
+		data() {
+			return {
+				loading: false,
+				tempFilePath: '',
+				canvasW: 0,
+				canvasH: 0,
+				canvasImgSrc: '',
+				ctx: null
+			};
+		},
+		methods: {
+			toSave(url) {
+				//#ifndef H5
+				uni.getImageInfo({
+					src: url,
+					success: function(image) {
+						console.log('图片信息:', JSON.stringify(image));
+						uni.saveImageToPhotosAlbum({
+							filePath: image.path,
+							success: function() {
+								console.log('save success');
+								uni.showToast({
+									title: '海报已保存相册',
+									icon: 'success',
+									duration: 2000
+								});
+							}
+						});
+					}
+				});
+				//#endif
+			},
+			async OnCanvas() {
+				this.loading = true;
+				// this.$queue.showLoading('海报生成中...');
+				_this.ctx = uni.createCanvasContext(_this.CanvasID, this);
+				const C_W = uni.upx2px(_this.Width), //canvas宽度
+					C_P = uni.upx2px(30), //canvas Paddng 间距
+					C_Q = uni.upx2px(150); //二维码或太阳码宽高
+				let _strlineW = 0; //文本宽度
+				let _imgInfo = await _this.getImageInfo({
+					imgSrc: _this.imgSrc
+				}); //广告图
+				let _QrCode = await _this.getImageInfo({
+					imgSrc: _this.QrSrc
+				}); //二维码或太阳码
+				let r = [_imgInfo.width, _imgInfo.height];
+				let q = [_QrCode.width, _QrCode.height];
+				let imgW = C_W - C_P * 2-8;
+				if (r[0] != imgW) {
+					r[1] = Math.floor((imgW / r[0]) * r[1]);
+					r[0] = imgW;
+				}
+				if (q[0] != C_Q) {
+					q[1] = Math.floor((C_Q / q[0]) * q[1]);
+					q[0] = C_Q;
+				}
+				_this.canvasW = C_W;
+				_this.canvasH = r[1] + q[1] + 68;
+				_this.ctx.setFillStyle(_this.CanvasBg); //canvas背景颜色
+				_this.ctx.fillRect(0, 0, C_W, _this.canvasH); //canvas画布大小
+
+				//添加图片展示
+				_this.ctx.drawImage(_imgInfo.path, C_P, C_P, r[0], r[1]);
+				//添加图片展示 end
+
+				//设置文本
+				//#ifdef H5
+				_this.ctx.setFontSize(uni.upx2px(32)); //设置标题字体大小
+				//#endif
+				//#ifdef APP-PLUS
+				_this.ctx.setFontSize(uni.upx2px(36)); //设置标题字体大小
+				_this.Title=_this.Title.substring(0,20)
+				//#endif
+				_this.ctx.setFillStyle(_this.TitleColor); //设置标题文本颜色
+				let _strLastIndex = 0; //每次开始截取的字符串的索引
+				let _strHeight = r[1] + C_P * 2 + 10; //绘制字体距离canvas顶部的初始高度
+				let _num = 1;
+				for (let i = 0; i < _this.Title.length; i++) {
+					_strlineW += _this.ctx.measureText(_this.Title[i]).width;
+					if (_strlineW > r[0]) {
+						if (_num == 2 && _this.LineType) {
+							//文字换行数量大于二进行省略号处理
+							_this.ctx.fillText(_this.Title.substring(_strLastIndex, i - 8) + '...', C_P, _strHeight);
+							_strlineW = 0;
+							_strLastIndex = i;
+							_num++;
+							break;
+						} else {
+							_this.ctx.fillText(_this.Title.substring(_strLastIndex, i), C_P, _strHeight);
+							_strlineW = 0;
+							_strHeight += 20;
+							_strLastIndex = i;
+							_num++;
+						}
+					} else if (i == _this.Title.length - 1) {
+						_this.ctx.fillText(_this.Title.substring(_strLastIndex, i + 1), C_P, _strHeight);
+						_strlineW = 0;
+					}
+				}
+				//设置文本 end
+			
+
+				//添加二维码
+				_strHeight += uni.upx2px(20);
+				_this.ctx.drawImage(_QrCode.path, r[0] - q[0] + C_P, _strHeight, q[0], q[1]);
+				//添加二维码 end
+
+				//添加推荐人与描述
+				_this.ctx.setFillStyle(_this.TitleColor);
+				_this.ctx.setFontSize(uni.upx2px(30));
+				_this.ctx.fillText(_this.Referrer, C_P, _strHeight + q[1] / 2);
+				_this.ctx.setFillStyle(_this.OriginalColor);
+				_this.ctx.setFontSize(uni.upx2px(24));
+				_this.ctx.fillText(_this.ViewDetails, C_P, _strHeight + q[1] / 2 + 20);
+				//添加推荐人与描述 end
+				//延迟后渲染至canvas上
+				setTimeout(function() {
+					_this.ctx.draw(true, ret => {
+						_this.getNewImage();
+					});
+				}, 600);
+			},
+			async getImageInfo({
+				imgSrc
+			}) {
+				return new Promise((resolve, errs) => {
+					uni.getImageInfo({
+						src: imgSrc,
+						success: function(image) {
+							resolve(image);
+						},
+						fail(err) {
+							errs(err);
+							_this.$queue.showToast('海报生成失败');
+							uni.hideLoading()
+						}
+					});
+				});
+			},
+			getNewImage() {
+				uni.canvasToTempFilePath({
+						canvasId: _this.CanvasID,
+						quality: 1,
+						complete: res => {
+							_this.tempFilePath = res.tempFilePath;
+							_this.$emit('success', res);
+							_this.loading = false;
+							_this.$queue.showToast('长按图片保存海报');
+							uni.hideLoading()
+						}
+					},
+					this
+				);
+			}
+		},
+		mounted() {
+			_this = this;
+			this.OnCanvas();
+		}
+	};
+</script>
+
+<style></style>

+ 261 - 0
components/wm-poster/wm-posters.vue

@@ -0,0 +1,261 @@
+<template>
+	<view style="background: #FFFFFF;">
+		<canvas v-if="!tempFilePath" :canvas-id="CanvasID" :style="{ width: canvasW + 'px', height: canvasH + 'px' }"></canvas>
+		<image v-else lazy-load :src="tempFilePath" mode="widthFix" class="is-response" @longpress="toSave(tempFilePath)"></image>
+	</view>
+</template>
+
+<script>
+	var _this;
+	export default {
+		name: 'wm-poster',
+		props: {
+			CanvasID: { //CanvasID 等同于 canvas-id
+				Type: String,
+				default: 'PosterCanvas'
+			},
+			imgSrc: { //展示图
+				Type: String,
+				default: ''
+			},
+			QrSrc: { //二维码
+				Type: String,
+				default: ''
+			},
+			Title: { //文本内容
+				Type: String,
+				default: '省钱兄电竞'
+			},
+			TitleColor: { //标题颜色
+				Type: String,
+				default: '#000000'
+			},
+			LineType: { //标题显示行数		(注超出2行显示会导致画布布局絮乱)
+				Type: [String, Boolean],
+				default: true
+			},
+			PriceTxt: { //价格值
+				Type: String,
+				default: '99.99'
+			},
+			PriceColor: { //价格颜色
+				Type: String,
+				default: '#e31d1a'
+			},
+			OriginalTxt: { //原价值
+				Type: String,
+				default: '199.99'
+			},
+			OriginalColor: { //默认颜色(如原价与扫描二维码颜色)
+				Type: String,
+				default: '#b8b8b8'
+			},
+			Width: { //画布宽度  (高度根据图片比例计算 单位upx)
+				Type: String,
+				default: 700
+			},
+			CanvasBg: { //canvas画布背景色
+				Type: String,
+				default: '#ffffff'
+			},
+			Referrer: { //推荐人信息
+				Type: String,
+				default: '省钱兄电竞精选好物'
+			},
+			ViewDetails: { //描述提示文字
+				Type: String,
+				default: '长按或扫描二维码免费领取'
+			}
+		},
+		data() {
+			return {
+				tempFilePath: '',
+				canvasW: 0,
+				canvasH: 0,
+				canvasImgSrc: '',
+				ctx: null
+			};
+		},
+		methods: {
+			toSave(url) {
+				//#ifndef H5
+				uni.getImageInfo({
+					src: url,
+					success: function(image) {
+						console.log('图片信息:', JSON.stringify(image));
+						uni.saveImageToPhotosAlbum({
+							filePath: image.path,
+							success: function() {
+								console.log('save success');
+								uni.showToast({
+									title: '海报已保存相册',
+									icon: 'success',
+									duration: 2000
+								});
+							}
+						});
+					}
+				});
+
+				//#endif
+			},
+			async OnCanvas() {
+				 
+				this.$queue.showLoading('海报生成中...');
+
+				_this.ctx = uni.createCanvasContext(_this.CanvasID, this);
+				const C_W = uni.upx2px(_this.Width), //canvas宽度
+					C_P = uni.upx2px(30), //canvas Paddng 间距
+					C_Q = uni.upx2px(150); //二维码或太阳码宽高
+				let _strlineW = 0; //文本宽度
+				let _imgInfo = await _this.getImageInfo({
+					imgSrc: _this.imgSrc
+				}); //广告图
+				let _QrCode = await _this.getImageInfo({
+					imgSrc: _this.QrSrc
+				}); //二维码或太阳码
+				let r = [_imgInfo.width, _imgInfo.height];
+				let q = [_QrCode.width, _QrCode.height];
+				let imgW = C_W - C_P * 2;
+				if (r[0] != imgW) {
+					r[1] = Math.floor((imgW / r[0]) * r[1]);
+					r[0] = imgW;
+				}
+				if (q[0] != C_Q) {
+					q[1] = Math.floor((C_Q / q[0]) * q[1]);
+					q[0] = C_Q;
+				}
+				_this.canvasW = C_W;
+				_this.canvasH = r[1] + q[1] + 128;
+				_this.ctx.setFillStyle(_this.CanvasBg); //canvas背景颜色
+				_this.ctx.fillRect(0, 0, C_W, _this.canvasH); //canvas画布大小
+
+				//添加图片展示
+				_this.ctx.drawImage(_imgInfo.path, C_P, C_P, r[0], r[1]);
+				//添加图片展示 end
+
+				//设置文本
+				//#ifdef H5
+				_this.ctx.setFontSize(uni.upx2px(32)); //设置标题字体大小
+				//#endif
+				//#ifndef H5
+				_this.ctx.setFontSize(uni.upx2px(36)); //设置标题字体大小
+				//#endif
+				_this.ctx.setFillStyle(_this.TitleColor); //设置标题文本颜色
+				let _strLastIndex = 0; //每次开始截取的字符串的索引
+				let _strHeight = r[1] + C_P * 2 + 10; //绘制字体距离canvas顶部的初始高度
+				let _num = 1;
+				for (let i = 0; i < _this.Title.length; i++) {
+					_strlineW += _this.ctx.measureText(_this.Title[i]).width;
+					if (_strlineW > r[0]) {
+						if (_num == 2 && _this.LineType) {
+							//文字换行数量大于二进行省略号处理
+							_this.ctx.fillText(_this.Title.substring(_strLastIndex, i - 8) + '...', C_P, _strHeight);
+							_strlineW = 0;
+							_strLastIndex = i;
+							_num++;
+							break;
+						} else {
+							_this.ctx.fillText(_this.Title.substring(_strLastIndex, i), C_P, _strHeight);
+							_strlineW = 0;
+							_strHeight += 20;
+							_strLastIndex = i;
+							_num++;
+						}
+					} else if (i == _this.Title.length - 1) {
+						_this.ctx.fillText(_this.Title.substring(_strLastIndex, i + 1), C_P, _strHeight);
+						_strlineW = 0;
+					}
+				}
+				//设置文本 end
+
+				//设置价格
+				_strlineW = C_P;
+				_strHeight += uni.upx2px(60);
+				if (_num == 1) {
+					_strHeight += 20; //单行标题时占位符
+				}
+
+				if (_this.PriceTxt != '') { //判断是否有销售价格
+					_this.ctx.setFillStyle(_this.PriceColor);
+					_this.ctx.setFontSize(uni.upx2px(38));
+					_this.ctx.fillText(_this.PriceTxt, _strlineW, _strHeight); //商品价格
+					_strlineW += _this.ctx.measureText(_this.PriceTxt).width + uni.upx2px(10);
+				}
+				if (_this.PriceTxt != '' && _this.OriginalTxt != '') { //判断是否有销售价格且原价
+					_this.ctx.setFillStyle(_this.OriginalColor);
+					_this.ctx.setFontSize(uni.upx2px(24));
+					_this.ctx.fillText(_this.OriginalTxt, _strlineW, _strHeight); //商品原价
+				}
+				_this.ctx.strokeStyle = _this.OriginalColor;
+				_this.ctx.moveTo(_strlineW, _strHeight - uni.upx2px(10)); //起点
+				_this.ctx.lineTo(_strlineW + _this.ctx.measureText(_this.OriginalTxt).width, _strHeight - uni.upx2px(10)); //终点
+				_this.ctx.stroke();
+				//设置价格 end
+
+				//添加二维码
+				_strHeight += uni.upx2px(20);
+				_this.ctx.drawImage(_QrCode.path, r[0] - q[0] + C_P, _strHeight, q[0], q[1]);
+				//添加二维码 end
+
+				//添加推荐人与描述
+				_this.ctx.setFillStyle(_this.TitleColor);
+				_this.ctx.setFontSize(uni.upx2px(30));
+				_this.ctx.fillText(_this.Referrer, C_P, _strHeight + q[1] / 2);
+				_this.ctx.setFillStyle(_this.OriginalColor);
+				_this.ctx.setFontSize(uni.upx2px(24));
+				_this.ctx.fillText(_this.ViewDetails, C_P, _strHeight + q[1] / 2 + 20);
+				//添加推荐人与描述 end
+
+				//延迟后渲染至canvas上
+				setTimeout(function() {
+					_this.ctx.draw(true, (ret) => {
+						_this.getNewImage();
+					});
+
+				}, 600);
+				// setTimeout(function () {
+				//     uni.hideLoading();
+
+				// }, 3000);
+			},
+			async getImageInfo({
+				imgSrc
+			}) {
+				return new Promise((resolve, errs) => {
+					uni.getImageInfo({
+						src: imgSrc,
+						success: function(image) {
+							resolve(image);
+						},
+						fail(err) {
+							errs(err);
+							uni.hideLoading();
+							_this.$queue.showToast("海报生成失败")
+						}
+					});
+				});
+
+			},
+			getNewImage() {
+				uni.canvasToTempFilePath({
+					canvasId: _this.CanvasID,
+					quality: 1,
+					complete: (res) => {
+						_this.tempFilePath = res.tempFilePath;
+						_this.$emit('success', res);
+						_this.$queue.showToast("长按图片保存海报")
+						// uni.hideLoading();
+					}
+				}, this);
+			}
+
+		},
+		mounted() {
+			 _this = this;
+			this.OnCanvas();
+		}
+	};
+</script>
+
+<style></style>

BIN
file/taobao.jks


BIN
file/测试证书/task.mobileprovision


BIN
file/测试证书/证书.p12


+ 1009 - 0
js_sdk/QuShe-SharerPoster/QS-SharePoster/QRCodeAlg.js

@@ -0,0 +1,1009 @@
+ /**
+     * 获取单个字符的utf8编码
+     * unicode BMP平面约65535个字符
+     * @param {num} code
+     * return {array}
+     */
+    function unicodeFormat8(code) {
+        // 1 byte
+        var c0, c1, c2;
+        if (code < 128) {
+            return [code];
+            // 2 bytes
+        } else if (code < 2048) {
+            c0 = 192 + (code >> 6);
+            c1 = 128 + (code & 63);
+            return [c0, c1];
+            // 3 bytes
+        } else {
+            c0 = 224 + (code >> 12);
+            c1 = 128 + (code >> 6 & 63);
+            c2 = 128 + (code & 63);
+            return [c0, c1, c2];
+        }
+    }
+    /**
+     * 获取字符串的utf8编码字节串
+     * @param {string} string
+     * @return {array}
+     */
+    function getUTF8Bytes(string) {
+        var utf8codes = [];
+        for (var i = 0; i < string.length; i++) {
+            var code = string.charCodeAt(i);
+            var utf8 = unicodeFormat8(code);
+            for (var j = 0; j < utf8.length; j++) {
+                utf8codes.push(utf8[j]);
+            }
+        }
+        return utf8codes;
+    }
+    /**
+     * 二维码算法实现
+     * @param {string} data              要编码的信息字符串
+     * @param {num} errorCorrectLevel 纠错等级
+     */
+    export default function QRCodeAlg(data, errorCorrectLevel) {
+        this.typeNumber = -1; //版本
+        this.errorCorrectLevel = errorCorrectLevel;
+        this.modules = null; //二维矩阵,存放最终结果
+        this.moduleCount = 0; //矩阵大小
+        this.dataCache = null; //数据缓存
+        this.rsBlocks = null; //版本数据信息
+        this.totalDataCount = -1; //可使用的数据量
+        this.data = data;
+        this.utf8bytes = getUTF8Bytes(data);
+        this.make();
+    }
+    QRCodeAlg.prototype = {
+        constructor: QRCodeAlg,
+        /**
+         * 获取二维码矩阵大小
+         * @return {num} 矩阵大小
+         */
+        getModuleCount: function () {
+            return this.moduleCount;
+        },
+        /**
+         * 编码
+         */
+        make: function () {
+            this.getRightType();
+            this.dataCache = this.createData();
+            this.createQrcode();
+        },
+        /**
+         * 设置二位矩阵功能图形
+         * @param  {bool} test 表示是否在寻找最好掩膜阶段
+         * @param  {num} maskPattern 掩膜的版本
+         */
+        makeImpl: function (maskPattern) {
+            this.moduleCount = this.typeNumber * 4 + 17;
+            this.modules = new Array(this.moduleCount);
+            for (var row = 0; row < this.moduleCount; row++) {
+                this.modules[row] = new Array(this.moduleCount);
+            }
+            this.setupPositionProbePattern(0, 0);
+            this.setupPositionProbePattern(this.moduleCount - 7, 0);
+            this.setupPositionProbePattern(0, this.moduleCount - 7);
+            this.setupPositionAdjustPattern();
+            this.setupTimingPattern();
+            this.setupTypeInfo(true, maskPattern);
+            if (this.typeNumber >= 7) {
+                this.setupTypeNumber(true);
+            }
+            this.mapData(this.dataCache, maskPattern);
+        },
+        /**
+         * 设置二维码的位置探测图形
+         * @param  {num} row 探测图形的中心横坐标
+         * @param  {num} col 探测图形的中心纵坐标
+         */
+        setupPositionProbePattern: function (row, col) {
+            for (var r = -1; r <= 7; r++) {
+                if (row + r <= -1 || this.moduleCount <= row + r) continue;
+                for (var c = -1; c <= 7; c++) {
+                    if (col + c <= -1 || this.moduleCount <= col + c) continue;
+                    if ((0 <= r && r <= 6 && (c == 0 || c == 6)) || (0 <= c && c <= 6 && (r == 0 || r == 6)) || (2 <= r && r <= 4 && 2 <= c && c <= 4)) {
+                        this.modules[row + r][col + c] = true;
+                    } else {
+                        this.modules[row + r][col + c] = false;
+                    }
+                }
+            }
+        },
+        /**
+         * 创建二维码
+         * @return {[type]} [description]
+         */
+        createQrcode: function () {
+            var minLostPoint = 0;
+            var pattern = 0;
+            var bestModules = null;
+            for (var i = 0; i < 8; i++) {
+                this.makeImpl(i);
+                var lostPoint = QRUtil.getLostPoint(this);
+                if (i == 0 || minLostPoint > lostPoint) {
+                    minLostPoint = lostPoint;
+                    pattern = i;
+                    bestModules = this.modules;
+                }
+            }
+            this.modules = bestModules;
+            this.setupTypeInfo(false, pattern);
+            if (this.typeNumber >= 7) {
+                this.setupTypeNumber(false);
+            }
+        },
+        /**
+         * 设置定位图形
+         * @return {[type]} [description]
+         */
+        setupTimingPattern: function () {
+            for (var r = 8; r < this.moduleCount - 8; r++) {
+                if (this.modules[r][6] != null) {
+                    continue;
+                }
+                this.modules[r][6] = (r % 2 == 0);
+                if (this.modules[6][r] != null) {
+                    continue;
+                }
+                this.modules[6][r] = (r % 2 == 0);
+            }
+        },
+        /**
+         * 设置矫正图形
+         * @return {[type]} [description]
+         */
+        setupPositionAdjustPattern: function () {
+            var pos = QRUtil.getPatternPosition(this.typeNumber);
+            for (var i = 0; i < pos.length; i++) {
+                for (var j = 0; j < pos.length; j++) {
+                    var row = pos[i];
+                    var col = pos[j];
+                    if (this.modules[row][col] != null) {
+                        continue;
+                    }
+                    for (var r = -2; r <= 2; r++) {
+                        for (var c = -2; c <= 2; c++) {
+                            if (r == -2 || r == 2 || c == -2 || c == 2 || (r == 0 && c == 0)) {
+                                this.modules[row + r][col + c] = true;
+                            } else {
+                                this.modules[row + r][col + c] = false;
+                            }
+                        }
+                    }
+                }
+            }
+        },
+        /**
+         * 设置版本信息(7以上版本才有)
+         * @param  {bool} test 是否处于判断最佳掩膜阶段
+         * @return {[type]}      [description]
+         */
+        setupTypeNumber: function (test) {
+            var bits = QRUtil.getBCHTypeNumber(this.typeNumber);
+            for (var i = 0; i < 18; i++) {
+                var mod = (!test && ((bits >> i) & 1) == 1);
+                this.modules[Math.floor(i / 3)][i % 3 + this.moduleCount - 8 - 3] = mod;
+                this.modules[i % 3 + this.moduleCount - 8 - 3][Math.floor(i / 3)] = mod;
+            }
+        },
+        /**
+         * 设置格式信息(纠错等级和掩膜版本)
+         * @param  {bool} test
+         * @param  {num} maskPattern 掩膜版本
+         * @return {}
+         */
+        setupTypeInfo: function (test, maskPattern) {
+            var data = (QRErrorCorrectLevel[this.errorCorrectLevel] << 3) | maskPattern;
+            var bits = QRUtil.getBCHTypeInfo(data);
+            // vertical
+            for (var i = 0; i < 15; i++) {
+                var mod = (!test && ((bits >> i) & 1) == 1);
+                if (i < 6) {
+                    this.modules[i][8] = mod;
+                } else if (i < 8) {
+                    this.modules[i + 1][8] = mod;
+                } else {
+                    this.modules[this.moduleCount - 15 + i][8] = mod;
+                }
+                // horizontal
+                var mod = (!test && ((bits >> i) & 1) == 1);
+                if (i < 8) {
+                    this.modules[8][this.moduleCount - i - 1] = mod;
+                } else if (i < 9) {
+                    this.modules[8][15 - i - 1 + 1] = mod;
+                } else {
+                    this.modules[8][15 - i - 1] = mod;
+                }
+            }
+            // fixed module
+            this.modules[this.moduleCount - 8][8] = (!test);
+        },
+        /**
+         * 数据编码
+         * @return {[type]} [description]
+         */
+        createData: function () {
+            var buffer = new QRBitBuffer();
+            var lengthBits = this.typeNumber > 9 ? 16 : 8;
+            buffer.put(4, 4); //添加模式
+            buffer.put(this.utf8bytes.length, lengthBits);
+            for (var i = 0, l = this.utf8bytes.length; i < l; i++) {
+                buffer.put(this.utf8bytes[i], 8);
+            }
+            if (buffer.length + 4 <= this.totalDataCount * 8) {
+                buffer.put(0, 4);
+            }
+            // padding
+            while (buffer.length % 8 != 0) {
+                buffer.putBit(false);
+            }
+            // padding
+            while (true) {
+                if (buffer.length >= this.totalDataCount * 8) {
+                    break;
+                }
+                buffer.put(QRCodeAlg.PAD0, 8);
+                if (buffer.length >= this.totalDataCount * 8) {
+                    break;
+                }
+                buffer.put(QRCodeAlg.PAD1, 8);
+            }
+            return this.createBytes(buffer);
+        },
+        /**
+         * 纠错码编码
+         * @param  {buffer} buffer 数据编码
+         * @return {[type]}
+         */
+        createBytes: function (buffer) {
+            var offset = 0;
+            var maxDcCount = 0;
+            var maxEcCount = 0;
+            var length = this.rsBlock.length / 3;
+            var rsBlocks = new Array();
+            for (var i = 0; i < length; i++) {
+                var count = this.rsBlock[i * 3 + 0];
+                var totalCount = this.rsBlock[i * 3 + 1];
+                var dataCount = this.rsBlock[i * 3 + 2];
+                for (var j = 0; j < count; j++) {
+                    rsBlocks.push([dataCount, totalCount]);
+                }
+            }
+            var dcdata = new Array(rsBlocks.length);
+            var ecdata = new Array(rsBlocks.length);
+            for (var r = 0; r < rsBlocks.length; r++) {
+                var dcCount = rsBlocks[r][0];
+                var ecCount = rsBlocks[r][1] - dcCount;
+                maxDcCount = Math.max(maxDcCount, dcCount);
+                maxEcCount = Math.max(maxEcCount, ecCount);
+                dcdata[r] = new Array(dcCount);
+                for (var i = 0; i < dcdata[r].length; i++) {
+                    dcdata[r][i] = 0xff & buffer.buffer[i + offset];
+                }
+                offset += dcCount;
+                var rsPoly = QRUtil.getErrorCorrectPolynomial(ecCount);
+                var rawPoly = new QRPolynomial(dcdata[r], rsPoly.getLength() - 1);
+                var modPoly = rawPoly.mod(rsPoly);
+                ecdata[r] = new Array(rsPoly.getLength() - 1);
+                for (var i = 0; i < ecdata[r].length; i++) {
+                    var modIndex = i + modPoly.getLength() - ecdata[r].length;
+                    ecdata[r][i] = (modIndex >= 0) ? modPoly.get(modIndex) : 0;
+                }
+            }
+            var data = new Array(this.totalDataCount);
+            var index = 0;
+            for (var i = 0; i < maxDcCount; i++) {
+                for (var r = 0; r < rsBlocks.length; r++) {
+                    if (i < dcdata[r].length) {
+                        data[index++] = dcdata[r][i];
+                    }
+                }
+            }
+            for (var i = 0; i < maxEcCount; i++) {
+                for (var r = 0; r < rsBlocks.length; r++) {
+                    if (i < ecdata[r].length) {
+                        data[index++] = ecdata[r][i];
+                    }
+                }
+            }
+            return data;
+
+        },
+        /**
+         * 布置模块,构建最终信息
+         * @param  {} data
+         * @param  {} maskPattern
+         * @return {}
+         */
+        mapData: function (data, maskPattern) {
+            var inc = -1;
+            var row = this.moduleCount - 1;
+            var bitIndex = 7;
+            var byteIndex = 0;
+            for (var col = this.moduleCount - 1; col > 0; col -= 2) {
+                if (col == 6) col--;
+                while (true) {
+                    for (var c = 0; c < 2; c++) {
+                        if (this.modules[row][col - c] == null) {
+                            var dark = false;
+                            if (byteIndex < data.length) {
+                                dark = (((data[byteIndex] >>> bitIndex) & 1) == 1);
+                            }
+                            var mask = QRUtil.getMask(maskPattern, row, col - c);
+                            if (mask) {
+                                dark = !dark;
+                            }
+                            this.modules[row][col - c] = dark;
+                            bitIndex--;
+                            if (bitIndex == -1) {
+                                byteIndex++;
+                                bitIndex = 7;
+                            }
+                        }
+                    }
+                    row += inc;
+                    if (row < 0 || this.moduleCount <= row) {
+                        row -= inc;
+                        inc = -inc;
+                        break;
+                    }
+                }
+            }
+        }
+    };
+    /**
+     * 填充字段
+     */
+    QRCodeAlg.PAD0 = 0xEC;
+    QRCodeAlg.PAD1 = 0x11;
+    //---------------------------------------------------------------------
+    // 纠错等级对应的编码
+    //---------------------------------------------------------------------
+    var QRErrorCorrectLevel = [1, 0, 3, 2];
+    //---------------------------------------------------------------------
+    // 掩膜版本
+    //---------------------------------------------------------------------
+    var QRMaskPattern = {
+        PATTERN000: 0,
+        PATTERN001: 1,
+        PATTERN010: 2,
+        PATTERN011: 3,
+        PATTERN100: 4,
+        PATTERN101: 5,
+        PATTERN110: 6,
+        PATTERN111: 7
+    };
+    //---------------------------------------------------------------------
+    // 工具类
+    //---------------------------------------------------------------------
+    var QRUtil = {
+        /*
+        每个版本矫正图形的位置
+         */
+        PATTERN_POSITION_TABLE: [
+            [],
+            [6, 18],
+            [6, 22],
+            [6, 26],
+            [6, 30],
+            [6, 34],
+            [6, 22, 38],
+            [6, 24, 42],
+            [6, 26, 46],
+            [6, 28, 50],
+            [6, 30, 54],
+            [6, 32, 58],
+            [6, 34, 62],
+            [6, 26, 46, 66],
+            [6, 26, 48, 70],
+            [6, 26, 50, 74],
+            [6, 30, 54, 78],
+            [6, 30, 56, 82],
+            [6, 30, 58, 86],
+            [6, 34, 62, 90],
+            [6, 28, 50, 72, 94],
+            [6, 26, 50, 74, 98],
+            [6, 30, 54, 78, 102],
+            [6, 28, 54, 80, 106],
+            [6, 32, 58, 84, 110],
+            [6, 30, 58, 86, 114],
+            [6, 34, 62, 90, 118],
+            [6, 26, 50, 74, 98, 122],
+            [6, 30, 54, 78, 102, 126],
+            [6, 26, 52, 78, 104, 130],
+            [6, 30, 56, 82, 108, 134],
+            [6, 34, 60, 86, 112, 138],
+            [6, 30, 58, 86, 114, 142],
+            [6, 34, 62, 90, 118, 146],
+            [6, 30, 54, 78, 102, 126, 150],
+            [6, 24, 50, 76, 102, 128, 154],
+            [6, 28, 54, 80, 106, 132, 158],
+            [6, 32, 58, 84, 110, 136, 162],
+            [6, 26, 54, 82, 110, 138, 166],
+            [6, 30, 58, 86, 114, 142, 170]
+        ],
+        G15: (1 << 10) | (1 << 8) | (1 << 5) | (1 << 4) | (1 << 2) | (1 << 1) | (1 << 0),
+        G18: (1 << 12) | (1 << 11) | (1 << 10) | (1 << 9) | (1 << 8) | (1 << 5) | (1 << 2) | (1 << 0),
+        G15_MASK: (1 << 14) | (1 << 12) | (1 << 10) | (1 << 4) | (1 << 1),
+        /*
+        BCH编码格式信息
+         */
+        getBCHTypeInfo: function (data) {
+            var d = data << 10;
+            while (QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G15) >= 0) {
+                d ^= (QRUtil.G15 << (QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G15)));
+            }
+            return ((data << 10) | d) ^ QRUtil.G15_MASK;
+        },
+        /*
+        BCH编码版本信息
+         */
+        getBCHTypeNumber: function (data) {
+            var d = data << 12;
+            while (QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G18) >= 0) {
+                d ^= (QRUtil.G18 << (QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G18)));
+            }
+            return (data << 12) | d;
+        },
+        /*
+        获取BCH位信息
+         */
+        getBCHDigit: function (data) {
+            var digit = 0;
+            while (data != 0) {
+                digit++;
+                data >>>= 1;
+            }
+            return digit;
+        },
+        /*
+        获取版本对应的矫正图形位置
+         */
+        getPatternPosition: function (typeNumber) {
+            return QRUtil.PATTERN_POSITION_TABLE[typeNumber - 1];
+        },
+        /*
+        掩膜算法
+         */
+        getMask: function (maskPattern, i, j) {
+            switch (maskPattern) {
+                case QRMaskPattern.PATTERN000:
+                    return (i + j) % 2 == 0;
+                case QRMaskPattern.PATTERN001:
+                    return i % 2 == 0;
+                case QRMaskPattern.PATTERN010:
+                    return j % 3 == 0;
+                case QRMaskPattern.PATTERN011:
+                    return (i + j) % 3 == 0;
+                case QRMaskPattern.PATTERN100:
+                    return (Math.floor(i / 2) + Math.floor(j / 3)) % 2 == 0;
+                case QRMaskPattern.PATTERN101:
+                    return (i * j) % 2 + (i * j) % 3 == 0;
+                case QRMaskPattern.PATTERN110:
+                    return ((i * j) % 2 + (i * j) % 3) % 2 == 0;
+                case QRMaskPattern.PATTERN111:
+                    return ((i * j) % 3 + (i + j) % 2) % 2 == 0;
+                default:
+                    throw new Error("bad maskPattern:" + maskPattern);
+            }
+        },
+        /*
+        获取RS的纠错多项式
+         */
+        getErrorCorrectPolynomial: function (errorCorrectLength) {
+            var a = new QRPolynomial([1], 0);
+            for (var i = 0; i < errorCorrectLength; i++) {
+                a = a.multiply(new QRPolynomial([1, QRMath.gexp(i)], 0));
+            }
+            return a;
+        },
+        /*
+        获取评价
+         */
+        getLostPoint: function (qrCode) {
+            var moduleCount = qrCode.getModuleCount(),
+                lostPoint = 0,
+                darkCount = 0;
+            for (var row = 0; row < moduleCount; row++) {
+                var sameCount = 0;
+                var head = qrCode.modules[row][0];
+                for (var col = 0; col < moduleCount; col++) {
+                    var current = qrCode.modules[row][col];
+                    //level 3 评价
+                    if (col < moduleCount - 6) {
+                        if (current && !qrCode.modules[row][col + 1] && qrCode.modules[row][col + 2] && qrCode.modules[row][col + 3] && qrCode.modules[row][col + 4] && !qrCode.modules[row][col + 5] && qrCode.modules[row][col + 6]) {
+                            if (col < moduleCount - 10) {
+                                if (qrCode.modules[row][col + 7] && qrCode.modules[row][col + 8] && qrCode.modules[row][col + 9] && qrCode.modules[row][col + 10]) {
+                                    lostPoint += 40;
+                                }
+                            } else if (col > 3) {
+                                if (qrCode.modules[row][col - 1] && qrCode.modules[row][col - 2] && qrCode.modules[row][col - 3] && qrCode.modules[row][col - 4]) {
+                                    lostPoint += 40;
+                                }
+                            }
+                        }
+                    }
+                    //level 2 评价
+                    if ((row < moduleCount - 1) && (col < moduleCount - 1)) {
+                        var count = 0;
+                        if (current) count++;
+                        if (qrCode.modules[row + 1][col]) count++;
+                        if (qrCode.modules[row][col + 1]) count++;
+                        if (qrCode.modules[row + 1][col + 1]) count++;
+                        if (count == 0 || count == 4) {
+                            lostPoint += 3;
+                        }
+                    }
+                    //level 1 评价
+                    if (head ^ current) {
+                        sameCount++;
+                    } else {
+                        head = current;
+                        if (sameCount >= 5) {
+                            lostPoint += (3 + sameCount - 5);
+                        }
+                        sameCount = 1;
+                    }
+                    //level 4 评价
+                    if (current) {
+                        darkCount++;
+                    }
+                }
+            }
+            for (var col = 0; col < moduleCount; col++) {
+                var sameCount = 0;
+                var head = qrCode.modules[0][col];
+                for (var row = 0; row < moduleCount; row++) {
+                    var current = qrCode.modules[row][col];
+                    //level 3 评价
+                    if (row < moduleCount - 6) {
+                        if (current && !qrCode.modules[row + 1][col] && qrCode.modules[row + 2][col] && qrCode.modules[row + 3][col] && qrCode.modules[row + 4][col] && !qrCode.modules[row + 5][col] && qrCode.modules[row + 6][col]) {
+                            if (row < moduleCount - 10) {
+                                if (qrCode.modules[row + 7][col] && qrCode.modules[row + 8][col] && qrCode.modules[row + 9][col] && qrCode.modules[row + 10][col]) {
+                                    lostPoint += 40;
+                                }
+                            } else if (row > 3) {
+                                if (qrCode.modules[row - 1][col] && qrCode.modules[row - 2][col] && qrCode.modules[row - 3][col] && qrCode.modules[row - 4][col]) {
+                                    lostPoint += 40;
+                                }
+                            }
+                        }
+                    }
+                    //level 1 评价
+                    if (head ^ current) {
+                        sameCount++;
+                    } else {
+                        head = current;
+                        if (sameCount >= 5) {
+                            lostPoint += (3 + sameCount - 5);
+                        }
+                        sameCount = 1;
+                    }
+                }
+            }
+            // LEVEL4
+            var ratio = Math.abs(100 * darkCount / moduleCount / moduleCount - 50) / 5;
+            lostPoint += ratio * 10;
+            return lostPoint;
+        }
+
+    };
+    //---------------------------------------------------------------------
+    // QRMath使用的数学工具
+    //---------------------------------------------------------------------
+    var QRMath = {
+        /*
+        将n转化为a^m
+         */
+        glog: function (n) {
+            if (n < 1) {
+                throw new Error("glog(" + n + ")");
+            }
+            return QRMath.LOG_TABLE[n];
+        },
+        /*
+        将a^m转化为n
+         */
+        gexp: function (n) {
+            while (n < 0) {
+                n += 255;
+            }
+            while (n >= 256) {
+                n -= 255;
+            }
+            return QRMath.EXP_TABLE[n];
+        },
+        EXP_TABLE: new Array(256),
+        LOG_TABLE: new Array(256)
+
+    };
+    for (var i = 0; i < 8; i++) {
+        QRMath.EXP_TABLE[i] = 1 << i;
+    }
+    for (var i = 8; i < 256; i++) {
+        QRMath.EXP_TABLE[i] = QRMath.EXP_TABLE[i - 4] ^ QRMath.EXP_TABLE[i - 5] ^ QRMath.EXP_TABLE[i - 6] ^ QRMath.EXP_TABLE[i - 8];
+    }
+    for (var i = 0; i < 255; i++) {
+        QRMath.LOG_TABLE[QRMath.EXP_TABLE[i]] = i;
+    }
+    //---------------------------------------------------------------------
+    // QRPolynomial 多项式
+    //---------------------------------------------------------------------
+    /**
+     * 多项式类
+     * @param {Array} num   系数
+     * @param {num} shift a^shift
+     */
+    function QRPolynomial(num, shift) {
+        if (num.length == undefined) {
+            throw new Error(num.length + "/" + shift);
+        }
+        var offset = 0;
+        while (offset < num.length && num[offset] == 0) {
+            offset++;
+        }
+        this.num = new Array(num.length - offset + shift);
+        for (var i = 0; i < num.length - offset; i++) {
+            this.num[i] = num[i + offset];
+        }
+    }
+    QRPolynomial.prototype = {
+        get: function (index) {
+            return this.num[index];
+        },
+        getLength: function () {
+            return this.num.length;
+        },
+        /**
+         * 多项式乘法
+         * @param  {QRPolynomial} e 被乘多项式
+         * @return {[type]}   [description]
+         */
+        multiply: function (e) {
+            var num = new Array(this.getLength() + e.getLength() - 1);
+            for (var i = 0; i < this.getLength(); i++) {
+                for (var j = 0; j < e.getLength(); j++) {
+                    num[i + j] ^= QRMath.gexp(QRMath.glog(this.get(i)) + QRMath.glog(e.get(j)));
+                }
+            }
+            return new QRPolynomial(num, 0);
+        },
+        /**
+         * 多项式模运算
+         * @param  {QRPolynomial} e 模多项式
+         * @return {}
+         */
+        mod: function (e) {
+            var tl = this.getLength(),
+                el = e.getLength();
+            if (tl - el < 0) {
+                return this;
+            }
+            var num = new Array(tl);
+            for (var i = 0; i < tl; i++) {
+                num[i] = this.get(i);
+            }
+            while (num.length >= el) {
+                var ratio = QRMath.glog(num[0]) - QRMath.glog(e.get(0));
+
+                for (var i = 0; i < e.getLength(); i++) {
+                    num[i] ^= QRMath.gexp(QRMath.glog(e.get(i)) + ratio);
+                }
+                while (num[0] == 0) {
+                    num.shift();
+                }
+            }
+            return new QRPolynomial(num, 0);
+        }
+    };
+
+    //---------------------------------------------------------------------
+    // RS_BLOCK_TABLE
+    //---------------------------------------------------------------------
+    /*
+    二维码各个版本信息[块数, 每块中的数据块数, 每块中的信息块数]
+     */
+    var RS_BLOCK_TABLE = [
+        // L
+        // M
+        // Q
+        // H
+        // 1
+        [1, 26, 19],
+        [1, 26, 16],
+        [1, 26, 13],
+        [1, 26, 9],
+
+        // 2
+        [1, 44, 34],
+        [1, 44, 28],
+        [1, 44, 22],
+        [1, 44, 16],
+
+        // 3
+        [1, 70, 55],
+        [1, 70, 44],
+        [2, 35, 17],
+        [2, 35, 13],
+
+        // 4
+        [1, 100, 80],
+        [2, 50, 32],
+        [2, 50, 24],
+        [4, 25, 9],
+
+        // 5
+        [1, 134, 108],
+        [2, 67, 43],
+        [2, 33, 15, 2, 34, 16],
+        [2, 33, 11, 2, 34, 12],
+
+        // 6
+        [2, 86, 68],
+        [4, 43, 27],
+        [4, 43, 19],
+        [4, 43, 15],
+
+        // 7
+        [2, 98, 78],
+        [4, 49, 31],
+        [2, 32, 14, 4, 33, 15],
+        [4, 39, 13, 1, 40, 14],
+
+        // 8
+        [2, 121, 97],
+        [2, 60, 38, 2, 61, 39],
+        [4, 40, 18, 2, 41, 19],
+        [4, 40, 14, 2, 41, 15],
+
+        // 9
+        [2, 146, 116],
+        [3, 58, 36, 2, 59, 37],
+        [4, 36, 16, 4, 37, 17],
+        [4, 36, 12, 4, 37, 13],
+
+        // 10
+        [2, 86, 68, 2, 87, 69],
+        [4, 69, 43, 1, 70, 44],
+        [6, 43, 19, 2, 44, 20],
+        [6, 43, 15, 2, 44, 16],
+
+        // 11
+        [4, 101, 81],
+        [1, 80, 50, 4, 81, 51],
+        [4, 50, 22, 4, 51, 23],
+        [3, 36, 12, 8, 37, 13],
+
+        // 12
+        [2, 116, 92, 2, 117, 93],
+        [6, 58, 36, 2, 59, 37],
+        [4, 46, 20, 6, 47, 21],
+        [7, 42, 14, 4, 43, 15],
+
+        // 13
+        [4, 133, 107],
+        [8, 59, 37, 1, 60, 38],
+        [8, 44, 20, 4, 45, 21],
+        [12, 33, 11, 4, 34, 12],
+
+        // 14
+        [3, 145, 115, 1, 146, 116],
+        [4, 64, 40, 5, 65, 41],
+        [11, 36, 16, 5, 37, 17],
+        [11, 36, 12, 5, 37, 13],
+
+        // 15
+        [5, 109, 87, 1, 110, 88],
+        [5, 65, 41, 5, 66, 42],
+        [5, 54, 24, 7, 55, 25],
+        [11, 36, 12],
+
+        // 16
+        [5, 122, 98, 1, 123, 99],
+        [7, 73, 45, 3, 74, 46],
+        [15, 43, 19, 2, 44, 20],
+        [3, 45, 15, 13, 46, 16],
+
+        // 17
+        [1, 135, 107, 5, 136, 108],
+        [10, 74, 46, 1, 75, 47],
+        [1, 50, 22, 15, 51, 23],
+        [2, 42, 14, 17, 43, 15],
+
+        // 18
+        [5, 150, 120, 1, 151, 121],
+        [9, 69, 43, 4, 70, 44],
+        [17, 50, 22, 1, 51, 23],
+        [2, 42, 14, 19, 43, 15],
+
+        // 19
+        [3, 141, 113, 4, 142, 114],
+        [3, 70, 44, 11, 71, 45],
+        [17, 47, 21, 4, 48, 22],
+        [9, 39, 13, 16, 40, 14],
+
+        // 20
+        [3, 135, 107, 5, 136, 108],
+        [3, 67, 41, 13, 68, 42],
+        [15, 54, 24, 5, 55, 25],
+        [15, 43, 15, 10, 44, 16],
+
+        // 21
+        [4, 144, 116, 4, 145, 117],
+        [17, 68, 42],
+        [17, 50, 22, 6, 51, 23],
+        [19, 46, 16, 6, 47, 17],
+
+        // 22
+        [2, 139, 111, 7, 140, 112],
+        [17, 74, 46],
+        [7, 54, 24, 16, 55, 25],
+        [34, 37, 13],
+
+        // 23
+        [4, 151, 121, 5, 152, 122],
+        [4, 75, 47, 14, 76, 48],
+        [11, 54, 24, 14, 55, 25],
+        [16, 45, 15, 14, 46, 16],
+
+        // 24
+        [6, 147, 117, 4, 148, 118],
+        [6, 73, 45, 14, 74, 46],
+        [11, 54, 24, 16, 55, 25],
+        [30, 46, 16, 2, 47, 17],
+
+        // 25
+        [8, 132, 106, 4, 133, 107],
+        [8, 75, 47, 13, 76, 48],
+        [7, 54, 24, 22, 55, 25],
+        [22, 45, 15, 13, 46, 16],
+
+        // 26
+        [10, 142, 114, 2, 143, 115],
+        [19, 74, 46, 4, 75, 47],
+        [28, 50, 22, 6, 51, 23],
+        [33, 46, 16, 4, 47, 17],
+
+        // 27
+        [8, 152, 122, 4, 153, 123],
+        [22, 73, 45, 3, 74, 46],
+        [8, 53, 23, 26, 54, 24],
+        [12, 45, 15, 28, 46, 16],
+
+        // 28
+        [3, 147, 117, 10, 148, 118],
+        [3, 73, 45, 23, 74, 46],
+        [4, 54, 24, 31, 55, 25],
+        [11, 45, 15, 31, 46, 16],
+
+        // 29
+        [7, 146, 116, 7, 147, 117],
+        [21, 73, 45, 7, 74, 46],
+        [1, 53, 23, 37, 54, 24],
+        [19, 45, 15, 26, 46, 16],
+
+        // 30
+        [5, 145, 115, 10, 146, 116],
+        [19, 75, 47, 10, 76, 48],
+        [15, 54, 24, 25, 55, 25],
+        [23, 45, 15, 25, 46, 16],
+
+        // 31
+        [13, 145, 115, 3, 146, 116],
+        [2, 74, 46, 29, 75, 47],
+        [42, 54, 24, 1, 55, 25],
+        [23, 45, 15, 28, 46, 16],
+
+        // 32
+        [17, 145, 115],
+        [10, 74, 46, 23, 75, 47],
+        [10, 54, 24, 35, 55, 25],
+        [19, 45, 15, 35, 46, 16],
+
+        // 33
+        [17, 145, 115, 1, 146, 116],
+        [14, 74, 46, 21, 75, 47],
+        [29, 54, 24, 19, 55, 25],
+        [11, 45, 15, 46, 46, 16],
+
+        // 34
+        [13, 145, 115, 6, 146, 116],
+        [14, 74, 46, 23, 75, 47],
+        [44, 54, 24, 7, 55, 25],
+        [59, 46, 16, 1, 47, 17],
+
+        // 35
+        [12, 151, 121, 7, 152, 122],
+        [12, 75, 47, 26, 76, 48],
+        [39, 54, 24, 14, 55, 25],
+        [22, 45, 15, 41, 46, 16],
+
+        // 36
+        [6, 151, 121, 14, 152, 122],
+        [6, 75, 47, 34, 76, 48],
+        [46, 54, 24, 10, 55, 25],
+        [2, 45, 15, 64, 46, 16],
+
+        // 37
+        [17, 152, 122, 4, 153, 123],
+        [29, 74, 46, 14, 75, 47],
+        [49, 54, 24, 10, 55, 25],
+        [24, 45, 15, 46, 46, 16],
+
+        // 38
+        [4, 152, 122, 18, 153, 123],
+        [13, 74, 46, 32, 75, 47],
+        [48, 54, 24, 14, 55, 25],
+        [42, 45, 15, 32, 46, 16],
+
+        // 39
+        [20, 147, 117, 4, 148, 118],
+        [40, 75, 47, 7, 76, 48],
+        [43, 54, 24, 22, 55, 25],
+        [10, 45, 15, 67, 46, 16],
+
+        // 40
+        [19, 148, 118, 6, 149, 119],
+        [18, 75, 47, 31, 76, 48],
+        [34, 54, 24, 34, 55, 25],
+        [20, 45, 15, 61, 46, 16]
+    ];
+
+    /**
+     * 根据数据获取对应版本
+     * @return {[type]} [description]
+     */
+    QRCodeAlg.prototype.getRightType = function () {
+        for (var typeNumber = 1; typeNumber < 41; typeNumber++) {
+            var rsBlock = RS_BLOCK_TABLE[(typeNumber - 1) * 4 + this.errorCorrectLevel];
+            if (rsBlock == undefined) {
+                throw new Error("bad rs block @ typeNumber:" + typeNumber + "/errorCorrectLevel:" + this.errorCorrectLevel);
+            }
+            var length = rsBlock.length / 3;
+            var totalDataCount = 0;
+            for (var i = 0; i < length; i++) {
+                var count = rsBlock[i * 3 + 0];
+                var dataCount = rsBlock[i * 3 + 2];
+                totalDataCount += dataCount * count;
+            }
+            var lengthBytes = typeNumber > 9 ? 2 : 1;
+            if (this.utf8bytes.length + lengthBytes < totalDataCount || typeNumber == 40) {
+                this.typeNumber = typeNumber;
+                this.rsBlock = rsBlock;
+                this.totalDataCount = totalDataCount;
+                break;
+            }
+        }
+    };
+
+    //---------------------------------------------------------------------
+    // QRBitBuffer
+    //---------------------------------------------------------------------
+    function QRBitBuffer() {
+        this.buffer = new Array();
+        this.length = 0;
+    }
+    QRBitBuffer.prototype = {
+        get: function (index) {
+            var bufIndex = Math.floor(index / 8);
+            return ((this.buffer[bufIndex] >>> (7 - index % 8)) & 1);
+        },
+        put: function (num, length) {
+            for (var i = 0; i < length; i++) {
+                this.putBit(((num >>> (length - i - 1)) & 1));
+            }
+        },
+        putBit: function (bit) {
+            var bufIndex = Math.floor(this.length / 8);
+            if (this.buffer.length <= bufIndex) {
+                this.buffer.push(0);
+            }
+            if (bit) {
+                this.buffer[bufIndex] |= (0x80 >>> (this.length % 8));
+            }
+            this.length++;
+        }
+    };

+ 1322 - 0
js_sdk/QuShe-SharerPoster/QS-SharePoster/QS-SharePoster.js

@@ -0,0 +1,1322 @@
+import _app from './app.js';
+import QRCodeAlg from './QRCodeAlg.js';
+import { base64ToPath } from './image-tools.js';
+const ShreUserPosterBackgroundKey = 'ShrePosterBackground_'; // 背景图片缓存名称前缀
+const idKey = 'QSSHAREPOSTER_IDKEY'; //drawArray自动生成的idkey
+var isMp = false;
+// #ifdef MP
+isMp = true;
+// #endif
+
+var nbgScale = 1;
+// export default 
+function getSharePoster(obj) {
+	return new Promise(async (resolve, reject) => {
+		try {
+			const result1 = await returnPromise(obj);
+			resolve(result1);
+		} catch (e) {
+			//TODO handle the exception
+			try {
+				if(obj.bgScale) {
+					obj.bgScale = Number(obj.bgScale) - 0.1
+				}else{
+					nbgScale = nbgScale - 0.1
+				}
+				console.log('------------清除缓存后, 开始第二次尝试------------');
+				const result2 = await returnPromise(obj);
+				resolve(result2);
+			} catch (e) {
+				//TODO handle the exception
+				reject(e);
+			}
+		}
+	})
+
+}
+
+function returnPromise(obj) {
+	let {
+		type,
+		formData,
+		background,
+		posterCanvasId,
+		backgroundImage,
+		reserve,
+		textArray,
+		drawArray,
+		qrCodeArray,
+		imagesArray,
+		setCanvasWH,
+		setCanvasToTempFilePath,
+		setDraw,
+		bgScale,
+		Context,
+		_this,
+		delayTimeScale,
+		drawDelayTime
+	} = obj;
+	return new Promise(async (rs, rj) => {
+		try {
+			_app.showLoading('正在准备海报数据');
+			if (!Context) {
+				_app.log('没有画布对象,创建画布对象');
+				Context = uni.createCanvasContext(posterCanvasId, (_this || null));
+			}
+			let bgObj;
+			if (background && background.width && background.height) {
+				bgObj = background;
+			} else {
+				bgObj = await getShreUserPosterBackground({
+					backgroundImage,
+					type,
+					formData
+				});
+			}
+			bgScale = bgScale || nbgScale;
+			bgObj.width = bgObj.width * bgScale;
+			bgObj.height = bgObj.height * bgScale;
+
+			_app.log('获取背景图信息对象成功:' + JSON.stringify(bgObj));
+			const params = {
+				bgObj,
+				type,
+				bgScale,
+				getBgObj: function() {
+					return params.bgObj;
+				},
+				setBgObj: function(newBgObj){
+					const n = {...params.bgObj, ...newBgObj};
+					params.bgObj = n;
+					bgObj = n;
+				}
+			};
+			if (imagesArray) {
+				if (typeof(imagesArray) == 'function')
+					imagesArray = imagesArray(params);
+				_app.showLoading('正在生成需绘制图片的临时路径');
+				_app.log('准备设置图片');
+				imagesArray = await setImage(imagesArray);
+				_app.hideLoading();
+			}
+			if (textArray) {
+				if (typeof(textArray) == 'function')
+					textArray = textArray(params);
+				textArray = setText(Context, textArray);
+
+			}
+			if (qrCodeArray) {
+				if (typeof(qrCodeArray) == 'function')
+					qrCodeArray = qrCodeArray(params);
+				_app.showLoading('正在生成需绘制图片的临时路径');
+				for (let i = 0; i < qrCodeArray.length; i++) {
+					_app.log(i);
+					if (qrCodeArray[i].image)
+						qrCodeArray[i].image = await _app.downloadFile_PromiseFc(qrCodeArray[i].image);
+				}
+				_app.hideLoading();
+			}
+			if (drawArray) {
+				if (typeof(drawArray) == 'function') {
+					drawArray = drawArray(params);
+				}
+				if (_app.isPromise(drawArray)) {
+					drawArray = await drawArray;
+				}
+
+				if (_app.isArray(drawArray) && drawArray.length > 0) {
+					let hasAllInfoCallback = false;
+					for (let i = 0; i < drawArray.length; i++) {
+						const drawArrayItem = drawArray[i];
+						if (_app.isFn(drawArrayItem.allInfoCallback) && !hasAllInfoCallback) hasAllInfoCallback = true;
+						drawArrayItem[idKey] = i;
+						let newData;
+						switch (drawArrayItem.type) {
+							case 'image':
+								newData = await setImage(drawArrayItem);
+								break;
+							case 'text':
+								newData = setText(Context, drawArrayItem);
+								break;
+							case 'qrcode':
+								if (drawArrayItem.image)
+									newData = {
+										image: await _app.downloadFile_PromiseFc(drawArrayItem.image)
+									};
+								break;
+							case 'custom':
+								break;
+							case 'fillrect':
+								break;
+							case 'strokeRect':
+								break;
+							case 'roundStrokeRect':
+								break;
+							case 'roundFillRect':
+								break;
+							default:
+								_app.log('未识别的类型');
+								break;
+						}
+						if (newData && _app.isObject(newData)) {
+							drawArray[i] = { ...drawArrayItem,
+								...newData
+							}
+						};
+					}
+
+					if (hasAllInfoCallback) {
+						_app.log('----------------hasAllInfoCallback----------------');
+						const drawArray_copy = [...drawArray];
+						drawArray_copy.sort((a, b) => {
+							const a_serialNum = !_app.isUndef(a.serialNum) && !_app.isNull(a.serialNum) ? Number(a.serialNum) : Number.NEGATIVE_INFINITY;
+							const b_serialNum = !_app.isUndef(b.serialNum) && !_app.isNull(b.serialNum) ? Number(b.serialNum) : Number.NEGATIVE_INFINITY;
+							return a_serialNum - b_serialNum;
+						})
+						_app.log('开始for循环');
+
+						for (let i = 0; i < drawArray_copy.length; i++) {
+							const item = { ...drawArray_copy[i]
+							};
+							if (_app.isFn(item.allInfoCallback)) {
+								let newData = item.allInfoCallback({
+									drawArray
+								});
+								if (_app.isPromise(newData)) newData = await newData;
+								const item_idKey = item[idKey];
+								if (!_app.isUndef(item_idKey)) {
+									drawArray[item[idKey]] = { ...item,
+										...newData
+									};
+								} else {
+									console.log('程序错误 找不到idKey!!!	...这不应该啊');
+								}
+							}
+						}
+						_app.log('for循环结束');
+					}
+				}
+			}
+			console.log('params:' + JSON.stringify(params))
+			if (setCanvasWH && typeof(setCanvasWH) == 'function') {
+				await new Promise((resolve, reject)=>{
+					setCanvasWH(params);
+					setTimeout(()=>{
+						resolve();
+					}, 50)
+				})
+			}
+			const poster = await drawShareImage({
+				Context,
+				type,
+				posterCanvasId,
+				reserve,
+				drawArray,
+				textArray,
+				imagesArray,
+				bgObj,
+				qrCodeArray,
+				setCanvasToTempFilePath,
+				setDraw,
+				bgScale,
+				_this,
+				delayTimeScale,
+				drawDelayTime
+			});
+			_app.hideLoading();
+			rs({
+				bgObj,
+				poster,
+				type
+			});
+		} catch (e) {
+			//TODO handle the exception
+			rj(e);
+		}
+	});
+}
+
+function drawShareImage(obj) { //绘制海报方法
+	let {
+		Context,
+		type,
+		posterCanvasId,
+		reserve,
+		bgObj,
+		drawArray,
+		textArray,
+		qrCodeArray,
+		imagesArray,
+		setCanvasToTempFilePath,
+		setDraw,
+		bgScale,
+		_this,
+		delayTimeScale,
+		drawDelayTime
+	} = obj;
+	const params = {
+		Context,
+		bgObj,
+		type,
+		bgScale
+	};
+	delayTimeScale = delayTimeScale !== undefined ? delayTimeScale : 15;
+	drawDelayTime = drawDelayTime !== undefined ? drawDelayTime : 100;
+	return new Promise((rs, rj) => {
+		try {
+			_app.showLoading('正在绘制海报');
+			_app.log('背景对象:' + JSON.stringify(bgObj));
+			if (bgObj && bgObj.path) {
+				_app.log('背景有图片路径');
+				Context.drawImage(bgObj.path, 0, 0, bgObj.width, bgObj.height);
+			} else {
+				_app.log('背景没有图片路径');
+				if (bgObj.backgroundColor) {
+					_app.log('背景有背景颜色:' + bgObj.backgroundColor);
+					Context.setFillStyle(bgObj.backgroundColor);
+					Context.fillRect(0, 0, bgObj.width, bgObj.height);
+				} else {
+					_app.log('背景没有背景颜色');
+				}
+			}
+
+			_app.showLoading('绘制图片');
+			if (imagesArray && imagesArray.length > 0)
+				drawImage(Context, imagesArray);
+
+			_app.showLoading('绘制自定义内容');
+			if (setDraw && typeof(setDraw) == 'function') setDraw(params);
+
+			_app.showLoading('绘制文本');
+			if (textArray && textArray.length > 0)
+				drawText(Context, textArray, bgObj);
+
+			_app.showLoading('绘制二维码');
+			if (qrCodeArray && qrCodeArray.length > 0) {
+				for (let i = 0; i < qrCodeArray.length; i++) {
+					drawQrCode(Context, qrCodeArray[i]);
+				}
+			}
+
+			_app.showLoading('绘制可控层级序列');
+			if (drawArray && drawArray.length > 0) {
+				for (let i = 0; i < drawArray.length; i++) {
+					const drawArrayItem = drawArray[i];
+					_app.log('绘制可控层级序列, drawArrayItem:' + JSON.stringify(drawArrayItem));
+					switch (drawArrayItem.type) {
+						case 'image':
+							_app.log('绘制可控层级序列, 绘制图片');
+							drawImage(Context, drawArrayItem);
+							break;
+						case 'text':
+							_app.log('绘制可控层级序列, 绘制文本');
+							drawText(Context, drawArrayItem, bgObj);
+							break;
+						case 'qrcode':
+							_app.log('绘制可控层级序列, 绘制二维码');
+							drawQrCode(Context, drawArrayItem);
+							break;
+						case 'custom':
+							_app.log('绘制可控层级序列, 绘制自定义内容');
+							if (drawArrayItem.setDraw && typeof drawArrayItem.setDraw === 'function')
+								drawArrayItem.setDraw(Context);
+							break;drawRoundStrokeRect, drawStrokeRect
+						case 'fillRect':
+							_app.log('绘制可控层级序列, 绘制填充直角矩形');
+							drawFillRect(Context, drawArrayItem);
+							break;
+						case 'strokeRect':
+							_app.log('绘制可控层级序列, 绘制线条直角矩形');
+							drawStrokeRect(Context, drawArrayItem);
+							break;
+						case 'roundStrokeRect':
+							_app.log('绘制可控层级序列, 绘制线条圆角矩形');
+							drawRoundStrokeRect(Context, drawArrayItem);
+							break;
+						case 'roundFillRect':
+							_app.log('绘制可控层级序列, 绘制填充圆角矩形');
+							drawRoundFillRect(Context, drawArrayItem);
+							break;
+						default:
+							_app.log('未识别的类型');
+							break;
+					}
+				}
+			}
+			_app.showLoading('绘制中')
+			setTimeout(() => {
+				_app.log('准备执行draw方法')
+				_app.log('Context:' + Context);
+				const fn = function(){
+					_app.showLoading('正在输出图片');
+					let setObj = setCanvasToTempFilePath || {};
+					if (setObj && typeof(setObj) == 'function')
+						setObj = setCanvasToTempFilePath(bgObj, type);
+					let canvasToTempFilePathFn;
+					const data = {
+						x: 0,
+						y: 0,
+						width: Number(bgObj.width),
+						height: Number(bgObj.height),
+						destWidth: Number(bgObj.width), // 若H5使用这里请不要乘以二
+						destHeight: Number(bgObj.height), // 若H5使用这里请不要乘以二
+						quality: .8,
+						fileType: 'jpg',
+						...setObj
+					};
+					console.log('canvasToTempFilePath的data对象:' + JSON.stringify(data));
+					canvasToTempFilePathFn = function() {
+						const toTempFilePathObj = { //输出为图片
+							...data,
+							canvasId: posterCanvasId,
+							success(res) {
+								_app.hideLoading();
+								rs(res);
+							},
+							fail(err) {
+								_app.hideLoading();
+								console.log('输出图片失败');
+								_app.log('输出图片失败:' + JSON.stringify(err));
+								rj('输出图片失败:' + JSON.stringify(err))
+							}
+						}
+						uni.canvasToTempFilePath(toTempFilePathObj, _this || null);
+					}
+					let delayTime = 0;
+					if (qrCodeArray) {
+						qrCodeArray.forEach(item => {
+							if (item.text) {
+								delayTime += Number(item.text.length);
+							}
+						})
+					}
+					if (imagesArray) {
+						imagesArray.forEach(() => {
+							delayTime += delayTimeScale;
+						})
+					}
+					if (textArray) {
+						textArray.forEach(() => {
+							delayTime += delayTimeScale;
+						})
+					}
+					if (drawArray) {
+						drawArray.forEach(item => {
+							switch (item.type) {
+								case 'text':
+									if (item.text) {
+										delayTime += item.text.length;
+									}
+									break;
+								default:
+									delayTime += delayTimeScale;
+									break;
+							}
+						})
+					}
+					_app.log('延时系数:' + delayTimeScale);
+					_app.log('总计延时:' + delayTime);
+					setTimeout(canvasToTempFilePathFn, delayTime);
+				}
+				Context.draw((typeof(reserve) == 'boolean' ? reserve : false), fn);
+			}, drawDelayTime);
+		} catch (e) {
+			//TODO handle the exception
+			_app.hideLoading();
+			rj(e);
+		}
+	});
+}
+
+// export
+function drawFillRect(Context, drawArrayItem = {}) {	//填充矩形
+	_app.log('进入绘制填充直角矩形方法, drawArrayItem:' + JSON.stringify(drawArrayItem));
+	Context.setFillStyle(drawArrayItem.backgroundColor || 'black');
+	Context.setGlobalAlpha(drawArrayItem.alpha || 1);
+	Context.fillRect(drawArrayItem.dx || 0, drawArrayItem.dy || 0, drawArrayItem.width || 0, drawArrayItem.height || 0);
+	Context.setGlobalAlpha(1);
+}
+
+// export
+function drawStrokeRect(Context, drawArrayItem = {}) {	//线条矩形
+	Context.setStrokeStyle(drawArrayItem.color||'black');
+	Context.setLineWidth(drawArrayItem.lineWidth || 1);
+	Context.strokeRect(drawArrayItem.dx, drawArrayItem.dy, drawArrayItem.width, drawArrayItem.height);
+}
+
+// export
+function drawRoundStrokeRect(Context, drawArrayItem = {}) {
+	let { dx, dy, width, height, r, lineWidth, color } = drawArrayItem;
+	r = r || width * .1;
+
+	if (width < 2 * r) {
+		r = width / 2;
+	}
+	if (width < 2 * r) {
+		r = width / 2;
+	}
+	Context.beginPath();
+	Context.moveTo(dx + r, dy);
+	Context.arcTo(dx + width, dy, dx + width, dy + height, r);
+	Context.arcTo(dx + width, dy + height, dx, dy + height, r);
+	Context.arcTo(dx, dy + height, dx, dy, r);
+	Context.arcTo(dx, dy, dx + width, dy, r);
+	Context.closePath();
+	Context.setLineWidth(lineWidth || 1);
+	Context.setStrokeStyle(color || 'black');
+	Context.stroke();
+}
+
+// export
+function drawRoundFillRect(Context, drawArrayItem = {}) {
+	let { dx, dy, width, height, r, backgroundColor } = drawArrayItem;
+	r = r || width * .1;
+
+	if (width < 2 * r) {
+		r = width / 2;
+	}
+	if (width < 2 * r) {
+		r = width / 2;
+	}
+	Context.beginPath();
+	Context.moveTo(dx + r, dy);
+	Context.arcTo(dx + width, dy, dx + width, dy + height, r);
+	Context.arcTo(dx + width, dy + height, dx, dy + height, r);
+	Context.arcTo(dx, dy + height, dx, dy, r);
+	Context.arcTo(dx, dy, dx + width, dy, r);
+	Context.closePath();
+	Context.setFillStyle(backgroundColor);
+	Context.fill();
+}
+
+// export 
+function setText(Context, texts) { // 设置文本数据
+	_app.log('进入设置文字方法, texts:' + JSON.stringify(texts));
+	if (texts && _app.isArray(texts)) {
+		_app.log('texts是数组');
+		if (texts.length > 0) {
+			for (let i = 0; i < texts.length; i++) {
+				_app.log('字符串信息-初始化之前:' + JSON.stringify(texts[i]));
+				texts[i] = setTextFn(Context, texts[i]);
+			}
+		}
+	} else {
+		_app.log('texts是对象');
+		texts = setTextFn(Context, texts);
+	}
+	_app.log('返回texts:' + JSON.stringify(texts));
+	return texts;
+}
+
+function setTextFn(Context, textItem) {
+	_app.log('进入设置文字方法, textItem:' + JSON.stringify(textItem));
+	if (_app.isNotNull_string(textItem.text)) {
+		textItem.text = String(textItem.text);
+		textItem.alpha = textItem.alpha !== undefined ? Number(textItem.alpha) : 1;
+		textItem.color = textItem.color || 'black';
+		textItem.size = textItem.size !== undefined ? Number(textItem.size) : 10;
+		textItem.textAlign = textItem.textAlign || 'left';
+		textItem.textBaseline = textItem.textBaseline || 'middle';
+		textItem.dx = Number(textItem.dx) || 0;
+		textItem.dy = Number(textItem.dy) || 0;
+		textItem.size = Math.ceil(Number(textItem.size));
+		_app.log('字符串信息-初始化默认值后:' + JSON.stringify(textItem));
+		const textLength = countTextLength(Context, {
+			text: textItem.text,
+			size: textItem.size
+		});
+		_app.log('字符串信息-初始化时的文本长度:' + textLength);
+		let infoCallBackObj = {};
+		if (textItem.infoCallBack && typeof(textItem.infoCallBack) === 'function') {
+			infoCallBackObj = textItem.infoCallBack(textLength);
+		}
+		textItem = {
+			...textItem,
+			textLength,
+			...infoCallBackObj
+		}
+		_app.log('字符串信息-infoCallBack后:' + JSON.stringify(textItem));
+	}
+	return textItem;
+}
+
+function countTextLength(Context, obj) {
+	_app.log('计算文字长度, obj:' + JSON.stringify(obj));
+	const {
+		text,
+		size
+	} = obj;
+	Context.setFontSize(size);
+	let textLength;
+	try{
+		textLength = Context.measureText(text); // 官方文档说 App端自定义组件编译模式暂时不可用measureText方法
+	}catch(e){
+		//TODO handle the exception
+		textLength = {};
+	}
+	textLength = {};
+	_app.log('measureText计算文字长度, textLength:' + JSON.stringify(textLength));
+	textLength = textLength && textLength.width ? textLength.width : 0;
+	if (!textLength) {
+		let l = 0;
+		for (let j = 0; j < text.length; j++) {
+			let t = text.substr(j, 1);
+			const countL = countStrLength(t);
+			_app.log('计算文字宽度系数:' + countL);
+			l += countL;
+		}
+		_app.log('文字宽度总系数:' + l);
+		textLength = l * size;
+	}
+	return textLength;
+}
+
+//计算字符长度系数
+function countStrLength(t) {
+	let l;
+	if (/a/.test(t)) {
+		l = 0.552734375
+	} else if (/b/.test(t)) {
+		l = 0.638671875
+	} else if (/c/.test(t)) {
+		l = 0.50146484375
+	} else if (/d/.test(t)) {
+		l = 0.6396484375
+	} else if (/e/.test(t)) {
+		l = 0.5673828125
+	} else if (/f/.test(t)) {
+		l = 0.3466796875
+	} else if (/g/.test(t)) {
+		l = 0.6396484375
+	} else if (/h/.test(t)) {
+		l = 0.61572265625
+	} else if (/i/.test(t)) {
+		l = 0.26611328125
+	} else if (/j/.test(t)) {
+		l = 0.26708984375
+	} else if (/k/.test(t)) {
+		l = 0.54443359375
+	} else if (/l/.test(t)) {
+		l = 0.26611328125
+	} else if (/m/.test(t)) {
+		l = 0.93701171875
+	} else if (/n/.test(t)) {
+		l = 0.6162109375
+	} else if (/o/.test(t)) {
+		l = 0.6357421875
+	} else if (/p/.test(t)) {
+		l = 0.638671875
+	} else if (/q/.test(t)) {
+		l = 0.6396484375
+	} else if (/r/.test(t)) {
+		l = 0.3818359375
+	} else if (/s/.test(t)) {
+		l = 0.462890625
+	} else if (/t/.test(t)) {
+		l = 0.37255859375
+	} else if (/u/.test(t)) {
+		l = 0.6162109375
+	} else if (/v/.test(t)) {
+		l = 0.52490234375
+	} else if (/w/.test(t)) {
+		l = 0.78955078125
+	} else if (/x/.test(t)) {
+		l = 0.5068359375
+	} else if (/y/.test(t)) {
+		l = 0.529296875
+	} else if (/z/.test(t)) {
+		l = 0.49169921875
+	} else if (/A/.test(t)) {
+		l = 0.70361328125
+	} else if (/B/.test(t)) {
+		l = 0.62744140625
+	} else if (/C/.test(t)) {
+		l = 0.6689453125
+	} else if (/D/.test(t)) {
+		l = 0.76171875
+	} else if (/E/.test(t)) {
+		l = 0.5498046875
+	} else if (/F/.test(t)) {
+		l = 0.53125
+	} else if (/G/.test(t)) {
+		l = 0.74365234375
+	} else if (/H/.test(t)) {
+		l = 0.7734375
+	} else if (/I/.test(t)) {
+		l = 0.2939453125
+	} else if (/J/.test(t)) {
+		l = 0.39599609375
+	} else if (/K/.test(t)) {
+		l = 0.634765625
+	} else if (/L/.test(t)) {
+		l = 0.51318359375
+	} else if (/M/.test(t)) {
+		l = 0.97705078125
+	} else if (/N/.test(t)) {
+		l = 0.81298828125
+	} else if (/O/.test(t)) {
+		l = 0.81494140625
+	} else if (/P/.test(t)) {
+		l = 0.61181640625
+	} else if (/Q/.test(t)) {
+		l = 0.81494140625
+	} else if (/R/.test(t)) {
+		l = 0.65283203125
+	} else if (/S/.test(t)) {
+		l = 0.5771484375
+	} else if (/T/.test(t)) {
+		l = 0.5732421875
+	} else if (/U/.test(t)) {
+		l = 0.74658203125
+	} else if (/V/.test(t)) {
+		l = 0.67626953125
+	} else if (/W/.test(t)) {
+		l = 1.017578125
+	} else if (/X/.test(t)) {
+		l = 0.64501953125
+	} else if (/Y/.test(t)) {
+		l = 0.603515625
+	} else if (/Z/.test(t)) {
+		l = 0.6201171875
+	} else if (/[0-9]/.test(t)) {
+		l = 0.58642578125
+	} else if (/[\u4e00-\u9fa5]/.test(t)) {
+		l = 1
+	} else if (/ /.test(t)) {
+		l = 0.2958984375
+	} else if (/\`/.test(t)) {
+		l = 0.294921875
+	} else if (/\~/.test(t)) {
+		l = 0.74169921875
+	} else if (/\!/.test(t)) {
+		l = 0.3125
+	} else if (/\@/.test(t)) {
+		l = 1.03125
+	} else if (/\#/.test(t)) {
+		l = 0.63818359375
+	} else if (/\$/.test(t)) {
+		l = 0.58642578125
+	} else if (/\%/.test(t)) {
+		l = 0.8896484375
+	} else if (/\^/.test(t)) {
+		l = 0.74169921875
+	} else if (/\&/.test(t)) {
+		l = 0.8701171875
+	} else if (/\*/.test(t)) {
+		l = 0.455078125
+	} else if (/\(/.test(t)) {
+		l = 0.333984375
+	} else if (/\)/.test(t)) {
+		l = 0.333984375
+	} else if (/\_/.test(t)) {
+		l = 0.4482421875
+	} else if (/\-/.test(t)) {
+		l = 0.4326171875
+	} else if (/\+/.test(t)) {
+		l = 0.74169921875
+	} else if (/\=/.test(t)) {
+		l = 0.74169921875
+	} else if (/\|/.test(t)) {
+		l = 0.26904296875
+	} else if (/\\/.test(t)) {
+		l = 0.416015625
+	} else if (/\[/.test(t)) {
+		l = 0.333984375
+	} else if (/\]/.test(t)) {
+		l = 0.333984375
+	} else if (/\;/.test(t)) {
+		l = 0.24072265625
+	} else if (/\'/.test(t)) {
+		l = 0.25634765625
+	} else if (/\,/.test(t)) {
+		l = 0.24072265625
+	} else if (/\./.test(t)) {
+		l = 0.24072265625
+	} else if (/\//.test(t)) {
+		l = 0.42724609375
+	} else if (/\{/.test(t)) {
+		l = 0.333984375
+	} else if (/\}/.test(t)) {
+		l = 0.333984375
+	} else if (/\:/.test(t)) {
+		l = 0.24072265625
+	} else if (/\"/.test(t)) {
+		l = 0.435546875
+	} else if (/\</.test(t)) {
+		l = 0.74169921875
+	} else if (/\>/.test(t)) {
+		l = 0.74169921875
+	} else if (/\?/.test(t)) {
+		l = 0.48291015625
+	} else {
+		l = 1
+	}
+	return l;
+}
+
+// export 
+function setImage(images) { // 设置图片数据
+	_app.log('进入设置图片数据方法');
+	return new Promise(async (resolve, rejcet) => {
+		try {
+			if (images && _app.isArray(images)) {
+				_app.log('images是一个数组');
+				for (let i = 0; i < images.length; i++) {
+					_app.log('设置图片数据循环中:' + i);
+					images[i] = await setImageFn(images[i]);
+				}
+			} else {
+				_app.log('images是一个对象');
+				images = await setImageFn(images);
+			}
+			resolve(images);
+		} catch (e) {
+			//TODO handle the exception
+			rejcet(e);
+		}
+	})
+}
+
+function base64ToPathFn(path) {
+	var reg = /^\s*data:([a-z]+\/[a-z0-9-+.]+(;[a-z-]+=[a-z0-9-]+)?)?(;base64)?,([a-z0-9!$&',()*+;=\-._~:@\/?%\s]*?)\s*$/i;
+	if(!reg.test(path)){
+	  return Promise.resolve(path);
+	}
+	return base64ToPath(path);
+}
+
+function setImageFn(image) {
+	return new Promise(async (resolve, reject) => {
+		if (image.url) {
+			image.url = (await base64ToPathFn(image.url));
+			let imgUrl = image.url;
+			imgUrl = await _app.downloadFile_PromiseFc(imgUrl);
+			image.url = imgUrl;
+			const hasinfoCallBack = image.infoCallBack && typeof(image.infoCallBack) === 'function';
+			let imageInfo = {};
+			imageInfo = await _app.getImageInfo_PromiseFc(imgUrl);
+			if (hasinfoCallBack) {
+				image = {
+					...image,
+					...image.infoCallBack(imageInfo)
+				};
+			}
+			image.dx = Number(image.dx) || 0;
+			image.dy = Number(image.dy) || 0;
+			image.dWidth = Number(image.dWidth || imageInfo.width);
+			image.dHeight = Number(image.dHeight || imageInfo.height);
+			image = {
+				...image,
+				imageInfo
+			}
+		}
+		resolve(image);
+	})
+}
+
+// export 
+function drawText(Context, textArray, bgObj) { // 先遍历换行再绘制
+	if (!_app.isArray(textArray)) {
+		_app.log('遍历文本方法, 不是数组');
+		textArray = [textArray];
+	} else {
+		_app.log('遍历文本方法, 是数组');
+	}
+	_app.log('遍历文本方法, textArray:' + JSON.stringify(textArray));
+	const newArr = [];
+	if (textArray && textArray.length > 0) {
+		for (let j = 0; j < textArray.length; j++) {
+			const textItem = textArray[j];
+			if (textItem.text && textItem.lineFeed) {
+				let lineNum = -1,
+					maxWidth = bgObj.width,
+					lineHeight = textItem.size,
+					dx = textItem.dx;
+				if (_app.isObject(textItem.lineFeed)) {
+					const lineFeed = textItem.lineFeed;
+					lineNum = (lineFeed.lineNum !== undefined && typeof(lineFeed.lineNum) === 'number') && lineFeed.lineNum >= 0 ?
+						lineFeed.lineNum : lineNum;
+					maxWidth = (lineFeed.maxWidth !== undefined && typeof(lineFeed.maxWidth) === 'number') ? lineFeed.maxWidth :
+						maxWidth;
+					lineHeight = (lineFeed.lineHeight !== undefined && typeof(lineFeed.lineHeight) === 'number') ? lineFeed.lineHeight :
+						lineHeight;
+					dx = (lineFeed.dx !== undefined && typeof(lineFeed.dx) === 'number') ? lineFeed.dx : dx;
+				}
+				const chr = (textItem.text).split("");
+				let temp = "";
+				const row = [];
+				//循环出几行文字组成数组
+				for (let a = 0, len = chr.length; a < len; a++) {
+					if (countTextLength(Context, {
+							text: temp,
+							size: textItem.size
+						}) <= maxWidth && countTextLength(Context, {
+							text: (temp + chr[a]),
+							size: textItem.size
+						}) <= maxWidth) {
+						temp += chr[a];
+						if (a == (chr.length - 1)) {
+							row.push(temp);
+						}
+					} else {
+						row.push(temp);
+						temp = chr[a];
+					}
+				}
+				_app.log('循环出的文本数组:' + JSON.stringify(row));
+				//只显示几行 变量间距lineHeight  变量行数lineNum
+				let allNum = (lineNum >= 0 && lineNum < row.length) ? lineNum : row.length;
+
+				for (let i = 0; i < allNum; i++) {
+					let str = row[i];
+					if (i == (allNum - 1) && allNum < row.length) {
+						str = str.substring(0, str.length - 1) + '...';
+					}
+					const obj = { ...textItem,
+						text: str,
+						dx: i === 0 ? textItem.dx : (dx >= 0 ? dx : textItem.dx),
+						dy: textItem.dy + (i * lineHeight),
+						textLength: countTextLength(Context, {
+							text: str,
+							size: textItem.size
+						})
+					};
+					_app.log('重新组成的文本对象:' + JSON.stringify(obj));
+					newArr.push(obj);
+				}
+			} else {
+				newArr.push(textItem);
+			}
+		}
+	}
+	_app.log('绘制文本新数组:' + JSON.stringify(newArr));
+	drawTexts(Context, newArr);
+}
+
+function setFont(textItem = {}) {
+	if (textItem.font && typeof(textItem.font) === 'string') {
+		_app.log(textItem.font)
+		return textItem.font;
+	} else {
+		let fontStyle = 'normal';
+		let fontVariant = 'normal';
+		let fontWeight = 'normal';
+		let fontSize = textItem.size || 10;
+		let fontFamily = 'sans-serif';
+		fontSize = Math.ceil(Number(fontSize));
+		if (textItem.fontStyle && typeof(textItem.fontStyle) === 'string')
+			fontStyle = textItem.fontStyle.trim();
+		if (textItem.fontVariant && typeof(textItem.fontVariant) === 'string')
+			fontVariant = textItem.fontVariant.trim();
+		if (textItem.fontWeight && (typeof(textItem.fontWeight) === 'string' || typeof(textItem.fontWeight) === 'number'))
+			fontWeight = textItem.fontWeight.trim();
+		if (textItem.fontFamily && typeof(textItem.fontFamily) === 'string')
+			fontFamily = textItem.fontFamily.trim();
+		return fontStyle + ' ' +
+			fontVariant + ' ' +
+			fontWeight + ' ' +
+			fontSize + 'px' + ' ' +
+			fontFamily;
+	}
+}
+
+function drawTexts(Context, texts) { // 绘制文本
+	_app.log('准备绘制文本方法, texts:' + JSON.stringify(texts));
+	if (texts && _app.isArray(texts)) {
+		_app.log('准备绘制文本方法, 是数组');
+		if (texts.length > 0) {
+			for (let i = 0; i < texts.length; i++) {
+				drawTextFn(Context, texts[i]);
+			}
+		}
+	} else {
+		_app.log('准备绘制文本方法, 不是数组');
+		drawTextFn(Context, texts);
+	}
+}
+
+function drawTextFn(Context, textItem) {
+	_app.log('进入绘制文本方法, textItem:' + JSON.stringify(textItem));
+	if (textItem && _app.isObject(textItem) && textItem.text) {
+		Context.font = setFont(textItem);
+		Context.setFillStyle(textItem.color);
+		Context.setGlobalAlpha(textItem.alpha);
+		Context.setTextAlign(textItem.textAlign);
+		Context.setTextBaseline(textItem.textBaseline);
+		Context.fillText(textItem.text, textItem.dx, textItem.dy);
+		if (textItem.lineThrough && _app.isObject(textItem.lineThrough)) {
+			_app.log('有删除线');
+			let lineThrough = textItem.lineThrough;
+			lineThrough.alpha = lineThrough.alpha !== undefined ? lineThrough.alpha : textItem.alpha;
+			lineThrough.style = lineThrough.style || textItem.color;
+			lineThrough.width = lineThrough.width !== undefined ? lineThrough.width : textItem.size / 10;
+			lineThrough.cap = lineThrough.cap !== undefined ? lineThrough.cap : 'butt';
+			_app.log('删除线对象:' + JSON.stringify(lineThrough));
+			Context.setGlobalAlpha(lineThrough.alpha);
+			Context.setStrokeStyle(lineThrough.style);
+			Context.setLineWidth(lineThrough.width);
+			Context.setLineCap(lineThrough.cap);
+			let mx, my;
+			switch (textItem.textAlign) {
+				case 'left':
+					mx = textItem.dx;
+					break;
+				case 'center':
+					mx = textItem.dx - (textItem.textLength) / 2;
+					break;
+				default:
+					mx = textItem.dx - (textItem.textLength);
+					break;
+			}
+			switch (textItem.textBaseline) {
+				case 'top':
+					my = textItem.dy + (textItem.size * .5);
+					break;
+				case 'middle':
+					my = textItem.dy;
+					break;
+				default:
+					my = textItem.dy - (textItem.size * .5);
+					break;
+			}
+			Context.beginPath();
+			Context.moveTo(mx, my);
+			Context.lineTo(mx + textItem.textLength, my);
+			Context.stroke();
+			Context.closePath();
+			_app.log('删除线完毕');
+		}
+		Context.setGlobalAlpha(1);
+		Context.font = '10px sans-serif';
+	}
+}
+// export 
+function drawImage(Context, images) { // 绘制图片
+	_app.log('判断图片数据类型:' + JSON.stringify(images))
+	if (images && _app.isArray(images)) {
+		if (images.length > 0) {
+			for (let i = 0; i < images.length; i++) {
+				readyDrawImageFn(Context, images[i]);
+			}
+		}
+	} else {
+		readyDrawImageFn(Context, images);
+	}
+
+}
+
+function readyDrawImageFn(Context, img) {
+	_app.log('判断绘制图片形状, img:' + JSON.stringify(img));
+	if (img.url) {
+		if (img.circleSet) {
+			drawCircleImage(Context, img);
+		} else if (img.roundRectSet) {
+			drawRoundRectImage(Context, img);
+		} else {
+			drawImageFn(Context, img);
+		}
+	}
+}
+
+function drawImageFn(Context, img) {
+	_app.log('进入绘制默认图片方法, img:' + JSON.stringify(img));
+	if (img.url) {
+		const hasAlpha = !_app.isUndef(img.alpha);
+		img.alpha = Number(!_app.isUndef(img.alpha) ? img.alpha : 1);
+		Context.setGlobalAlpha(img.alpha);
+		_app.log('绘制默认图片方法, 有url');
+		if (img.dWidth && img.dHeight && img.sx && img.sy && img.sWidth && img.sHeight) {
+			_app.log('绘制默认图片方法, 绘制第一种方案');
+			Context.drawImage(img.url, 
+			Number(img.sx) || false, Number(img.sy) || false, 
+			Number(img.sWidth) || false, Number(img.sHeight) || false,
+			Number(img.dx || 0), Number(img.dy || 0),
+			Number(img.dWidth) || false, Number(img.dHeight) || false,);
+		} else if (img.dWidth && img.dHeight) {
+			_app.log('绘制默认图片方法, 绘制第二种方案');
+			Context.drawImage(img.url, Number(img.dx || 0), Number(img.dy || 0),
+				Number(img.dWidth) || false, Number(img.dHeight) || false);
+		} else {
+			_app.log('绘制默认图片方法, 绘制第三种方案');
+			Context.drawImage(img.url, Number(img.dx || 0), Number(img.dy || 0));
+		}
+		if (hasAlpha) {
+			Context.setGlobalAlpha(1);
+		}
+	}
+	_app.log('绘制默认图片方法, 绘制完毕');
+}
+
+function drawCircleImage(Context, obj) {
+	_app.log('进入绘制圆形图片方法, obj:' + JSON.stringify(obj));
+	let {
+		dx,
+		dy,
+		dWidth,
+		dHeight,
+		circleSet,
+		imageInfo
+	} = obj;
+	let x, y, r;
+	if (typeof circleSet === 'object') {
+		x = circleSet.x;
+		y = circleSet.y;
+		r = circleSet.r;
+	}
+	if (!r) {
+		let d;
+		d = dWidth > dHeight ? dHeight : dWidth;
+		r = d / 2;
+	}
+
+	x = x ? dx + x : (dx || 0) + r;
+	y = y ? dy + y : (dy || 0) + r;
+	Context.save();
+	Context.beginPath();
+	Context.arc(x, y, r, 0, 2 * Math.PI, false);
+	Context.closePath();
+	Context.setGlobalAlpha(0);
+	Context.fillStyle = '#FFFFFF';
+	Context.fill();
+	Context.setGlobalAlpha(1);
+	Context.clip();
+	drawImageFn(Context, obj);
+	_app.log('默认图片绘制完毕');
+	Context.restore();
+}
+
+function drawRoundRectImage(Context, obj) { // 绘制矩形
+	_app.log('进入绘制矩形图片方法, obj:' + JSON.stringify(obj));
+	Context.save();
+	let {
+		dx,
+		dy,
+		dWidth,
+		dHeight,
+		roundRectSet,
+		imageInfo
+	} = obj;
+	let r;
+	if (typeof roundRectSet === 'object') {
+		r = roundRectSet.r;
+	}
+	r = r || dWidth * .1;
+
+	if (dWidth < 2 * r) {
+		r = dWidth / 2;
+	}
+	if (dHeight < 2 * r) {
+		r = dHeight / 2;
+	}
+	Context.beginPath();
+	Context.moveTo(dx + r, dy);
+	Context.arcTo(dx + dWidth, dy, dx + dWidth, dy + dHeight, r);
+	Context.arcTo(dx + dWidth, dy + dHeight, dx, dy + dHeight, r);
+	Context.arcTo(dx, dy + dHeight, dx, dy, r);
+	Context.arcTo(dx, dy, dx + dWidth, dy, r);
+	Context.closePath();
+	Context.setGlobalAlpha(0);
+	Context.fillStyle = '#FFFFFF';
+	Context.fill();
+	Context.setGlobalAlpha(1);
+	Context.clip();
+	drawImageFn(Context, obj);
+	Context.restore();
+	_app.log('进入绘制矩形图片方法, 绘制完毕');
+}
+
+// export 
+function drawQrCode(Context, qrCodeObj) { //生成二维码方法, 参考了 诗小柒 的二维码生成器代码
+	_app.log('进入绘制二维码方法')
+	_app.showLoading('正在生成二维码');
+	let qrcodeAlgObjCache = [];
+	let options = {
+		text: String(qrCodeObj.text || '') || '', // 生成内容
+		size: Number(qrCodeObj.size || 0) || 200, // 二维码大小
+		background: String(qrCodeObj.background || '') || '#ffffff', // 背景色
+		foreground: String(qrCodeObj.foreground || '') || '#000000', // 前景色
+		pdground: String(qrCodeObj.pdground || '') || '#000000', // 定位角点颜色
+		correctLevel: Number(qrCodeObj.correctLevel || 0) || 3, // 容错级别
+		image: String(qrCodeObj.image || '') || '', // 二维码图标
+		imageSize: Number(qrCodeObj.imageSize || 0) || 40, // 二维码图标大小
+		dx: Number(qrCodeObj.dx || 0) || 0, // x轴距离
+		dy: Number(qrCodeObj.dy || 0) || 0 // y轴距离
+	}
+	let qrCodeAlg = null;
+	let d = 0;
+	for (var i = 0, l = qrcodeAlgObjCache.length; i < l; i++) {
+		d = i;
+		if (qrcodeAlgObjCache[i].text == options.text && qrcodeAlgObjCache[i].text.correctLevel == options.correctLevel) {
+			qrCodeAlg = qrcodeAlgObjCache[i].obj;
+			break;
+		}
+	}
+	if (d == l) {
+		qrCodeAlg = new QRCodeAlg(options.text, options.correctLevel);
+		qrcodeAlgObjCache.push({
+			text: options.text,
+			correctLevel: options.correctLevel,
+			obj: qrCodeAlg
+		});
+	}
+	let getForeGround = function(config) {
+		let options = config.options;
+		if (options.pdground && (
+				(config.row > 1 && config.row < 5 && config.col > 1 && config.col < 5) ||
+				(config.row > (config.count - 6) && config.row < (config.count - 2) && config.col > 1 && config.col < 5) ||
+				(config.row > 1 && config.row < 5 && config.col > (config.count - 6) && config.col < (config.count - 2))
+			)) {
+			return options.pdground;
+		}
+		return options.foreground;
+	}
+	let count = qrCodeAlg.getModuleCount();
+	let ratioSize = options.size;
+	let ratioImgSize = options.imageSize;
+	//计算每个点的长宽
+	let tileW = (ratioSize / count).toPrecision(4);
+	let tileH = (ratioSize / count).toPrecision(4);
+	//绘制
+	for (let row = 0; row < count; row++) {
+		for (let col = 0; col < count; col++) {
+			let w = (Math.ceil((col + 1) * tileW) - Math.floor(col * tileW));
+			let h = (Math.ceil((row + 1) * tileW) - Math.floor(row * tileW));
+			let foreground = getForeGround({
+				row: row,
+				col: col,
+				count: count,
+				options: options
+			});
+			Context.setFillStyle(qrCodeAlg.modules[row][col] ? foreground : options.background);
+			Context.fillRect(options.dx + Math.round(col * tileW), options.dy + Math.round(row * tileH), w, h);
+		}
+	}
+	if (options.image) {
+		let x = options.dx + Number(((ratioSize - ratioImgSize) / 2).toFixed(2));
+		let y = options.dy + Number(((ratioSize - ratioImgSize) / 2).toFixed(2));
+		drawRoundedRect(Context, x, y, ratioImgSize, ratioImgSize, 2, 6, true, true)
+		Context.drawImage(options.image, x, y, ratioImgSize, ratioImgSize);
+		// 画圆角矩形
+		function drawRoundedRect(ctxi, x, y, width, height, r, lineWidth, fill, stroke) {
+			ctxi.setLineWidth(lineWidth);
+			ctxi.setFillStyle(options.background);
+			ctxi.setStrokeStyle(options.background);
+			ctxi.beginPath(); // draw top and top right corner 
+			ctxi.moveTo(x + r, y);
+			ctxi.arcTo(x + width, y, x + width, y + r, r); // draw right side and bottom right corner 
+			ctxi.arcTo(x + width, y + height, x + width - r, y + height, r); // draw bottom and bottom left corner 
+			ctxi.arcTo(x, y + height, x, y + height - r, r); // draw left and top left corner 
+			ctxi.arcTo(x, y, x + r, y, r);
+			ctxi.closePath();
+			if (fill) {
+				ctxi.fill();
+			}
+			if (stroke) {
+				ctxi.stroke();
+			}
+		}
+	}
+	_app.log('进入绘制二维码方法完毕')
+	_app.hideLoading();
+}
+
+
+function getShreUserPosterBackground(objs) { //检查背景图是否存在于本地, 若存在直接返回, 否则调用getShreUserPosterBackgroundFc方法
+	let {
+		backgroundImage,
+		type
+	} = objs;
+	return new Promise(async (resolve, reject) => {
+		try {
+			_app.showLoading('正在获取海报背景图');
+			const savedFilePath = await getShreUserPosterBackgroundFc(objs)
+			_app.hideLoading();
+			resolve(savedFilePath);
+		} catch (e) {
+			_app.hideLoading();
+			_app.showToast('获取分享用户背景图失败:' + JSON.stringify(e));
+			_app.log(JSON.stringify(e));
+			reject(e);
+		}
+	})
+}
+
+function getPosterStorage(type) {
+	return _app.getStorageSync(getStorageKey(type));
+}
+
+function removePosterStorage(type) {
+	const ShreUserPosterBackgroundKey = getStorageKey(type);
+	const pbg = _app.getStorageSync(ShreUserPosterBackgroundKey);
+	if (pbg && pbg.path) {
+		_app.removeStorageSync(ShreUserPosterBackgroundKey);
+	}
+}
+
+function setPosterStorage(type, data) {
+	_app.setStorage(getStorageKey(type), data);
+}
+
+function getStorageKey(type) {
+	return ShreUserPosterBackgroundKey + (type || 'default');
+}
+
+function getShreUserPosterBackgroundFc(objs, upimage) { //下载并保存背景图方法
+	let {
+		backgroundImage,
+		type
+	} = objs;
+	_app.log('获取分享背景图, 尝试清空本地数据');
+	return new Promise(async (resolve, reject) => {
+		try {
+			_app.showLoading('正在下载海报背景图');
+			_app.log('没有从后端获取的背景图片路径, 尝试从后端获取背景图片路径');
+			let image = backgroundImage?backgroundImage:(await _app.getPosterUrl(objs));
+			image = (await base64ToPathFn(image));
+			_app.log('尝试下载并保存背景图:' + image);
+			const savedFilePath = await _app.downLoadAndSaveFile_PromiseFc(image);
+			if (savedFilePath) {
+				_app.log('下载并保存背景图成功:' + savedFilePath);
+				const imageObj = await _app.getImageInfo_PromiseFc(savedFilePath);
+				_app.log('获取图片信息成功');
+				const returnObj = {
+					path: savedFilePath,
+					width: imageObj.width,
+					height: imageObj.height,
+					name: _app.fileNameInPath(image)
+				}
+				_app.log('拼接背景图信息对象成功:' + JSON.stringify(returnObj));
+
+				// #ifndef H5
+				setPosterStorage(type, { ...returnObj
+				});
+				// #endif
+
+				_app.hideLoading();
+				_app.log('返回背景图信息对象');
+				resolve({ ...returnObj
+				});
+			} else {
+				_app.hideLoading();
+				reject('not find savedFilePath');
+			}
+		} catch (e) {
+			//TODO handle the exception
+			reject(e);
+		}
+	});
+}
+
+
+module.exports = {
+	getSharePoster,
+	setText,
+	setImage,
+	drawText,
+	drawImage,
+	drawQrCode,
+	drawFillRect,
+	drawStrokeRect,
+	drawRoundStrokeRect,
+	drawRoundFillRect
+}

+ 561 - 0
js_sdk/QuShe-SharerPoster/QS-SharePoster/app.js

@@ -0,0 +1,561 @@
+let log = console.log; // 如果在项目的APP.vue文件中的onlaunch中设置 console.log = ()=> {} 则在此也不会有打印信息
+// log = ()=>{};	// 打开注释则该插件不会打印任何信息
+let _app = {
+	//交互控制
+	log(t) {
+		log(t);
+	}, // 打印控制,
+	showLoading(msg, ifmask) {
+		uni.showLoading({
+			title: msg,
+			mask: ifmask || false
+		})
+	},
+	hideLoading() {
+		uni.hideLoading();
+	},
+	showToast(msg, icon) {
+		uni.showToast({
+			title: msg,
+			icon: icon || 'none'
+		})
+	},
+	getPosterUrl(objs) { // 后端获取背景图的url路径方法
+		let {
+			backgroundImage,
+			type,
+			formData
+		} = objs;
+		return new Promise((rs, rj) => {
+			let image;
+			if (backgroundImage) {
+				image = backgroundImage;
+			}else{
+				switch (type) { //根据type获取背景图, 一般要改成request获取
+					case 1:
+						image = '';
+						break;
+					default:
+						image = '/static/logo.png';
+						break;
+				}
+			}
+			if (image) {
+				rs(image); // resolve图片的路径
+			}else{
+				rj('背景图片路径不存在');
+			}
+		})
+	},
+
+
+
+
+
+
+	//下面一般不用动他
+	shareTypeListSheetArray: {
+		array: [0, 1, 2, 3, 4, 5]
+	}, // 分享类型 0-图文链接 1-纯文字 2-纯图片 3-音乐 4-视频 5-小程序
+	isArray(arg) {
+		return Object.prototype.toString.call(arg) === '[object Array]';
+	},
+	isObject(arg) {
+		return Object.prototype.toString.call(arg) === '[object Object]';
+	},
+	isPromise(obj) {
+		return !!obj && (typeof obj === 'object' || typeof obj === 'function') && typeof obj.then === 'function';
+	},
+	isNull(arg) {
+		return arg === null;
+	},
+	isUndefined(arg) {
+		return arg === undefined;
+	},
+	isUndef(arg) {
+		return arg === undefined;
+	},
+	isNotNull_string(arg) {
+		return arg !== null && arg !== undefined && arg !== '';
+	},
+	isFn(fn) {
+		return fn && typeof fn === 'function';
+	},
+	getStorage(key, scb, fcb) {
+		uni.getStorage({
+			key,
+			success: function(res) {
+				if (res.data && res.data != "") {
+					if (scb) scb(res.data);
+				} else {
+					if (fcb) fcb();
+				}
+			},
+			fail: function() {
+				if (fcb) fcb();
+			}
+		})
+	},
+	setStorage(key, data) {
+		log('设置缓存')
+		log('key:' + key)
+		log('data:' + JSON.stringify(data));
+		uni.setStorage({
+			key,
+			data
+		})
+	},
+	setStorageSync(key, data) {
+		uni.setStorageSync(key, data);
+	},
+	getStorageSync(key) {
+		return uni.getStorageSync(key);
+	},
+	clearStorageSync() {
+		uni.clearStorageSync();
+	},
+	removeStorageSync(key) {
+		uni.removeStorageSync(key);
+	},
+	getImageInfo(url, cb, fcb) {
+		url = checkMPUrl(url);
+		uni.getImageInfo({
+			src: url,
+			success(res) {
+				if (cb && typeof(cb) == 'function') cb(res);
+			},
+			fail(err) {
+				if (fcb && typeof(fcb) == 'function') fcb(err);
+			}
+		})
+	},
+	downloadFile(url, cb) {
+		url = checkMPUrl(url);
+		uni.downloadFile({
+			url,
+			success(res) {
+				if (cb && typeof(cb) == 'function') cb(res);
+			}
+		})
+	},
+	downloadFile_PromiseFc(url) {
+		return new Promise((rs, rj) => {
+			if (url.substring(0, 4) !== 'http') {
+				rs(url);
+			}else {
+				url = checkMPUrl(url);
+				log('url:' + url);
+				uni.downloadFile({
+					url,
+					success(res) {
+						if (res && res.tempFilePath)
+							rs(res.tempFilePath);
+						else
+							rj('not find tempFilePath');
+					},
+					fail(err) {
+						rj(err);
+					}
+				})
+			}
+		});
+	},
+	saveFile(url) {
+		uni.saveFile({
+			tempFilePath: url,
+			success(res) {
+				log('保存成功:' + JSON.stringify(res))
+			}
+		})
+	},
+	downLoadAndSaveFile_PromiseFc(url) {
+		return new Promise((rs, rj) => {
+			log('准备下载并保存图片:' + url);
+			if (url.substring(0, 4) === 'http') {
+				url = checkMPUrl(url);
+				uni.downloadFile({
+					url,
+					success(d_res) {
+						log('下载背景图成功:' + JSON.stringify(d_res));
+						if (d_res && d_res.tempFilePath) {
+							
+							// #ifdef H5
+							rs(d_res.tempFilePath);
+							// #endif
+							
+							// #ifndef H5
+							uni.saveFile({
+								tempFilePath: d_res.tempFilePath,
+								success(s_res) {
+									log('保存背景图成功:' + JSON.stringify(s_res));
+									if (s_res && s_res.savedFilePath)
+										rs(s_res.savedFilePath);
+									else
+										rs(d_res.tempFilePath);
+								},
+								fail(err) {
+									rs(d_res.tempFilePath);
+								}
+							})
+							// #endif
+							
+						} else {
+							rj('not find tempFilePath');
+						}
+					},
+					fail(err) {
+						rj(err);
+					}
+				})
+			}else{
+				rs(url);
+			}
+		})
+	},
+	checkFile_PromiseFc(url) {
+		return new Promise((rs, rj) => {
+			uni.getSavedFileList({
+				success(res) {
+					let d = res.fileList;
+					let index = d.findIndex(item => {
+						return item.filePath === url;
+					})
+					rs(index);
+				},
+				fail(err) {
+					rj(err);
+				}
+			})
+		});
+	},
+	removeSavedFile(path) {
+		uni.getSavedFileList({
+			success(res) {
+				let d = res.fileList;
+				let index = d.findIndex(item => {
+					return item.filePath === path;
+				});
+				if (index >= 0)
+					uni.removeSavedFile({
+						filePath: path
+					})
+			}
+		})
+	},
+	fileNameInPath(path) {
+		let s = path.split("/");
+		return s[s.length - 1];
+	},
+	getImageInfo_PromiseFc(imgPath) {
+		return new Promise((rs, rj) => {
+			log('准备获取图片信息:' + imgPath);
+			imgPath = checkMPUrl(imgPath);
+			uni.getImageInfo({
+				src: imgPath,
+				success: res => {
+					log('获取图片信息成功:' + JSON.stringify(res));
+					rs(res);
+				},
+				fail: err => {
+					log('获取图片信息失败:' + JSON.stringify(err));
+					rj(err)
+				}
+			})
+		});
+	},
+	previewImage(urls) {
+		if (typeof(urls) == 'string')
+			urls = [urls];
+		uni.previewImage({
+			urls,
+		})
+	},
+	actionSheet(obj, cb) {
+		let sheetArray = [];
+		for (let i = 0; i < obj.array.length; i++) {
+			switch (obj.array[i]) {
+				case 'sinaweibo':
+					sheetArray[i] = '新浪微博';
+					break;
+				case 'qq':
+					sheetArray[i] = 'QQ';
+					break;
+				case 'weixin':
+					sheetArray[i] = '微信';
+					break;
+				case 'WXSceneSession':
+					sheetArray[i] = '微信好友';
+					break;
+				case 'WXSenceTimeline':
+					sheetArray[i] = '微信朋友圈';
+					break;
+				case 'WXSceneFavorite':
+					sheetArray[i] = '微信收藏';
+					break;
+				case 0:
+					sheetArray[i] = '图文链接';
+					break;
+				case 1:
+					sheetArray[i] = '纯文字';
+					break;
+				case 2:
+					sheetArray[i] = '纯图片';
+					break;
+				case 3:
+					sheetArray[i] = '音乐';
+					break;
+				case 4:
+					sheetArray[i] = '视频';
+					break;
+				case 5:
+					sheetArray[i] = '小程序';
+					break;
+				default:
+					break;
+			}
+		}
+		this.showActionSheet(sheetArray, cb);
+	},
+	showActionSheet(sheetArray, cb) {
+		uni.showActionSheet({
+			itemList: sheetArray,
+			success: (e) => {
+				if (cb && typeof(cb) == 'function') cb(e.tapIndex);
+			}
+		})
+	},
+	getProvider(type, cb, sheet) {
+		let _this = this;
+		uni.getProvider({
+			service: type,
+			success: function(res) {
+				if (sheet) {
+					let obj = {};
+					obj.array = res.provider;
+					_this.actionSheet(obj, function(index) {
+						if (cb && typeof(cb) == "function") cb(res.provider[index]);
+					});
+				} else {
+					if (type == 'payment') {
+						let providers = res.provider;
+						let payTypeArray = [];
+						for (let i = 0; i < providers.length; i++) {
+							if (providers[i] == 'wxpay') {
+								payTypeArray[i] = {
+									name: '微信支付',
+									value: providers[i],
+									img: '/static/image/wei.png'
+								};
+							} else if (providers[i] == 'alipay') {
+								payTypeArray[i] = {
+									name: "支付宝支付",
+									value: providers[i],
+									img: '/static/image/ali.png'
+								};
+							}
+						}
+						if (cb && typeof(cb) == "function") cb(payTypeArray);
+					} else {
+						if (cb && typeof(cb) == "function") cb(res);
+					}
+				}
+			},
+		})
+	},
+	// #ifdef APP-PLUS
+	getShare(providerName, WXScene, shareType, title, summary, href, imageUrl, miniProgramObj, mediaUrl, scb, fcb) { //miniProgram: {path: '', type: 0, webUrl: ''}
+		let _this = this;
+		if (typeof(shareType) == 'number' && !isNaN(shareType) && shareType >= 0) {
+			_this.readyShare(providerName, WXScene, shareType, title, summary, href, imageUrl, miniProgramObj, mediaUrl, scb,
+				fcb);
+		} else {
+			_this.actionSheet(_this.shareTypeListSheetArray, function(index) {
+				_this.readyShare(providerName, WXScene, _this.shareTypeListSheetArray.array[index], title, summary, href,
+					imageUrl, miniProgramObj, mediaUrl, scb, fcb);
+			});
+		}
+	},
+	readyShare(providerName, WXScene, shareType, title, summary, href, imageUrl, miniProgramObj, mediaUrl, scb, fcb) {
+		let _this = this;
+		let shareObjData = {};
+		switch (shareType) {
+			case 0:
+				shareObjData = {
+					href: href,
+					summary: summary,
+					imageUrl: imageUrl
+				};
+				break;
+			case 1:
+				shareObjData = {
+					summary: summary,
+					href: href
+				};
+				break;
+			case 2:
+				shareObjData = {
+					imageUrl: imageUrl
+				};
+				break;
+			case 3:
+				if (mediaUrl) {
+					_this.showToast('暂不支持此分享类型');
+					return;
+				};
+				shareObjData = {
+					mediaUrl: mediaUrl
+				};
+				break;
+			case 4:
+				if (mediaUrl) {
+					_this.showToast('暂不支持此分享类型');
+					return;
+				};
+				shareObjData = {
+					mediaUrl: mediaUrl
+				};
+				break;
+			case 5:
+				shareObjData = {
+					miniProgram: { ...miniProgramObj,
+						id: miniProgramId,
+						type: miniProgramShareType
+					},
+					imageUrl: imageUrl
+				};
+				providerName = 'weixin';
+				break;
+			default:
+				_this.showToast('分享参数-shareType错误');
+				return;
+				break;
+		}
+		shareObjData.title = title;
+		_this.share(providerName, WXScene, shareType, shareObjData, function(res) {
+			if (scb && typeof(scb) == 'function') scb(res);
+		}, function(err) {
+			if (fcb && typeof(fcb) == 'function') fcb(err);
+		});
+	},
+	share(providerName, WXScene, shareType, data, scb, fcb) {
+		let _this = this;
+		let shareObj = {
+			provider: '',
+			success: Function,
+			fail: Function
+		};
+		if (providerName && providerName != '') {
+			shareObj.provider = providerName;
+			if (providerName == 'weixin') {
+				_this.readyShareWXScene(WXScene, function(Scene) {
+					shareObj.scene = Scene;
+					_this.doingShare(shareObj, shareType, data, scb, fcb);
+				});
+			} else {
+				_this.doingShare(shareObj, shareType, data, scb, fcb);
+			}
+		} else {
+			_this.getProvider('share', function(name) {
+				shareObj.provider = name;
+				if (name == 'weixin') {
+					_this.readyShareWXScene(WXScene, function(Scene) {
+						shareObj.scene = Scene;
+						_this.doingShare(shareObj, shareType, data, scb, fcb);
+					});
+				} else {
+					_this.doingShare(shareObj, shareType, data, scb, fcb);
+				}
+			}, true);
+		}
+	},
+	readyShareWXScene(WXScene, cb) {
+		let _this = this;
+		let WXScenetypelist = {
+			array: ['WXSceneSession', 'WXSenceTimeline', 'WXSceneFavorite']
+		};
+		if (WXScene && WXScene != "") {
+			if (cb && typeof(cb) == 'function') cb(WXScene);
+		} else {
+			_this.actionSheet(WXScenetypelist, function(index) {
+				if (cb && typeof(cb) == 'function') cb(WXScenetypelist.array[index]);
+			});
+		}
+	},
+	doingShare(shareObj, shareType, data, scb, fcb) {
+		shareObj.type = shareType;
+		if (data && data.title) shareObj.title = data.title;
+		switch (shareType) {
+			case 0: //图文链接
+				shareObj.href = data.href;
+				shareObj.summary = data.summary;
+				shareObj.imageUrl = data.imageUrl;
+				break;
+			case 1: //纯文字
+				shareObj.summary = data.summary;
+				shareObj.href = data.href;
+				break;
+			case 2: //纯图片
+				shareObj.imageUrl = data.imageUrl;
+				break;
+			case 3: //音乐
+				if (!data.mediaUrl) {
+					_this.showToast('暂不支持此分享类型');
+					return;
+				};
+				shareObj.mediaUrl = data.mediaUrl;
+				break;
+			case 4: //视频
+				if (!data.mediaUrl) {
+					_this.showToast('暂不支持此分享类型');
+					return;
+				};
+				shareObj.mediaUrl = data.mediaUrl;
+				break;
+			case 5: //小程序
+				if (miniProgramId == '') {
+					uni.showToast('未设置小程序ID, 请使用其他方式分享');
+					return;
+				}
+				let mp = {
+					id: miniProgramId
+				};
+				mp.path = data.miniProgram.path;
+				mp.type = data.miniProgram.type;
+				if (data.miniProgram.webUrl) mp.webUrl = data.miniProgram.webUrl;
+				shareObj.miniProgram = mp;
+				shareObj.imageUrl = data.imageUrl;
+				break;
+			default:
+				appJS.showToast('分享参数-shareType错误');
+				break;
+		}
+		shareObj.success = function(res) {
+			if (scb && typeof(scb) == 'function') scb(res);
+		}
+		shareObj.fail = function(err) {
+			if (fcb && typeof(fcb) == 'function') fcb(err);
+		}
+		log(JSON.stringify(shareObj));
+		uni.share(shareObj);
+	},
+	// #endif
+}
+
+function checkMPUrl(url) {
+	// #ifdef MP
+	if(process.env.NODE_ENV !== 'development'){
+		if(
+			url.substring(0, 4) === 'http' && 
+			url.substring(0, 5) !== 'https' && 
+			url.substring(0, 12) !== 'http://store' && 
+			url.substring(0, 10) !== 'http://tmp' && 
+			url.substring(0, 10) !== 'http://usr'
+		) {
+			url = 'https' + url.substring(4, url.length);
+		}
+	}
+	// #endif
+	return url;
+}
+
+module.exports = _app;

+ 147 - 0
js_sdk/QuShe-SharerPoster/QS-SharePoster/image-tools.js

@@ -0,0 +1,147 @@
+function getLocalFilePath(path) {
+    if (path.indexOf('_www') === 0 || path.indexOf('_doc') === 0 || path.indexOf('_documents') === 0 || path.indexOf('_downloads') === 0) {
+        return path
+    }
+    if (path.indexOf('file://') === 0) {
+        return path
+    }
+    if (path.indexOf('/storage/emulated/0/') === 0) {
+        return path
+    }
+    if (path.indexOf('/') === 0) {
+        var localFilePath = plus.io.convertAbsoluteFileSystem(path)
+        if (localFilePath !== path) {
+            return localFilePath
+        } else {
+            path = path.substr(1)
+        }
+    }
+    return '_www/' + path
+}
+
+export function pathToBase64(path) {
+    return new Promise(function(resolve, reject) {
+        if (typeof window === 'object' && 'document' in window) {
+            if (typeof FileReader === 'function') {
+                var xhr = new XMLHttpRequest()
+                xhr.open('GET', path, true)
+                xhr.responseType = 'blob'
+                xhr.onload = function() {
+                    if (this.status === 200) {
+                        let fileReader = new FileReader()
+                        fileReader.onload = function(e) {
+                            resolve(e.target.result)
+                        }
+                        fileReader.onerror = reject
+                        fileReader.readAsDataURL(this.response)
+                    }
+                }
+                xhr.onerror = reject
+                xhr.send()
+                return
+            }
+            var canvas = document.createElement('canvas')
+            var c2x = canvas.getContext('2d')
+            var img = new Image
+            img.onload = function() {
+                canvas.width = img.width
+                canvas.height = img.height
+                c2x.drawImage(img, 0, 0)
+                resolve(canvas.toDataURL())
+                canvas.height = canvas.width = 0
+            }
+            img.onerror = reject
+            img.src = path
+            return
+        }
+        if (typeof plus === 'object') {
+            plus.io.resolveLocalFileSystemURL(getLocalFilePath(path), function(entry) {
+                entry.file(function(file) {
+                    var fileReader = new plus.io.FileReader()
+                    fileReader.onload = function(data) {
+                        resolve(data.target.result)
+                    }
+                    fileReader.onerror = function(error) {
+                        reject(error)
+                    }
+                    fileReader.readAsDataURL(file)
+                }, function(error) {
+                    reject(error)
+                })
+            }, function(error) {
+                reject(error)
+            })
+            return
+        }
+        if (typeof wx === 'object' && wx.canIUse('getFileSystemManager')) {
+            wx.getFileSystemManager().readFile({
+                filePath: path,
+                encoding: 'base64',
+                success: function(res) {
+                    resolve('data:image/png;base64,' + res.data)
+                },
+                fail: function(error) {
+                    reject(error)
+                }
+            })
+            return
+        }
+        reject(new Error('not support'))
+    })
+}
+
+export function base64ToPath(base64) {
+    return new Promise(function(resolve, reject) {
+        if (typeof window === 'object' && 'document' in window) {
+            base64 = base64.split(',')
+            var type = base64[0].match(/:(.*?);/)[1]
+            var str = atob(base64[1])
+            var n = str.length
+            var array = new Uint8Array(n)
+            while (n--) {
+                array[n] = str.charCodeAt(n)
+            }
+            return resolve((window.URL || window.webkitURL).createObjectURL(new Blob([array], { type: type })))
+        }
+        var extName = base64.match(/data\:\S+\/(\S+);/)
+        if (extName) {
+            extName = extName[1]
+        } else {
+            reject(new Error('base64 error'))
+        }
+        var fileName = Date.now() + '.' + extName
+        if (typeof plus === 'object') {
+            var bitmap = new plus.nativeObj.Bitmap('bitmap' + Date.now())
+            bitmap.loadBase64Data(base64, function() {
+                var filePath = '_doc/uniapp_temp/' + fileName
+                bitmap.save(filePath, {}, function() {
+                    bitmap.clear()
+                    resolve(filePath)
+                }, function(error) {
+                    bitmap.clear()
+                    reject(error)
+                })
+            }, function(error) {
+                bitmap.clear()
+                reject(error)
+            })
+            return
+        }
+        if (typeof wx === 'object' && wx.canIUse('getFileSystemManager')) {
+            var filePath = wx.env.USER_DATA_PATH + '/' + fileName
+            wx.getFileSystemManager().writeFile({
+                filePath: filePath,
+                data: base64.replace(/^data:\S+\/\S+;base64,/, ''),
+                encoding: 'base64',
+                success: function() {
+                    resolve(filePath)
+                },
+                fail: function(error) {
+                    reject(error)
+                }
+            })
+            return
+        }
+        reject(new Error('not support'))
+    })
+}

+ 1382 - 0
js_sdk/Sansnn-uQRCode/uqrcode.js

@@ -0,0 +1,1382 @@
+//---------------------------------------------------------------------
+// github https://github.com/Sansnn/uQRCode
+//---------------------------------------------------------------------
+
+let uQRCode = {};
+
+(function() {
+	//---------------------------------------------------------------------
+	// QRCode for JavaScript
+	//
+	// Copyright (c) 2009 Kazuhiko Arase
+	//
+	// URL: http://www.d-project.com/
+	//
+	// Licensed under the MIT license:
+	//   http://www.opensource.org/licenses/mit-license.php
+	//
+	// The word "QR Code" is registered trademark of 
+	// DENSO WAVE INCORPORATED
+	//   http://www.denso-wave.com/qrcode/faqpatent-e.html
+	//
+	//---------------------------------------------------------------------
+
+	//---------------------------------------------------------------------
+	// QR8bitByte
+	//---------------------------------------------------------------------
+
+	function QR8bitByte(data) {
+		this.mode = QRMode.MODE_8BIT_BYTE;
+		this.data = data;
+	}
+
+	QR8bitByte.prototype = {
+
+		getLength: function(buffer) {
+			return this.data.length;
+		},
+
+		write: function(buffer) {
+			for (var i = 0; i < this.data.length; i++) {
+				// not JIS ...
+				buffer.put(this.data.charCodeAt(i), 8);
+			}
+		}
+	};
+
+	//---------------------------------------------------------------------
+	// QRCode
+	//---------------------------------------------------------------------
+
+	function QRCode(typeNumber, errorCorrectLevel) {
+		this.typeNumber = typeNumber;
+		this.errorCorrectLevel = errorCorrectLevel;
+		this.modules = null;
+		this.moduleCount = 0;
+		this.dataCache = null;
+		this.dataList = new Array();
+	}
+
+	QRCode.prototype = {
+
+		addData: function(data) {
+			var newData = new QR8bitByte(data);
+			this.dataList.push(newData);
+			this.dataCache = null;
+		},
+
+		isDark: function(row, col) {
+			if (row < 0 || this.moduleCount <= row || col < 0 || this.moduleCount <= col) {
+				throw new Error(row + "," + col);
+			}
+			return this.modules[row][col];
+		},
+
+		getModuleCount: function() {
+			return this.moduleCount;
+		},
+
+		make: function() {
+			// Calculate automatically typeNumber if provided is < 1
+			if (this.typeNumber < 1) {
+				var typeNumber = 1;
+				for (typeNumber = 1; typeNumber < 40; typeNumber++) {
+					var rsBlocks = QRRSBlock.getRSBlocks(typeNumber, this.errorCorrectLevel);
+
+					var buffer = new QRBitBuffer();
+					var totalDataCount = 0;
+					for (var i = 0; i < rsBlocks.length; i++) {
+						totalDataCount += rsBlocks[i].dataCount;
+					}
+
+					for (var i = 0; i < this.dataList.length; i++) {
+						var data = this.dataList[i];
+						buffer.put(data.mode, 4);
+						buffer.put(data.getLength(), QRUtil.getLengthInBits(data.mode, typeNumber));
+						data.write(buffer);
+					}
+					if (buffer.getLengthInBits() <= totalDataCount * 8)
+						break;
+				}
+				this.typeNumber = typeNumber;
+			}
+			this.makeImpl(false, this.getBestMaskPattern());
+		},
+
+		makeImpl: function(test, maskPattern) {
+
+			this.moduleCount = this.typeNumber * 4 + 17;
+			this.modules = new Array(this.moduleCount);
+
+			for (var row = 0; row < this.moduleCount; row++) {
+
+				this.modules[row] = new Array(this.moduleCount);
+
+				for (var col = 0; col < this.moduleCount; col++) {
+					this.modules[row][col] = null; //(col + row) % 3;
+				}
+			}
+
+			this.setupPositionProbePattern(0, 0);
+			this.setupPositionProbePattern(this.moduleCount - 7, 0);
+			this.setupPositionProbePattern(0, this.moduleCount - 7);
+			this.setupPositionAdjustPattern();
+			this.setupTimingPattern();
+			this.setupTypeInfo(test, maskPattern);
+
+			if (this.typeNumber >= 7) {
+				this.setupTypeNumber(test);
+			}
+
+			if (this.dataCache == null) {
+				this.dataCache = QRCode.createData(this.typeNumber, this.errorCorrectLevel, this.dataList);
+			}
+
+			this.mapData(this.dataCache, maskPattern);
+		},
+
+		setupPositionProbePattern: function(row, col) {
+
+			for (var r = -1; r <= 7; r++) {
+
+				if (row + r <= -1 || this.moduleCount <= row + r) continue;
+
+				for (var c = -1; c <= 7; c++) {
+
+					if (col + c <= -1 || this.moduleCount <= col + c) continue;
+
+					if ((0 <= r && r <= 6 && (c == 0 || c == 6)) ||
+						(0 <= c && c <= 6 && (r == 0 || r == 6)) ||
+						(2 <= r && r <= 4 && 2 <= c && c <= 4)) {
+						this.modules[row + r][col + c] = true;
+					} else {
+						this.modules[row + r][col + c] = false;
+					}
+				}
+			}
+		},
+
+		getBestMaskPattern: function() {
+
+			var minLostPoint = 0;
+			var pattern = 0;
+
+			for (var i = 0; i < 8; i++) {
+
+				this.makeImpl(true, i);
+
+				var lostPoint = QRUtil.getLostPoint(this);
+
+				if (i == 0 || minLostPoint > lostPoint) {
+					minLostPoint = lostPoint;
+					pattern = i;
+				}
+			}
+
+			return pattern;
+		},
+
+		createMovieClip: function(target_mc, instance_name, depth) {
+
+			var qr_mc = target_mc.createEmptyMovieClip(instance_name, depth);
+			var cs = 1;
+
+			this.make();
+
+			for (var row = 0; row < this.modules.length; row++) {
+
+				var y = row * cs;
+
+				for (var col = 0; col < this.modules[row].length; col++) {
+
+					var x = col * cs;
+					var dark = this.modules[row][col];
+
+					if (dark) {
+						qr_mc.beginFill(0, 100);
+						qr_mc.moveTo(x, y);
+						qr_mc.lineTo(x + cs, y);
+						qr_mc.lineTo(x + cs, y + cs);
+						qr_mc.lineTo(x, y + cs);
+						qr_mc.endFill();
+					}
+				}
+			}
+
+			return qr_mc;
+		},
+
+		setupTimingPattern: function() {
+
+			for (var r = 8; r < this.moduleCount - 8; r++) {
+				if (this.modules[r][6] != null) {
+					continue;
+				}
+				this.modules[r][6] = (r % 2 == 0);
+			}
+
+			for (var c = 8; c < this.moduleCount - 8; c++) {
+				if (this.modules[6][c] != null) {
+					continue;
+				}
+				this.modules[6][c] = (c % 2 == 0);
+			}
+		},
+
+		setupPositionAdjustPattern: function() {
+
+			var pos = QRUtil.getPatternPosition(this.typeNumber);
+
+			for (var i = 0; i < pos.length; i++) {
+
+				for (var j = 0; j < pos.length; j++) {
+
+					var row = pos[i];
+					var col = pos[j];
+
+					if (this.modules[row][col] != null) {
+						continue;
+					}
+
+					for (var r = -2; r <= 2; r++) {
+
+						for (var c = -2; c <= 2; c++) {
+
+							if (r == -2 || r == 2 || c == -2 || c == 2 ||
+								(r == 0 && c == 0)) {
+								this.modules[row + r][col + c] = true;
+							} else {
+								this.modules[row + r][col + c] = false;
+							}
+						}
+					}
+				}
+			}
+		},
+
+		setupTypeNumber: function(test) {
+
+			var bits = QRUtil.getBCHTypeNumber(this.typeNumber);
+
+			for (var i = 0; i < 18; i++) {
+				var mod = (!test && ((bits >> i) & 1) == 1);
+				this.modules[Math.floor(i / 3)][i % 3 + this.moduleCount - 8 - 3] = mod;
+			}
+
+			for (var i = 0; i < 18; i++) {
+				var mod = (!test && ((bits >> i) & 1) == 1);
+				this.modules[i % 3 + this.moduleCount - 8 - 3][Math.floor(i / 3)] = mod;
+			}
+		},
+
+		setupTypeInfo: function(test, maskPattern) {
+
+			var data = (this.errorCorrectLevel << 3) | maskPattern;
+			var bits = QRUtil.getBCHTypeInfo(data);
+
+			// vertical		
+			for (var i = 0; i < 15; i++) {
+
+				var mod = (!test && ((bits >> i) & 1) == 1);
+
+				if (i < 6) {
+					this.modules[i][8] = mod;
+				} else if (i < 8) {
+					this.modules[i + 1][8] = mod;
+				} else {
+					this.modules[this.moduleCount - 15 + i][8] = mod;
+				}
+			}
+
+			// horizontal
+			for (var i = 0; i < 15; i++) {
+
+				var mod = (!test && ((bits >> i) & 1) == 1);
+
+				if (i < 8) {
+					this.modules[8][this.moduleCount - i - 1] = mod;
+				} else if (i < 9) {
+					this.modules[8][15 - i - 1 + 1] = mod;
+				} else {
+					this.modules[8][15 - i - 1] = mod;
+				}
+			}
+
+			// fixed module
+			this.modules[this.moduleCount - 8][8] = (!test);
+
+		},
+
+		mapData: function(data, maskPattern) {
+
+			var inc = -1;
+			var row = this.moduleCount - 1;
+			var bitIndex = 7;
+			var byteIndex = 0;
+
+			for (var col = this.moduleCount - 1; col > 0; col -= 2) {
+
+				if (col == 6) col--;
+
+				while (true) {
+
+					for (var c = 0; c < 2; c++) {
+
+						if (this.modules[row][col - c] == null) {
+
+							var dark = false;
+
+							if (byteIndex < data.length) {
+								dark = (((data[byteIndex] >>> bitIndex) & 1) == 1);
+							}
+
+							var mask = QRUtil.getMask(maskPattern, row, col - c);
+
+							if (mask) {
+								dark = !dark;
+							}
+
+							this.modules[row][col - c] = dark;
+							bitIndex--;
+
+							if (bitIndex == -1) {
+								byteIndex++;
+								bitIndex = 7;
+							}
+						}
+					}
+
+					row += inc;
+
+					if (row < 0 || this.moduleCount <= row) {
+						row -= inc;
+						inc = -inc;
+						break;
+					}
+				}
+			}
+
+		}
+
+	};
+
+	QRCode.PAD0 = 0xEC;
+	QRCode.PAD1 = 0x11;
+
+	QRCode.createData = function(typeNumber, errorCorrectLevel, dataList) {
+
+		var rsBlocks = QRRSBlock.getRSBlocks(typeNumber, errorCorrectLevel);
+
+		var buffer = new QRBitBuffer();
+
+		for (var i = 0; i < dataList.length; i++) {
+			var data = dataList[i];
+			buffer.put(data.mode, 4);
+			buffer.put(data.getLength(), QRUtil.getLengthInBits(data.mode, typeNumber));
+			data.write(buffer);
+		}
+
+		// calc num max data.
+		var totalDataCount = 0;
+		for (var i = 0; i < rsBlocks.length; i++) {
+			totalDataCount += rsBlocks[i].dataCount;
+		}
+
+		if (buffer.getLengthInBits() > totalDataCount * 8) {
+			throw new Error("code length overflow. (" +
+				buffer.getLengthInBits() +
+				">" +
+				totalDataCount * 8 +
+				")");
+		}
+
+		// end code
+		if (buffer.getLengthInBits() + 4 <= totalDataCount * 8) {
+			buffer.put(0, 4);
+		}
+
+		// padding
+		while (buffer.getLengthInBits() % 8 != 0) {
+			buffer.putBit(false);
+		}
+
+		// padding
+		while (true) {
+
+			if (buffer.getLengthInBits() >= totalDataCount * 8) {
+				break;
+			}
+			buffer.put(QRCode.PAD0, 8);
+
+			if (buffer.getLengthInBits() >= totalDataCount * 8) {
+				break;
+			}
+			buffer.put(QRCode.PAD1, 8);
+		}
+
+		return QRCode.createBytes(buffer, rsBlocks);
+	}
+
+	QRCode.createBytes = function(buffer, rsBlocks) {
+
+		var offset = 0;
+
+		var maxDcCount = 0;
+		var maxEcCount = 0;
+
+		var dcdata = new Array(rsBlocks.length);
+		var ecdata = new Array(rsBlocks.length);
+
+		for (var r = 0; r < rsBlocks.length; r++) {
+
+			var dcCount = rsBlocks[r].dataCount;
+			var ecCount = rsBlocks[r].totalCount - dcCount;
+
+			maxDcCount = Math.max(maxDcCount, dcCount);
+			maxEcCount = Math.max(maxEcCount, ecCount);
+
+			dcdata[r] = new Array(dcCount);
+
+			for (var i = 0; i < dcdata[r].length; i++) {
+				dcdata[r][i] = 0xff & buffer.buffer[i + offset];
+			}
+			offset += dcCount;
+
+			var rsPoly = QRUtil.getErrorCorrectPolynomial(ecCount);
+			var rawPoly = new QRPolynomial(dcdata[r], rsPoly.getLength() - 1);
+
+			var modPoly = rawPoly.mod(rsPoly);
+			ecdata[r] = new Array(rsPoly.getLength() - 1);
+			for (var i = 0; i < ecdata[r].length; i++) {
+				var modIndex = i + modPoly.getLength() - ecdata[r].length;
+				ecdata[r][i] = (modIndex >= 0) ? modPoly.get(modIndex) : 0;
+			}
+
+		}
+
+		var totalCodeCount = 0;
+		for (var i = 0; i < rsBlocks.length; i++) {
+			totalCodeCount += rsBlocks[i].totalCount;
+		}
+
+		var data = new Array(totalCodeCount);
+		var index = 0;
+
+		for (var i = 0; i < maxDcCount; i++) {
+			for (var r = 0; r < rsBlocks.length; r++) {
+				if (i < dcdata[r].length) {
+					data[index++] = dcdata[r][i];
+				}
+			}
+		}
+
+		for (var i = 0; i < maxEcCount; i++) {
+			for (var r = 0; r < rsBlocks.length; r++) {
+				if (i < ecdata[r].length) {
+					data[index++] = ecdata[r][i];
+				}
+			}
+		}
+
+		return data;
+
+	}
+
+	//---------------------------------------------------------------------
+	// QRMode
+	//---------------------------------------------------------------------
+
+	var QRMode = {
+		MODE_NUMBER: 1 << 0,
+		MODE_ALPHA_NUM: 1 << 1,
+		MODE_8BIT_BYTE: 1 << 2,
+		MODE_KANJI: 1 << 3
+	};
+
+	//---------------------------------------------------------------------
+	// QRErrorCorrectLevel
+	//---------------------------------------------------------------------
+
+	var QRErrorCorrectLevel = {
+		L: 1,
+		M: 0,
+		Q: 3,
+		H: 2
+	};
+
+	//---------------------------------------------------------------------
+	// QRMaskPattern
+	//---------------------------------------------------------------------
+
+	var QRMaskPattern = {
+		PATTERN000: 0,
+		PATTERN001: 1,
+		PATTERN010: 2,
+		PATTERN011: 3,
+		PATTERN100: 4,
+		PATTERN101: 5,
+		PATTERN110: 6,
+		PATTERN111: 7
+	};
+
+	//---------------------------------------------------------------------
+	// QRUtil
+	//---------------------------------------------------------------------
+
+	var QRUtil = {
+
+		PATTERN_POSITION_TABLE: [
+			[],
+			[6, 18],
+			[6, 22],
+			[6, 26],
+			[6, 30],
+			[6, 34],
+			[6, 22, 38],
+			[6, 24, 42],
+			[6, 26, 46],
+			[6, 28, 50],
+			[6, 30, 54],
+			[6, 32, 58],
+			[6, 34, 62],
+			[6, 26, 46, 66],
+			[6, 26, 48, 70],
+			[6, 26, 50, 74],
+			[6, 30, 54, 78],
+			[6, 30, 56, 82],
+			[6, 30, 58, 86],
+			[6, 34, 62, 90],
+			[6, 28, 50, 72, 94],
+			[6, 26, 50, 74, 98],
+			[6, 30, 54, 78, 102],
+			[6, 28, 54, 80, 106],
+			[6, 32, 58, 84, 110],
+			[6, 30, 58, 86, 114],
+			[6, 34, 62, 90, 118],
+			[6, 26, 50, 74, 98, 122],
+			[6, 30, 54, 78, 102, 126],
+			[6, 26, 52, 78, 104, 130],
+			[6, 30, 56, 82, 108, 134],
+			[6, 34, 60, 86, 112, 138],
+			[6, 30, 58, 86, 114, 142],
+			[6, 34, 62, 90, 118, 146],
+			[6, 30, 54, 78, 102, 126, 150],
+			[6, 24, 50, 76, 102, 128, 154],
+			[6, 28, 54, 80, 106, 132, 158],
+			[6, 32, 58, 84, 110, 136, 162],
+			[6, 26, 54, 82, 110, 138, 166],
+			[6, 30, 58, 86, 114, 142, 170]
+		],
+
+		G15: (1 << 10) | (1 << 8) | (1 << 5) | (1 << 4) | (1 << 2) | (1 << 1) | (1 << 0),
+		G18: (1 << 12) | (1 << 11) | (1 << 10) | (1 << 9) | (1 << 8) | (1 << 5) | (1 << 2) | (1 << 0),
+		G15_MASK: (1 << 14) | (1 << 12) | (1 << 10) | (1 << 4) | (1 << 1),
+
+		getBCHTypeInfo: function(data) {
+			var d = data << 10;
+			while (QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G15) >= 0) {
+				d ^= (QRUtil.G15 << (QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G15)));
+			}
+			return ((data << 10) | d) ^ QRUtil.G15_MASK;
+		},
+
+		getBCHTypeNumber: function(data) {
+			var d = data << 12;
+			while (QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G18) >= 0) {
+				d ^= (QRUtil.G18 << (QRUtil.getBCHDigit(d) - QRUtil.getBCHDigit(QRUtil.G18)));
+			}
+			return (data << 12) | d;
+		},
+
+		getBCHDigit: function(data) {
+
+			var digit = 0;
+
+			while (data != 0) {
+				digit++;
+				data >>>= 1;
+			}
+
+			return digit;
+		},
+
+		getPatternPosition: function(typeNumber) {
+			return QRUtil.PATTERN_POSITION_TABLE[typeNumber - 1];
+		},
+
+		getMask: function(maskPattern, i, j) {
+
+			switch (maskPattern) {
+
+				case QRMaskPattern.PATTERN000:
+					return (i + j) % 2 == 0;
+				case QRMaskPattern.PATTERN001:
+					return i % 2 == 0;
+				case QRMaskPattern.PATTERN010:
+					return j % 3 == 0;
+				case QRMaskPattern.PATTERN011:
+					return (i + j) % 3 == 0;
+				case QRMaskPattern.PATTERN100:
+					return (Math.floor(i / 2) + Math.floor(j / 3)) % 2 == 0;
+				case QRMaskPattern.PATTERN101:
+					return (i * j) % 2 + (i * j) % 3 == 0;
+				case QRMaskPattern.PATTERN110:
+					return ((i * j) % 2 + (i * j) % 3) % 2 == 0;
+				case QRMaskPattern.PATTERN111:
+					return ((i * j) % 3 + (i + j) % 2) % 2 == 0;
+
+				default:
+					throw new Error("bad maskPattern:" + maskPattern);
+			}
+		},
+
+		getErrorCorrectPolynomial: function(errorCorrectLength) {
+
+			var a = new QRPolynomial([1], 0);
+
+			for (var i = 0; i < errorCorrectLength; i++) {
+				a = a.multiply(new QRPolynomial([1, QRMath.gexp(i)], 0));
+			}
+
+			return a;
+		},
+
+		getLengthInBits: function(mode, type) {
+
+			if (1 <= type && type < 10) {
+
+				// 1 - 9
+
+				switch (mode) {
+					case QRMode.MODE_NUMBER:
+						return 10;
+					case QRMode.MODE_ALPHA_NUM:
+						return 9;
+					case QRMode.MODE_8BIT_BYTE:
+						return 8;
+					case QRMode.MODE_KANJI:
+						return 8;
+					default:
+						throw new Error("mode:" + mode);
+				}
+
+			} else if (type < 27) {
+
+				// 10 - 26
+
+				switch (mode) {
+					case QRMode.MODE_NUMBER:
+						return 12;
+					case QRMode.MODE_ALPHA_NUM:
+						return 11;
+					case QRMode.MODE_8BIT_BYTE:
+						return 16;
+					case QRMode.MODE_KANJI:
+						return 10;
+					default:
+						throw new Error("mode:" + mode);
+				}
+
+			} else if (type < 41) {
+
+				// 27 - 40
+
+				switch (mode) {
+					case QRMode.MODE_NUMBER:
+						return 14;
+					case QRMode.MODE_ALPHA_NUM:
+						return 13;
+					case QRMode.MODE_8BIT_BYTE:
+						return 16;
+					case QRMode.MODE_KANJI:
+						return 12;
+					default:
+						throw new Error("mode:" + mode);
+				}
+
+			} else {
+				throw new Error("type:" + type);
+			}
+		},
+
+		getLostPoint: function(qrCode) {
+
+			var moduleCount = qrCode.getModuleCount();
+
+			var lostPoint = 0;
+
+			// LEVEL1
+
+			for (var row = 0; row < moduleCount; row++) {
+
+				for (var col = 0; col < moduleCount; col++) {
+
+					var sameCount = 0;
+					var dark = qrCode.isDark(row, col);
+
+					for (var r = -1; r <= 1; r++) {
+
+						if (row + r < 0 || moduleCount <= row + r) {
+							continue;
+						}
+
+						for (var c = -1; c <= 1; c++) {
+
+							if (col + c < 0 || moduleCount <= col + c) {
+								continue;
+							}
+
+							if (r == 0 && c == 0) {
+								continue;
+							}
+
+							if (dark == qrCode.isDark(row + r, col + c)) {
+								sameCount++;
+							}
+						}
+					}
+
+					if (sameCount > 5) {
+						lostPoint += (3 + sameCount - 5);
+					}
+				}
+			}
+
+			// LEVEL2
+
+			for (var row = 0; row < moduleCount - 1; row++) {
+				for (var col = 0; col < moduleCount - 1; col++) {
+					var count = 0;
+					if (qrCode.isDark(row, col)) count++;
+					if (qrCode.isDark(row + 1, col)) count++;
+					if (qrCode.isDark(row, col + 1)) count++;
+					if (qrCode.isDark(row + 1, col + 1)) count++;
+					if (count == 0 || count == 4) {
+						lostPoint += 3;
+					}
+				}
+			}
+
+			// LEVEL3
+
+			for (var row = 0; row < moduleCount; row++) {
+				for (var col = 0; col < moduleCount - 6; col++) {
+					if (qrCode.isDark(row, col) &&
+						!qrCode.isDark(row, col + 1) &&
+						qrCode.isDark(row, col + 2) &&
+						qrCode.isDark(row, col + 3) &&
+						qrCode.isDark(row, col + 4) &&
+						!qrCode.isDark(row, col + 5) &&
+						qrCode.isDark(row, col + 6)) {
+						lostPoint += 40;
+					}
+				}
+			}
+
+			for (var col = 0; col < moduleCount; col++) {
+				for (var row = 0; row < moduleCount - 6; row++) {
+					if (qrCode.isDark(row, col) &&
+						!qrCode.isDark(row + 1, col) &&
+						qrCode.isDark(row + 2, col) &&
+						qrCode.isDark(row + 3, col) &&
+						qrCode.isDark(row + 4, col) &&
+						!qrCode.isDark(row + 5, col) &&
+						qrCode.isDark(row + 6, col)) {
+						lostPoint += 40;
+					}
+				}
+			}
+
+			// LEVEL4
+
+			var darkCount = 0;
+
+			for (var col = 0; col < moduleCount; col++) {
+				for (var row = 0; row < moduleCount; row++) {
+					if (qrCode.isDark(row, col)) {
+						darkCount++;
+					}
+				}
+			}
+
+			var ratio = Math.abs(100 * darkCount / moduleCount / moduleCount - 50) / 5;
+			lostPoint += ratio * 10;
+
+			return lostPoint;
+		}
+
+	};
+
+
+	//---------------------------------------------------------------------
+	// QRMath
+	//---------------------------------------------------------------------
+
+	var QRMath = {
+
+		glog: function(n) {
+
+			if (n < 1) {
+				throw new Error("glog(" + n + ")");
+			}
+
+			return QRMath.LOG_TABLE[n];
+		},
+
+		gexp: function(n) {
+
+			while (n < 0) {
+				n += 255;
+			}
+
+			while (n >= 256) {
+				n -= 255;
+			}
+
+			return QRMath.EXP_TABLE[n];
+		},
+
+		EXP_TABLE: new Array(256),
+
+		LOG_TABLE: new Array(256)
+
+	};
+
+	for (var i = 0; i < 8; i++) {
+		QRMath.EXP_TABLE[i] = 1 << i;
+	}
+	for (var i = 8; i < 256; i++) {
+		QRMath.EXP_TABLE[i] = QRMath.EXP_TABLE[i - 4] ^
+			QRMath.EXP_TABLE[i - 5] ^
+			QRMath.EXP_TABLE[i - 6] ^
+			QRMath.EXP_TABLE[i - 8];
+	}
+	for (var i = 0; i < 255; i++) {
+		QRMath.LOG_TABLE[QRMath.EXP_TABLE[i]] = i;
+	}
+
+	//---------------------------------------------------------------------
+	// QRPolynomial
+	//---------------------------------------------------------------------
+
+	function QRPolynomial(num, shift) {
+
+		if (num.length == undefined) {
+			throw new Error(num.length + "/" + shift);
+		}
+
+		var offset = 0;
+
+		while (offset < num.length && num[offset] == 0) {
+			offset++;
+		}
+
+		this.num = new Array(num.length - offset + shift);
+		for (var i = 0; i < num.length - offset; i++) {
+			this.num[i] = num[i + offset];
+		}
+	}
+
+	QRPolynomial.prototype = {
+
+		get: function(index) {
+			return this.num[index];
+		},
+
+		getLength: function() {
+			return this.num.length;
+		},
+
+		multiply: function(e) {
+
+			var num = new Array(this.getLength() + e.getLength() - 1);
+
+			for (var i = 0; i < this.getLength(); i++) {
+				for (var j = 0; j < e.getLength(); j++) {
+					num[i + j] ^= QRMath.gexp(QRMath.glog(this.get(i)) + QRMath.glog(e.get(j)));
+				}
+			}
+
+			return new QRPolynomial(num, 0);
+		},
+
+		mod: function(e) {
+
+			if (this.getLength() - e.getLength() < 0) {
+				return this;
+			}
+
+			var ratio = QRMath.glog(this.get(0)) - QRMath.glog(e.get(0));
+
+			var num = new Array(this.getLength());
+
+			for (var i = 0; i < this.getLength(); i++) {
+				num[i] = this.get(i);
+			}
+
+			for (var i = 0; i < e.getLength(); i++) {
+				num[i] ^= QRMath.gexp(QRMath.glog(e.get(i)) + ratio);
+			}
+
+			// recursive call
+			return new QRPolynomial(num, 0).mod(e);
+		}
+	};
+
+	//---------------------------------------------------------------------
+	// QRRSBlock
+	//---------------------------------------------------------------------
+
+	function QRRSBlock(totalCount, dataCount) {
+		this.totalCount = totalCount;
+		this.dataCount = dataCount;
+	}
+
+	QRRSBlock.RS_BLOCK_TABLE = [
+
+		// L
+		// M
+		// Q
+		// H
+
+		// 1
+		[1, 26, 19],
+		[1, 26, 16],
+		[1, 26, 13],
+		[1, 26, 9],
+
+		// 2
+		[1, 44, 34],
+		[1, 44, 28],
+		[1, 44, 22],
+		[1, 44, 16],
+
+		// 3
+		[1, 70, 55],
+		[1, 70, 44],
+		[2, 35, 17],
+		[2, 35, 13],
+
+		// 4		
+		[1, 100, 80],
+		[2, 50, 32],
+		[2, 50, 24],
+		[4, 25, 9],
+
+		// 5
+		[1, 134, 108],
+		[2, 67, 43],
+		[2, 33, 15, 2, 34, 16],
+		[2, 33, 11, 2, 34, 12],
+
+		// 6
+		[2, 86, 68],
+		[4, 43, 27],
+		[4, 43, 19],
+		[4, 43, 15],
+
+		// 7		
+		[2, 98, 78],
+		[4, 49, 31],
+		[2, 32, 14, 4, 33, 15],
+		[4, 39, 13, 1, 40, 14],
+
+		// 8
+		[2, 121, 97],
+		[2, 60, 38, 2, 61, 39],
+		[4, 40, 18, 2, 41, 19],
+		[4, 40, 14, 2, 41, 15],
+
+		// 9
+		[2, 146, 116],
+		[3, 58, 36, 2, 59, 37],
+		[4, 36, 16, 4, 37, 17],
+		[4, 36, 12, 4, 37, 13],
+
+		// 10		
+		[2, 86, 68, 2, 87, 69],
+		[4, 69, 43, 1, 70, 44],
+		[6, 43, 19, 2, 44, 20],
+		[6, 43, 15, 2, 44, 16],
+
+		// 11
+		[4, 101, 81],
+		[1, 80, 50, 4, 81, 51],
+		[4, 50, 22, 4, 51, 23],
+		[3, 36, 12, 8, 37, 13],
+
+		// 12
+		[2, 116, 92, 2, 117, 93],
+		[6, 58, 36, 2, 59, 37],
+		[4, 46, 20, 6, 47, 21],
+		[7, 42, 14, 4, 43, 15],
+
+		// 13
+		[4, 133, 107],
+		[8, 59, 37, 1, 60, 38],
+		[8, 44, 20, 4, 45, 21],
+		[12, 33, 11, 4, 34, 12],
+
+		// 14
+		[3, 145, 115, 1, 146, 116],
+		[4, 64, 40, 5, 65, 41],
+		[11, 36, 16, 5, 37, 17],
+		[11, 36, 12, 5, 37, 13],
+
+		// 15
+		[5, 109, 87, 1, 110, 88],
+		[5, 65, 41, 5, 66, 42],
+		[5, 54, 24, 7, 55, 25],
+		[11, 36, 12],
+
+		// 16
+		[5, 122, 98, 1, 123, 99],
+		[7, 73, 45, 3, 74, 46],
+		[15, 43, 19, 2, 44, 20],
+		[3, 45, 15, 13, 46, 16],
+
+		// 17
+		[1, 135, 107, 5, 136, 108],
+		[10, 74, 46, 1, 75, 47],
+		[1, 50, 22, 15, 51, 23],
+		[2, 42, 14, 17, 43, 15],
+
+		// 18
+		[5, 150, 120, 1, 151, 121],
+		[9, 69, 43, 4, 70, 44],
+		[17, 50, 22, 1, 51, 23],
+		[2, 42, 14, 19, 43, 15],
+
+		// 19
+		[3, 141, 113, 4, 142, 114],
+		[3, 70, 44, 11, 71, 45],
+		[17, 47, 21, 4, 48, 22],
+		[9, 39, 13, 16, 40, 14],
+
+		// 20
+		[3, 135, 107, 5, 136, 108],
+		[3, 67, 41, 13, 68, 42],
+		[15, 54, 24, 5, 55, 25],
+		[15, 43, 15, 10, 44, 16],
+
+		// 21
+		[4, 144, 116, 4, 145, 117],
+		[17, 68, 42],
+		[17, 50, 22, 6, 51, 23],
+		[19, 46, 16, 6, 47, 17],
+
+		// 22
+		[2, 139, 111, 7, 140, 112],
+		[17, 74, 46],
+		[7, 54, 24, 16, 55, 25],
+		[34, 37, 13],
+
+		// 23
+		[4, 151, 121, 5, 152, 122],
+		[4, 75, 47, 14, 76, 48],
+		[11, 54, 24, 14, 55, 25],
+		[16, 45, 15, 14, 46, 16],
+
+		// 24
+		[6, 147, 117, 4, 148, 118],
+		[6, 73, 45, 14, 74, 46],
+		[11, 54, 24, 16, 55, 25],
+		[30, 46, 16, 2, 47, 17],
+
+		// 25
+		[8, 132, 106, 4, 133, 107],
+		[8, 75, 47, 13, 76, 48],
+		[7, 54, 24, 22, 55, 25],
+		[22, 45, 15, 13, 46, 16],
+
+		// 26
+		[10, 142, 114, 2, 143, 115],
+		[19, 74, 46, 4, 75, 47],
+		[28, 50, 22, 6, 51, 23],
+		[33, 46, 16, 4, 47, 17],
+
+		// 27
+		[8, 152, 122, 4, 153, 123],
+		[22, 73, 45, 3, 74, 46],
+		[8, 53, 23, 26, 54, 24],
+		[12, 45, 15, 28, 46, 16],
+
+		// 28
+		[3, 147, 117, 10, 148, 118],
+		[3, 73, 45, 23, 74, 46],
+		[4, 54, 24, 31, 55, 25],
+		[11, 45, 15, 31, 46, 16],
+
+		// 29
+		[7, 146, 116, 7, 147, 117],
+		[21, 73, 45, 7, 74, 46],
+		[1, 53, 23, 37, 54, 24],
+		[19, 45, 15, 26, 46, 16],
+
+		// 30
+		[5, 145, 115, 10, 146, 116],
+		[19, 75, 47, 10, 76, 48],
+		[15, 54, 24, 25, 55, 25],
+		[23, 45, 15, 25, 46, 16],
+
+		// 31
+		[13, 145, 115, 3, 146, 116],
+		[2, 74, 46, 29, 75, 47],
+		[42, 54, 24, 1, 55, 25],
+		[23, 45, 15, 28, 46, 16],
+
+		// 32
+		[17, 145, 115],
+		[10, 74, 46, 23, 75, 47],
+		[10, 54, 24, 35, 55, 25],
+		[19, 45, 15, 35, 46, 16],
+
+		// 33
+		[17, 145, 115, 1, 146, 116],
+		[14, 74, 46, 21, 75, 47],
+		[29, 54, 24, 19, 55, 25],
+		[11, 45, 15, 46, 46, 16],
+
+		// 34
+		[13, 145, 115, 6, 146, 116],
+		[14, 74, 46, 23, 75, 47],
+		[44, 54, 24, 7, 55, 25],
+		[59, 46, 16, 1, 47, 17],
+
+		// 35
+		[12, 151, 121, 7, 152, 122],
+		[12, 75, 47, 26, 76, 48],
+		[39, 54, 24, 14, 55, 25],
+		[22, 45, 15, 41, 46, 16],
+
+		// 36
+		[6, 151, 121, 14, 152, 122],
+		[6, 75, 47, 34, 76, 48],
+		[46, 54, 24, 10, 55, 25],
+		[2, 45, 15, 64, 46, 16],
+
+		// 37
+		[17, 152, 122, 4, 153, 123],
+		[29, 74, 46, 14, 75, 47],
+		[49, 54, 24, 10, 55, 25],
+		[24, 45, 15, 46, 46, 16],
+
+		// 38
+		[4, 152, 122, 18, 153, 123],
+		[13, 74, 46, 32, 75, 47],
+		[48, 54, 24, 14, 55, 25],
+		[42, 45, 15, 32, 46, 16],
+
+		// 39
+		[20, 147, 117, 4, 148, 118],
+		[40, 75, 47, 7, 76, 48],
+		[43, 54, 24, 22, 55, 25],
+		[10, 45, 15, 67, 46, 16],
+
+		// 40
+		[19, 148, 118, 6, 149, 119],
+		[18, 75, 47, 31, 76, 48],
+		[34, 54, 24, 34, 55, 25],
+		[20, 45, 15, 61, 46, 16]
+	];
+
+	QRRSBlock.getRSBlocks = function(typeNumber, errorCorrectLevel) {
+
+		var rsBlock = QRRSBlock.getRsBlockTable(typeNumber, errorCorrectLevel);
+
+		if (rsBlock == undefined) {
+			throw new Error("bad rs block @ typeNumber:" + typeNumber + "/errorCorrectLevel:" + errorCorrectLevel);
+		}
+
+		var length = rsBlock.length / 3;
+
+		var list = new Array();
+
+		for (var i = 0; i < length; i++) {
+
+			var count = rsBlock[i * 3 + 0];
+			var totalCount = rsBlock[i * 3 + 1];
+			var dataCount = rsBlock[i * 3 + 2];
+
+			for (var j = 0; j < count; j++) {
+				list.push(new QRRSBlock(totalCount, dataCount));
+			}
+		}
+
+		return list;
+	}
+
+	QRRSBlock.getRsBlockTable = function(typeNumber, errorCorrectLevel) {
+
+		switch (errorCorrectLevel) {
+			case QRErrorCorrectLevel.L:
+				return QRRSBlock.RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 0];
+			case QRErrorCorrectLevel.M:
+				return QRRSBlock.RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 1];
+			case QRErrorCorrectLevel.Q:
+				return QRRSBlock.RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 2];
+			case QRErrorCorrectLevel.H:
+				return QRRSBlock.RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 3];
+			default:
+				return undefined;
+		}
+	}
+
+	//---------------------------------------------------------------------
+	// QRBitBuffer
+	//---------------------------------------------------------------------
+
+	function QRBitBuffer() {
+		this.buffer = new Array();
+		this.length = 0;
+	}
+
+	QRBitBuffer.prototype = {
+
+		get: function(index) {
+			var bufIndex = Math.floor(index / 8);
+			return ((this.buffer[bufIndex] >>> (7 - index % 8)) & 1) == 1;
+		},
+
+		put: function(num, length) {
+			for (var i = 0; i < length; i++) {
+				this.putBit(((num >>> (length - i - 1)) & 1) == 1);
+			}
+		},
+
+		getLengthInBits: function() {
+			return this.length;
+		},
+
+		putBit: function(bit) {
+
+			var bufIndex = Math.floor(this.length / 8);
+			if (this.buffer.length <= bufIndex) {
+				this.buffer.push(0);
+			}
+
+			if (bit) {
+				this.buffer[bufIndex] |= (0x80 >>> (this.length % 8));
+			}
+
+			this.length++;
+		}
+	};
+
+	//---------------------------------------------------------------------
+	// Support Chinese
+	//---------------------------------------------------------------------
+	function utf16To8(text) {
+		var result = '';
+		var c;
+		for (var i = 0; i < text.length; i++) {
+			c = text.charCodeAt(i);
+			if (c >= 0x0001 && c <= 0x007F) {
+				result += text.charAt(i);
+			} else if (c > 0x07FF) {
+				result += String.fromCharCode(0xE0 | c >> 12 & 0x0F);
+				result += String.fromCharCode(0x80 | c >> 6 & 0x3F);
+				result += String.fromCharCode(0x80 | c >> 0 & 0x3F);
+			} else {
+				result += String.fromCharCode(0xC0 | c >> 6 & 0x1F);
+				result += String.fromCharCode(0x80 | c >> 0 & 0x3F);
+			}
+		}
+		return result;
+	}
+
+	uQRCode = {
+		
+		errorCorrectLevel: QRErrorCorrectLevel,
+		
+		defaults: {
+			size: 256,
+			margin: 0,
+			backgroundColor: '#ffffff',
+			foregroundColor: '#000000',
+			fileType: 'png', // 'jpg', 'png'
+			errorCorrectLevel: QRErrorCorrectLevel.H,
+			typeNumber: -1
+		},
+
+		make: function(options) {
+			var defaultOptions = {
+				canvasId: options.canvasId,
+				componentInstance: options.componentInstance,
+				text: options.text,
+				size: this.defaults.size,
+				margin: this.defaults.margin,
+				backgroundColor: this.defaults.backgroundColor,
+				foregroundColor: this.defaults.foregroundColor,
+				fileType: this.defaults.fileType,
+				errorCorrectLevel: this.defaults.errorCorrectLevel,
+				typeNumber: this.defaults.typeNumber
+			};
+			if (options) {
+				for (var i in options) {
+					defaultOptions[i] = options[i];
+				}
+			}
+			options = defaultOptions;
+			if (!options.canvasId) {
+				console.error('uQRCode: Please set canvasId!');
+				return;
+			}
+
+			function createCanvas() {
+				var qrcode = new QRCode(options.typeNumber, options.errorCorrectLevel);
+				qrcode.addData(utf16To8(options.text));
+				qrcode.make();
+
+				var ctx = uni.createCanvasContext(options.canvasId, options.componentInstance);
+				ctx.setFillStyle(options.backgroundColor);
+				ctx.fillRect(0, 0, options.size, options.size);
+
+				var tileW = (options.size - options.margin * 2) / qrcode.getModuleCount();
+				var tileH = tileW;
+
+				for (var row = 0; row < qrcode.getModuleCount(); row++) {
+					for (var col = 0; col < qrcode.getModuleCount(); col++) {
+						var style = qrcode.isDark(row, col) ? options.foregroundColor : options.backgroundColor;
+						ctx.setFillStyle(style);
+						var x = Math.round(col * tileW) + options.margin;
+						var y = Math.round(row * tileH) + options.margin;
+						var w = Math.ceil((col + 1) * tileW) - Math.floor(col * tileW);
+						var h = Math.ceil((row + 1) * tileW) - Math.floor(row * tileW);
+						ctx.fillRect(x, y, w, h);
+					}
+				}
+
+				setTimeout(function() {
+					ctx.draw(false, (function() {
+						setTimeout(function() {
+							uni.canvasToTempFilePath({
+								canvasId: options.canvasId,
+								fileType: options.fileType,
+								width: options.size,
+								height: options.size,
+								destWidth: options.size,
+								destHeight: options.size,
+								success: function(res) {
+									options.success && options.success(res.tempFilePath);
+								},
+								fail: function(error) {
+									options.fail && options.fail(error);
+								},
+								complete: function(res) {
+									options.complete && options.complete(res);
+								}
+							}, options.componentInstance);
+						}, options.text.length + 100);
+					})());
+				}, 150);
+			}
+			
+			createCanvas();
+		}
+
+	}
+
+})()
+
+export default uQRCode

+ 461 - 0
js_sdk/ican-H5Api/ican-H5Api.js

@@ -0,0 +1,461 @@
+//#ifdef H5
+/** clipboard.js v2.0.4**/
+!function (t, e) {
+    try {
+        window.ClipboardJS = e();
+    } catch (e) {
+    }
+    ;"object" == typeof exports && "object" == typeof module ? module.exports = e() : "function" == typeof define && define.amd ? define([], e) : "object" == typeof exports ? exports.ClipboardJS = e() : t.ClipboardJS = e()
+}(this, function () {
+    return function (n) {
+        var o = {};
+
+        function r(t) {
+            if (o[t]) return o[t].exports;
+            var e = o[t] = {i: t, l: !1, exports: {}};
+            return n[t].call(e.exports, e, e.exports, r), e.l = !0, e.exports
+        }
+
+        return r.m = n, r.c = o, r.d = function (t, e, n) {
+            r.o(t, e) || Object.defineProperty(t, e, {enumerable: !0, get: n})
+        }, r.r = function (t) {
+            "undefined" != typeof Symbol && Symbol.toStringTag && Object.defineProperty(t, Symbol.toStringTag, {value: "Module"}), Object.defineProperty(t, "__esModule", {value: !0})
+        }, r.t = function (e, t) {
+            if (1 & t && (e = r(e)), 8 & t) return e;
+            if (4 & t && "object" == typeof e && e && e.__esModule) return e;
+            var n = Object.create(null);
+            if (r.r(n), Object.defineProperty(n, "default", {
+                enumerable: !0,
+                value: e
+            }), 2 & t && "string" != typeof e) for (var o in e) r.d(n, o, function (t) {
+                return e[t]
+            }.bind(null, o));
+            return n
+        }, r.n = function (t) {
+            var e = t && t.__esModule ? function () {
+                return t.default
+            } : function () {
+                return t
+            };
+            return r.d(e, "a", e), e
+        }, r.o = function (t, e) {
+            return Object.prototype.hasOwnProperty.call(t, e)
+        }, r.p = "", r(r.s = 0)
+    }([function (t, e, n) {
+        "use strict";
+        var r = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (t) {
+            return typeof t
+        } : function (t) {
+            return t && "function" == typeof Symbol && t.constructor === Symbol && t !== Symbol.prototype ? "symbol" : typeof t
+        }, i = function () {
+            function o(t, e) {
+                for (var n = 0; n < e.length; n++) {
+                    var o = e[n];
+                    o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(t, o.key, o)
+                }
+            }
+
+            return function (t, e, n) {
+                return e && o(t.prototype, e), n && o(t, n), t
+            }
+        }(), a = o(n(1)), c = o(n(3)), u = o(n(4));
+
+        function o(t) {
+            return t && t.__esModule ? t : {default: t}
+        }
+
+        var l = function (t) {
+            function o(t, e) {
+                !function (t, e) {
+                    if (!(t instanceof e)) throw new TypeError("Cannot call a class as a function")
+                }(this, o);
+                var n = function (t, e) {
+                    if (!t) throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
+                    return !e || "object" != typeof e && "function" != typeof e ? t : e
+                }(this, (o.__proto__ || Object.getPrototypeOf(o)).call(this));
+                return n.resolveOptions(e), n.listenClick(t), n
+            }
+
+            return function (t, e) {
+                if ("function" != typeof e && null !== e) throw new TypeError("Super expression must either be null or a function, not " + typeof e);
+                t.prototype = Object.create(e && e.prototype, {
+                    constructor: {
+                        value: t,
+                        enumerable: !1,
+                        writable: !0,
+                        configurable: !0
+                    }
+                }), e && (Object.setPrototypeOf ? Object.setPrototypeOf(t, e) : t.__proto__ = e)
+            }(o, c.default), i(o, [{
+                key: "resolveOptions", value: function () {
+                    var t = 0 < arguments.length && void 0 !== arguments[0] ? arguments[0] : {};
+                    this.action = "function" == typeof t.action ? t.action : this.defaultAction, this.target = "function" == typeof t.target ? t.target : this.defaultTarget, this.text = "function" == typeof t.text ? t.text : this.defaultText, this.container = "object" === r(t.container) ? t.container : document.body
+                }
+            }, {
+                key: "listenClick", value: function (t) {
+                    var e = this;
+                    this.listener = (0, u.default)(t, "click", function (t) {
+                        return e.onClick(t)
+                    })
+                }
+            }, {
+                key: "onClick", value: function (t) {
+                    var e = t.delegateTarget || t.currentTarget;
+                    this.clipboardAction && (this.clipboardAction = null), this.clipboardAction = new a.default({
+                        action: this.action(e),
+                        target: this.target(e),
+                        text: this.text(e),
+                        container: this.container,
+                        trigger: e,
+                        emitter: this
+                    })
+                }
+            }, {
+                key: "defaultAction", value: function (t) {
+                    return s("action", t) || 'copy'
+                }
+            }, {
+                key: "defaultTarget", value: function (t) {
+                    var e = s("target", t);
+                    if (e) {
+                        return document.querySelector(e)
+                    }
+                }
+            }, {
+                key: "defaultText", value: function (t) {
+                    return s("text", t) || this.text
+                }
+            }, {
+                key: "destroy", value: function () {
+                    this.listener.destroy(), this.clipboardAction && (this.clipboardAction.destroy(), this.clipboardAction = null)
+                }
+            }], [{
+                key: "isSupported", value: function () {
+                    var t = 0 < arguments.length && void 0 !== arguments[0] ? arguments[0] : ["copy", "cut"],
+                        e = "string" == typeof t ? [t] : t, n = !!document.queryCommandSupported;
+                    return e.forEach(function (t) {
+                        n = n && !!document.queryCommandSupported(t)
+                    }), n
+                }
+            }]), o
+        }();
+
+        function s(t, e) {
+            var n = "data-clipboard-" + t;
+            let isFun = e && typeof e.hasAttribute === 'function';
+            if (isFun && e.hasAttribute(n)) {
+                return e.getAttribute(n)
+            }
+        }
+
+        t.exports = l
+    }, function (t, e, n) {
+        "use strict";
+        var o, r = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (t) {
+            return typeof t
+        } : function (t) {
+            return t && "function" == typeof Symbol && t.constructor === Symbol && t !== Symbol.prototype ? "symbol" : typeof t
+        }, i = function () {
+            function o(t, e) {
+                for (var n = 0; n < e.length; n++) {
+                    var o = e[n];
+                    o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(t, o.key, o)
+                }
+            }
+
+            return function (t, e, n) {
+                return e && o(t.prototype, e), n && o(t, n), t
+            }
+        }(), a = n(2), c = (o = a) && o.__esModule ? o : {default: o};
+        var u = function () {
+            function e(t) {
+                !function (t, e) {
+                    if (!(t instanceof e)) throw new TypeError("Cannot call a class as a function")
+                }(this, e), this.resolveOptions(t), this.initSelection()
+            }
+
+            return i(e, [{
+                key: "resolveOptions", value: function () {
+                    var t = 0 < arguments.length && void 0 !== arguments[0] ? arguments[0] : {};
+                    this.action = t.action, this.container = t.container, this.emitter = t.emitter, this.target = t.target, this.text = t.text, this.trigger = t.trigger, this.selectedText = ""
+                }
+            }, {
+                key: "initSelection", value: function () {
+                    this.text ? this.selectFake() : this.target && this.selectTarget()
+                }
+            }, {
+                key: "selectFake", value: function () {
+                    var t = this, e = "rtl" == document.documentElement.getAttribute("dir");
+                    this.removeFake(), this.fakeHandlerCallback = function () {
+                        return t.removeFake()
+                    }, this.fakeHandler = this.container.addEventListener("click", this.fakeHandlerCallback) || !0, this.fakeElem = document.createElement("textarea"), this.fakeElem.style.fontSize = "12pt", this.fakeElem.style.border = "0", this.fakeElem.style.padding = "0", this.fakeElem.style.margin = "0", this.fakeElem.style.position = "absolute", this.fakeElem.style[e ? "right" : "left"] = "-9999px";
+                    var n = window.pageYOffset || document.documentElement.scrollTop;
+                    this.fakeElem.style.top = n + "px", this.fakeElem.setAttribute("readonly", ""), this.fakeElem.value = this.text, this.container.appendChild(this.fakeElem), this.selectedText = (0, c.default)(this.fakeElem), this.copyText()
+                }
+            }, {
+                key: "removeFake", value: function () {
+                    this.fakeHandler && (this.container.removeEventListener("click", this.fakeHandlerCallback), this.fakeHandler = null, this.fakeHandlerCallback = null), this.fakeElem && (this.container.removeChild(this.fakeElem), this.fakeElem = null)
+                }
+            }, {
+                key: "selectTarget", value: function () {
+                    this.selectedText = (0, c.default)(this.target), this.copyText()
+                }
+            }, {
+                key: "copyText", value: function () {
+                    var e = void 0;
+                    try {
+                        e = document.execCommand(this.action)
+                    } catch (t) {
+                        e = !1
+                    }
+                    this.handleResult(e)
+                }
+            }, {
+                key: "handleResult", value: function (t) {
+                    this.emitter.emit(t ? "success" : "error", {
+                        action: this.action,
+                        text: this.selectedText,
+                        trigger: this.trigger,
+                        clearSelection: this.clearSelection.bind(this)
+                    })
+                }
+            }, {
+                key: "clearSelection", value: function () {
+                    this.trigger && this.trigger.focus(), window.getSelection().removeAllRanges()
+                }
+            }, {
+                key: "destroy", value: function () {
+                    this.removeFake()
+                }
+            }, {
+                key: "action", set: function () {
+                    var t = 0 < arguments.length && void 0 !== arguments[0] ? arguments[0] : "copy";
+                    if (this._action = t, "copy" !== this._action && "cut" !== this._action) throw new Error('Invalid "action" value, use either "copy" or "cut"')
+                }, get: function () {
+                    return this._action
+                }
+            }, {
+                key: "target", set: function (t) {
+                    if (void 0 !== t) {
+                        if (!t || "object" !== (void 0 === t ? "undefined" : r(t)) || 1 !== t.nodeType) throw new Error('Invalid "target" value, use a valid Element');
+                        if ("copy" === this.action && t.hasAttribute("disabled")) throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');
+                        if ("cut" === this.action && (t.hasAttribute("readonly") || t.hasAttribute("disabled"))) throw new Error('Invalid "target" attribute. You can\'t cut text from elements with "readonly" or "disabled" attributes');
+                        this._target = t
+                    }
+                }, get: function () {
+                    return this._target
+                }
+            }]), e
+        }();
+        t.exports = u
+    }, function (t, e) {
+        t.exports = function (t) {
+            var e;
+            if ("SELECT" === t.nodeName) t.focus(), e = t.value; else if ("INPUT" === t.nodeName || "TEXTAREA" === t.nodeName) {
+                var n = t.hasAttribute("readonly");
+                n || t.setAttribute("readonly", ""), t.select(), t.setSelectionRange(0, t.value.length), n || t.removeAttribute("readonly"), e = t.value
+            } else {
+                t.hasAttribute("contenteditable") && t.focus();
+                var o = window.getSelection(), r = document.createRange();
+                r.selectNodeContents(t), o.removeAllRanges(), o.addRange(r), e = o.toString()
+            }
+            return e
+        }
+    }, function (t, e) {
+        function n() {
+        }
+
+        n.prototype = {
+            on: function (t, e, n) {
+                var o = this.e || (this.e = {});
+                return (o[t] || (o[t] = [])).push({fn: e, ctx: n}), this
+            }, once: function (t, e, n) {
+                var o = this;
+
+                function r() {
+                    o.off(t, r), e.apply(n, arguments)
+                }
+
+                return r._ = e, this.on(t, r, n)
+            }, emit: function (t) {
+                for (var e = [].slice.call(arguments, 1), n = ((this.e || (this.e = {}))[t] || []).slice(), o = 0, r = n.length; o < r; o++) n[o].fn.apply(n[o].ctx, e);
+                return this
+            }, off: function (t, e) {
+                var n = this.e || (this.e = {}), o = n[t], r = [];
+                if (o && e) for (var i = 0, a = o.length; i < a; i++) o[i].fn !== e && o[i].fn._ !== e && r.push(o[i]);
+                return r.length ? n[t] = r : delete n[t], this
+            }
+        }, t.exports = n
+    }, function (t, e, n) {
+        var d = n(5), h = n(6);
+        t.exports = function (t, e, n) {
+            if (!t && !e && !n) throw new Error("Missing required arguments");
+            if (!d.string(e)) throw new TypeError("Second argument must be a String");
+            if (!d.fn(n)) throw new TypeError("Third argument must be a Function");
+            if (d.node(t)) return s = e, f = n, (l = t).addEventListener(s, f), {
+                destroy: function () {
+                    l.removeEventListener(s, f)
+                }
+            };
+            if (d.nodeList(t)) return a = t, c = e, u = n, Array.prototype.forEach.call(a, function (t) {
+                t.addEventListener(c, u)
+            }), {
+                destroy: function () {
+                    Array.prototype.forEach.call(a, function (t) {
+                        t.removeEventListener(c, u)
+                    })
+                }
+            };
+            if (d.string(t)) return o = t, r = e, i = n, h(document.body, o, r, i);
+            throw new TypeError("First argument must be a String, HTMLElement, HTMLCollection, or NodeList");
+            var o, r, i, a, c, u, l, s, f
+        }
+    }, function (t, n) {
+        n.node = function (t) {
+            return void 0 !== t && t instanceof HTMLElement && 1 === t.nodeType
+        }, n.nodeList = function (t) {
+            var e = Object.prototype.toString.call(t);
+            return void 0 !== t && ("[object NodeList]" === e || "[object HTMLCollection]" === e) && "length" in t && (0 === t.length || n.node(t[0]))
+        }, n.string = function (t) {
+            return "string" == typeof t || t instanceof String
+        }, n.fn = function (t) {
+            return "[object Function]" === Object.prototype.toString.call(t)
+        }
+    }, function (t, e, n) {
+        var a = n(7);
+
+        function i(t, e, n, o, r) {
+            var i = function (e, n, t, o) {
+                return function (t) {
+                    t.delegateTarget = a(t.target, n), t.delegateTarget && o.call(e, t)
+                }
+            }.apply(this, arguments);
+            return t.addEventListener(n, i, r), {
+                destroy: function () {
+                    t.removeEventListener(n, i, r)
+                }
+            }
+        }
+
+        t.exports = function (t, e, n, o, r) {
+            return "function" == typeof t.addEventListener ? i.apply(null, arguments) : "function" == typeof n ? i.bind(null, document).apply(null, arguments) : ("string" == typeof t && (t = document.querySelectorAll(t)), Array.prototype.map.call(t, function (t) {
+                return i(t, e, n, o, r)
+            }))
+        }
+    }, function (t, e) {
+        if ("undefined" != typeof Element && !Element.prototype.matches) {
+            var n = Element.prototype;
+            n.matches = n.matchesSelector || n.mozMatchesSelector || n.msMatchesSelector || n.oMatchesSelector || n.webkitMatchesSelector
+        }
+        t.exports = function (t, e) {
+            for (; t && 9 !== t.nodeType;) {
+                if ("function" == typeof t.matches && t.matches(e)) return t;
+                t = t.parentNode
+            }
+        }
+    }])
+});
+let Types = {
+    isFunction: function (obj) {
+        var type = Object.prototype.toString.call(obj)
+        return type == '[object Function]'
+    }, isObject: function (obj) {
+        var type = Object.prototype.toString.call(obj)
+        return type == '[object Object]'
+    }, isString: function (obj) {
+        var type = Object.prototype.toString.call(obj)
+        return type == '[object String]'
+    }
+}
+uni.setClipboardData = function (options) {
+    let emptyFun = function () {
+    }
+    let config = {data: null, event: null, success: emptyFun, fail: emptyFun, complete: emptyFun}
+    if (options && Types.isObject(options)) {
+        config = Object.assign({}, config, options)
+    }
+    if (options && Types.isString(options)) {
+        config = Object.assign({}, config, {data: options})
+    }
+    let data = config.data
+    let success = config.success || emptyFun
+    let fail = config.fail || emptyFun
+    let complete = config.complete || emptyFun
+    let e = config.event || window.event || {}
+    let cb = new ClipboardJS('.null', {text: () => data})
+    cb.on('success', function (res) {
+        window.__clipboard__ = data;
+        success && Types.isFunction(success) && success({data: res.text})
+        complete && Types.isFunction(complete) && complete()
+        cb.off('error')
+        cb.off('success')
+        cb.destroy()
+    })
+    cb.on('error', function (err) {
+        fail && Types.isFunction(fail) && fail(err)
+        complete && Types.isFunction(complete) && complete()
+        cb.off('error')
+        cb.off('success')
+        cb.destroy()
+    })
+    cb.onClick(e)
+};
+uni.getClipboardData = function (options) {
+    let emptyFun = function () {
+    }
+    let config = {data: null, event: null, success: emptyFun, fail: emptyFun, complete: emptyFun}
+    if (options && Types.isObject(options)) {
+        config = Object.assign({}, config, options)
+    }
+    let success = config.success || emptyFun
+    let fail = config.fail || emptyFun
+    let complete = config.complete || emptyFun
+    if (window.__clipboard__ !== undefined) {
+        success && Types.isFunction(success) && success({data: window.__clipboard__})
+    } else {
+        fail && Types.isFunction(fail) && fail({data: null})
+    }
+    complete && Types.isFunction(complete) && complete()
+};
+
+function fileDownLoad(data) {
+    var linkElement = document.createElement('a')
+    linkElement.setAttribute('href', data.blob)
+    linkElement.setAttribute('downLoad', data.name)
+    linkElement.click()
+}
+
+uni.saveImageToPhotosAlbum = uni.saveVideoToPhotosAlbum = function (options) {
+    let emptyFun = function () {
+    }
+    let config = {filePath: null, success: emptyFun, fail: emptyFun, complete: emptyFun}
+    if (options && Types.isObject(options)) {
+        config = Object.assign({}, config, options)
+    }
+    if (options && Types.isString(options)) {
+        config = Object.assign({}, config, {filePath: options})
+    }
+    let filePath = config.filePath
+    let success = config.success || emptyFun
+    let fail = config.fail || emptyFun
+    let complete = config.complete || emptyFun
+    if (!filePath) {
+        fail && Types.isFunction(fail) && fail({msg: 'no File'})
+        complete && Types.isFunction(complete) && complete()
+        return
+    }
+    let names = filePath.split('/')
+    let name = names[names.length - 1]
+    uni.downloadFile({
+        url: filePath, success: function (res) {
+            let tempFilePath = res.tempFilePath
+            fileDownLoad({name: name, blob: tempFilePath})
+            success && Types.isFunction(success) && success({filePath: filePath})
+        }, fail: function (err) {
+            fail && Types.isFunction(fail) && fail({msg: err})
+        }, complete: function () {
+            complete && Types.isFunction(complete) && complete()
+        }
+    })
+}
+//#endif

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

+ 30 - 0
main.js

@@ -0,0 +1,30 @@
+import Vue from 'vue'
+import App from './App'
+
+import HttpRequest from './common/httpRequest'
+import HttpCache from './common/cache'
+import queue from './common/queue'
+import homeList from './components/home-list/homeList.vue'
+Vue.component('homeList', homeList);
+import homeuserList from './components/home-list/homeuserList.vue'
+Vue.component('homeuserList', homeuserList);
+Vue.config.productionTip = false
+Vue.prototype.$Request = HttpRequest;
+Vue.prototype.$queue = queue;
+
+Vue.prototype.$Sysconf = HttpRequest.config;
+Vue.prototype.$SysCache = HttpCache;
+
+
+
+App.mpType = 'app'
+
+// 引入全局uView
+import uView from "uview-ui";
+Vue.use(uView);
+
+const app = new Vue({
+    ...App
+})
+
+app.$mount()

+ 209 - 0
manifest.json

@@ -0,0 +1,209 @@
+{
+    "name" : "码兄聘聘",
+    "appid" : "__UNI__B73A0EA",
+    "description" : "",
+    "versionName" : "1.0.0",
+    "versionCode" : "100",
+    "transformPx" : false,
+    /* 5+App特有相关 */
+    "app-plus" : {
+        "usingComponents" : true,
+        "nvueStyleCompiler" : "uni-app",
+        "compilerVersion" : 3,
+        "splashscreen" : {
+            "alwaysShowBeforeRender" : true,
+            "waiting" : true,
+            "autoclose" : true,
+            "delay" : 0
+        },
+        /* 模块配置 */
+        "modules" : {
+            "Payment" : {},
+            "Share" : {},
+            "OAuth" : {},
+            "Maps" : {},
+            "Push" : {}
+        },
+        /* 应用发布信息 */
+        "distribute" : {
+            /* android打包配置 */
+            "android" : {
+                "permissions" : [
+                    "<uses-feature android:name=\"android.hardware.camera\"/>",
+                    "<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
+                    "<uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\"/>",
+                    "<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.CAMERA\"/>",
+                    "<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
+                    "<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
+                    "<uses-permission android:name=\"android.permission.INTERNET\"/>",
+                    "<uses-permission android:name=\"android.permission.MODIFY_AUDIO_SETTINGS\"/>",
+                    "<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
+                    "<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
+                    "<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.RECORD_AUDIO\"/>",
+                    "<uses-permission android:name=\"android.permission.VIBRATE\"/>",
+                    "<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
+                    "<uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\"/>",
+                    "<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>",
+                    "<uses-permission android:name=\"android.permission.CALL_PHONE\"/>"
+                ],
+                "autoSdkPermissions" : true
+            },
+            /* ios打包配置 */
+            "ios" : {
+                "dSYMs" : false
+            },
+            /* SDK配置 */
+            "sdkConfigs" : {
+                "payment" : {
+                    "weixin" : {
+                        "__platform__" : [ "android" ],
+                        "appid" : "wxeaffcaea958472d3",
+                        "UniversalLinks" : "https://maxiong.xianmxkj.com/"
+                    },
+                    "alipay" : {
+                        "__platform__" : [ "android" ]
+                    }
+                },
+                "share" : {
+                    "weixin" : {
+                        "appid" : "wxeaffcaea958472d3",
+                        "UniversalLinks" : "https://maxiong.xianmxkj.com/"
+                    }
+                },
+                "oauth" : {
+                    "weixin" : {
+                        "appid" : "wxeaffcaea958472d3",
+                        "appsecret" : "32f6f3ce599abff2fa0191a36780c66c",
+                        "UniversalLinks" : "https://maxiong.xianmxkj.com/"
+                    }
+                },
+                "ad" : {},
+                "maps" : {
+                    "amap" : {
+                        "appkey_ios" : "d359b4a4f42b069cf18a46e1bf6f5db1",
+                        "appkey_android" : "b87c5f80d78510f3ed8071f369d78eb1"
+                    }
+                },
+                "push" : {
+                    "unipush" : {}
+                }
+            },
+            "icons" : {
+                "android" : {
+                    "hdpi" : "unpackage/res/icons/72x72.png",
+                    "xhdpi" : "unpackage/res/icons/96x96.png",
+                    "xxhdpi" : "unpackage/res/icons/144x144.png",
+                    "xxxhdpi" : "unpackage/res/icons/192x192.png"
+                },
+                "ios" : {
+                    "appstore" : "unpackage/res/icons/1024x1024.png",
+                    "ipad" : {
+                        "app" : "unpackage/res/icons/76x76.png",
+                        "app@2x" : "unpackage/res/icons/152x152.png",
+                        "notification" : "unpackage/res/icons/20x20.png",
+                        "notification@2x" : "unpackage/res/icons/40x40.png",
+                        "proapp@2x" : "unpackage/res/icons/167x167.png",
+                        "settings" : "unpackage/res/icons/29x29.png",
+                        "settings@2x" : "unpackage/res/icons/58x58.png",
+                        "spotlight" : "unpackage/res/icons/40x40.png",
+                        "spotlight@2x" : "unpackage/res/icons/80x80.png"
+                    },
+                    "iphone" : {
+                        "app@2x" : "unpackage/res/icons/120x120.png",
+                        "app@3x" : "unpackage/res/icons/180x180.png",
+                        "notification@2x" : "unpackage/res/icons/40x40.png",
+                        "notification@3x" : "unpackage/res/icons/60x60.png",
+                        "settings@2x" : "unpackage/res/icons/58x58.png",
+                        "settings@3x" : "unpackage/res/icons/87x87.png",
+                        "spotlight@2x" : "unpackage/res/icons/80x80.png",
+                        "spotlight@3x" : "unpackage/res/icons/120x120.png"
+                    }
+                }
+            }
+        },
+        "nvueCompiler" : "uni-app",
+        "nativePlugins" : {
+            "TRTCCloudUniPlugin-TRTCCloudImpl" : {
+                "__plugin_info__" : {
+                    "name" : "【官方】腾讯云实时音视频SDK",
+                    "description" : "uni-app TRTC SDK 是腾讯云实时音视频通讯解决方案在 uni-app 上的 SDK,提供实时音视频服务",
+                    "platforms" : "Android,iOS",
+                    "url" : "https://ext.dcloud.net.cn/plugin?id=7774",
+                    "android_package_name" : "com.mxkj.xamx",
+                    "ios_bundle_id" : "",
+                    "isCloud" : true,
+                    "bought" : 1,
+                    "pid" : "7774",
+                    "parameters" : {}
+                }
+            }
+        }
+    },
+    /* 快应用特有相关 */
+    "quickapp" : {},
+    /* 小程序特有相关 */
+    "mp-weixin" : {
+        "appid" : "wx8211debf9a8a8457",
+        "setting" : {
+            "urlCheck" : false,
+            "minified" : true,
+            "postcss" : true,
+            "es6" : true
+        },
+        "usingComponents" : true,
+        "optimization" : {
+            "subPackages" : true
+        },
+        "permission" : {
+            "scope.userLocation" : {
+                "desc" : "你的位置信息将用于小程序定位"
+            }
+        },
+        "requiredPrivateInfos" : [ "getLocation", "chooseLocation" ]
+    },
+    "mp-alipay" : {
+        "usingComponents" : true
+    },
+    "mp-baidu" : {
+        "usingComponents" : true
+    },
+    "mp-toutiao" : {
+        "usingComponents" : true
+    },
+    "uniStatistics" : {
+        "enable" : false
+    },
+    "h5" : {
+        "router" : {
+            "mode" : "history"
+        },
+        "devServer" : {
+            "https" : false,
+            "port" : 8080,
+            "disableHostCheck" : true,
+            "proxy" : {
+                // 可代理多个
+                "/TencentGet" : {
+                    "target" : "https://apis.map.qq.com/ws/geocoder/v1/", // 腾讯地图逆地址解析
+                    "changeOrigin" : true,
+                    "secure" : false,
+                    "pathRewrite" : {
+                        "^/TencentGet" : ""
+                    }
+                }
+            }
+        },
+        "sdkConfigs" : {
+            "maps" : {
+                "qqmap" : {
+                    "key" : "WYJBZ-BOVK2-QZSU2-CJHY2-7BFHE-JEFNP"
+                }
+            }
+        }
+    }
+}

BIN
my/.DS_Store


+ 307 - 0
my/address/Endaddress.vue

@@ -0,0 +1,307 @@
+<template>
+	<view>
+		<!-- #ifdef MP-WEIXIN -->
+		<view class="part1">
+			<view class="mobile">
+				<u-field v-model="mobile" placeholder="联系电话" icon="phone-fill" maxlength="11" type="number" label-align="center">
+				</u-field>
+			</view>
+			<view class="name">
+				<u-field v-model="name" placeholder="姓名" icon="account-fill" label-align="center">
+				</u-field>
+			</view>
+
+			<view class="address" @click="bindmap">
+				<u-field v-model="cityaddress" placeholder="地址" :disabled="true" icon="map-fill" label-align="center">
+				</u-field>
+			</view>
+			<view class="detailaddress">
+				<u-field v-model="detailaddress" placeholder="详细地址" icon="map-fill" label-align="center">
+				</u-field>
+			</view>
+			<view
+				style="height: 100upx;background:#FFFFFF;display: flex;margin-top: 30upx;justify-content: space-between;padding: 0rpx 22rpx;">
+				<view style="font-size: 30upx;color: #333333;text-align: left;padding: 30upx;width: 70%;">设为默认地址</view>
+				<switch type="switch" :checked='isDefault === 1 ? true : false' @change="switch1Change"
+					style="padding: 20upx;transform:scale(0.9)" color="#557EFD" />
+			</view>
+			<view class="btn" @click="bindhelp">确定</view>
+		</view>
+		<!-- <view style="margin-top: 20upx;background-color: #FFFFFF;height: max-content;padding: 20upx 20upx 20upx 35upx;">
+			<input type="text" placeholder="收货人" style="height: 80upx;color: #000000;font-size: 30upx;" v-model="consignee" />
+			<input type="number" placeholder="手机号码" maxlength="11" style="height: 80upx;margin-top: 20upx;color: #000000;font-size: 30upx;"
+			 v-model="mobile" />
+			<view style="height: 1upx;margin-top: 50upx;"></view>
+			<pickerAddress style="padding-bottom: 20upx;color: #000000;font-size: 30upx;height: 30upx;" @change="change">{{provinces}}</pickerAddress>
+			<input type="text" v-model="detail" placeholder="详情地址:如道路,门牌号,小区,楼栋号,单元室等" style="height: 80upx;margin-top: 30upx;color: #000000;font-size: 30upx;" />
+		</view> -->
+
+		<!-- #endif -->
+		<!-- #ifndef MP-WEIXIN -->
+		<view class="part1">
+			<view class="mobile">
+				<u-field v-model="mobile" placeholder="联系电话" icon="phone-fill" maxlength="11" label-align="center">
+				</u-field>
+			</view>
+			<view class="name">
+				<u-field v-model="name" placeholder="姓名" icon="account-fill" label-align="center">
+				</u-field>
+			</view>
+
+			<view class="address" @click="bindmap">
+				<u-field v-model="cityaddress" placeholder="地址" :disabled="true" icon="map-fill" label-align="center">
+				</u-field>
+			</view>
+			<view class="detailaddress">
+				<u-field v-model="detailaddress" placeholder="详细地址" icon="map-fill" label-align="center">
+				</u-field>
+			</view>
+			<view
+				style="height: 100upx;background:#FFFFFF;display: flex;margin-top: 30upx;justify-content: space-between;padding: 0rpx 22rpx;">
+				<view style="font-size: 34upx;color: #333333;text-align: left;padding: 30upx;width: 70%;">设为默认地址</view>
+				<switch type="switch" :checked='isDefault === 1 ? true : false' @change="switch1Change"
+					style="padding: 20upx;transform:scale(0.9)" color="#557EFD" />
+			</view>
+			<view class="btn" @click="bindhelp">确定</view>
+		</view>
+		<!-- #endif -->
+		<!-- <button
+			style="width: 85%;height: 90upx;background: #FF332F;font-size: 32upx;color: #FFFFFF;margin-top: 40upx;border-radius: 50upx;line-height: 100upx;"
+			@tap='setEditAddress'>保存</button> -->
+	</view>
+</template>
+
+<script>
+	// import pickerAddress from '@/components/wangding-pickerAddress/wangding-pickerAddress.vue'
+	import pickerAddress from '@/my/components/wangding-pickerAddress/wangding-pickerAddress.vue'
+	export default {
+		components: {
+			pickerAddress
+		},
+		data() {
+			return {
+				// provinces: '选择地址',
+				// consignee: '',
+				// mobile: '',
+				// detail: '',
+				// createAt: '',
+				id: '',
+				isDefault: 0,
+				// id: 0,
+				mobile: '',
+				name: '',
+				cityaddress: '',
+				detailaddress: '',
+				latitude: '',
+				longitude: '',
+				province:'',
+				city:'',
+				district:''
+			}
+		},
+		onLoad(d) {
+			let id = d.id;
+			if (id) {
+				this.id = d.id;
+				uni.setNavigationBarTitle({
+					title: '编辑地址'
+				});
+				this.getAddressList(id);
+			} else {
+				uni.setNavigationBarTitle({
+					title: '添加地址'
+				});
+			}
+		},
+		methods: {
+			change(data) {
+				this.provinces = data.data.join('')
+			},
+			bindmap() {
+				var that = this
+				// if (that.ciaddress == '') {
+				uni.chooseLocation({
+					success: function(res) {
+						// console.log(res)
+						console.log('位置名称:' + res.name);
+						console.log('详细地址:' + res.address);
+						console.log('纬度:' + res.latitude);
+						console.log('经度:' + res.longitude);
+						that.detailaddress = res.name
+						that.latitude = res.latitude
+						that.longitude = res.longitude
+						that.shengcheng(res.longitude, res.latitude)
+					}
+				});
+				// }
+			},
+			shengcheng(longitude, latitude) {
+				this.$Request.getT('/app/Login/selectCity?lat=' + latitude + '&lng=' + longitude).then(res => {
+					// console.log(res)
+					if (res.code === 0) {
+						this.cityaddress = res.data.province + res.data.city + res.data.district
+						// console.log(this.address)
+						this.province = res.data.province
+						this.city = res.data.city
+						this.district = res.data.district
+					}
+				});
+
+			},
+			getAddressList(id) {
+				uni.showLoading({
+					title: '加载中...'
+				});
+				this.$Request.getT('/app/address/selectAddressByAddressId?addressId=' + id).then(res => {
+					console.log(res)
+					if (res.code == 0) {
+						this.name = res.data.name;
+						this.mobile = res.data.phone;
+						this.cityaddress =res.data.province+ res.data.city+res.data.district;
+						this.detailaddress = res.data.detailsAddress;
+						this.isDefault = res.data.isDefault;
+						this.userId = res.data.userId;
+						this.latitude = res.data.latitude;
+						this.longitude = res.data.longitude;
+						this.province = res.data.province
+						this.city = res.data.city
+						this.district = res.data.district
+					}
+					uni.hideLoading();
+				});
+			},
+			bindhelp() {
+				if (this.id) {
+					this.$queue.showLoading('修改中...')
+				} else {
+					this.$queue.showLoading('添加中...')
+				}
+				let userId = this.$queue.getData('userId');
+				let data = {
+					name: this.name,
+					phone: this.mobile,
+					province:this.province,
+					city:this.city,
+					district:this.district,
+					detailsAddress:this.detailaddress,
+					isDefault:this.isDefault,
+					addressId: this.id,
+					// address: this.cityaddress,
+					// addressDetail: this.detailaddress,
+					latitude: this.latitude,
+					longitude: this.longitude,
+					// userId: userId,
+					
+					// addressDefault: this.isDefault
+				}
+				if (this.mobile.length < 11) {
+					uni.showToast({
+						icon: 'none',
+						title: '请输入正确的电话信息'
+					});
+					return;
+				}
+				if (this.name != undefined && this.name != '') {} else {
+					uni.showToast({
+						icon: 'none',
+						title: '收货人不能为空'
+					});
+					return;
+				}
+				if (this.cityaddress == '选择地址') {
+					uni.showToast({
+						icon: 'none',
+						title: '请选择地址信息'
+					});
+					return;
+				}
+				if (this.detailaddress != undefined && this.detailaddress != '') {} else {
+					uni.showToast({
+						icon: 'none',
+						title: '地址信息不能为空'
+					});
+					return;
+				}
+
+				if (this.id) {
+					this.$Request.postJson('/app/address/updateAddress', data).then(res => {
+						if (res.code == 0) {
+							uni.hideLoading()
+							this.$queue.showToast("修改成功!");
+							setTimeout(d => {
+								uni.navigateBack();
+							}, 1000)
+						} else {
+							uni.hideLoading()
+							this.$queue.showToast(res.msg);
+						}
+					});
+				} else {
+					this.$Request.postJson('/app/address/insertAddress', data).then(res => {
+						if (res.code == 0) {
+							uni.hideLoading()
+							this.$queue.showToast("添加成功!");
+							setTimeout(d => {
+								uni.navigateBack();
+							}, 1000)
+						} else {
+							uni.hideLoading()
+							this.$queue.showToast(res.msg);
+						}
+					});
+				}
+			},
+			//校验手机格式
+			checkMobile(mobile) {
+				return RegExp(/^1[34578]\d{9}$/).test(mobile);
+			},
+			switch1Change(e) {
+				this.isDefault = e.detail.value == true ? 1 : 0;
+			}
+		}
+	}
+</script>
+
+<style>
+	body {
+		background: #FFFFFF;
+	}
+
+	.part1 {
+		width: 100%;
+		background: #ffffff;
+		margin-top: 24rpx;
+		padding-bottom: 40upx;
+	}
+
+	.btn {
+		width: 90%;
+		height: 80upx;
+		background: #557EFD;
+		border-radius: 14upx;
+		margin: 0 auto;
+		color: white;
+		text-align: center;
+		line-height: 80upx;
+		letter-spacing: 2upx;
+		margin-top: 40upx;
+	}
+
+	.u-icon__icon {
+		font-size: 45rpx !important;
+	}
+	
+	.u-field {
+		padding: 35rpx 28rpx !important;
+	}
+	
+	.u-label {
+		flex: 0 0 42px !important;
+	}
+	.u-field__input-wrap {
+	    font-size: 30rpx !important;
+	}
+	.u-textarea-class {
+	    font-size: 30rpx !important;
+	}
+</style>

+ 260 - 0
my/address/address.vue

@@ -0,0 +1,260 @@
+<template>
+	<view style="padding-bottom: 150upx;">
+		<!-- #ifdef MP-WEIXIN -->
+		<view style="height: max-content;background-color: #FFFFFF;margin-top: 10upx;padding: 30upx 30upx 10upx 40upx;"
+			v-for="(item,index) in list" :key='index' >
+			<view @tap='goBackByAddress(item.addressId)'>
+				<view style="display: flex;" >
+					<view style="font-size: 32rpx;color: #333333;">{{item.name}}</view>
+					<view style="font-size: 32rpx;color: #333333;margin-left: 20upx;text-align: right;">{{item.phone}}
+					</view>
+				</view>
+				<view style="display: flex;margin-top: 35rpx;">
+					<view style="font-size: 32rpx;color: #333333;width: 90%;">
+						{{item.province}}{{item.city}}{{item.district}} {{item.detailsAddress}}
+					</view>
+				</view>
+			</view>
+			<view style="margin-top: 15upx;height: 1upx;background-color: #E3E4E5;margin-bottom: 10upx;"></view>
+			<view style="display: flex;padding: 10rpx 2rpx 10rpx 0rpx;font-size: 32rpx;">
+				<radio-group name="openWay" style="text-align: left;width: 70%;">
+					<label class="tui-radio" v-if="item.isDefault == '1'">
+						<radio :checked="item.isDefault == 1 ? true : false" active-color="#557EFD" disabled='true'
+							style="transform:scale(0.8);" />
+						<text style="font-size: 32rpx;margin-left: 10upx;">默认地址</text>
+					</label>
+				</radio-group>
+				<view style="display: flex;margin-left: 80upx;margin-top: 5upx;width: 40%;text-align: right;" v-if="isfa !=1">
+					<view style="font-size: 32rpx;color: #999999;width: 50%;" @tap='deleteAddressList(item.addressId)'>
+						删除</view>
+					<view style="font-size: 32rpx;color: #999999;width: 50%;" @tap='goAddress(item.addressId)'>编辑</view>
+				</view>
+			</view>
+		</view>
+		<view style="position: fixed;bottom: 0rpx;left: 0;right: 0;background: #FFFFFF;height:150rpx;">
+			<button style="background: #557EFD;color: #FFFFFF;margin: 35rpx;position: fixed;bottom: 0upx;width: 90%;"
+				@tap="goAddress('')">
+				+新建地址
+			</button>
+		</view>
+		<!-- #endif -->
+		<!-- #ifndef MP-WEIXIN -->
+		<view style="height: max-content;background-color: #FFFFFF;margin-top: 10upx;padding: 30upx 30upx 10upx 40upx;"
+			v-for="(item,index) in list" :key='index' >
+			<view @tap='goBackByAddress(item.addressId)'>
+				<view style="display: flex;" >
+					<view style="font-size: 30upx;color: #333333;">{{item.name}}</view>
+					<view style="font-size: 30upx;color: #333333;margin-left: 20upx;text-align: right;">{{item.phone}}
+					</view>
+				</view>
+				<view style="display: flex;margin-top: 30upx;height: 70upx;">
+					<view style="font-size: 30upx;color: #333333;width: 90%;">
+						{{item.province}}{{item.city}}{{item.district}} {{item.detailsAddress}}
+					</view>
+				</view>
+			</view>
+			<view style="margin-top: 30rpx;height: 1upx;background-color: #E3E4E5;margin-bottom: 10upx;"></view>
+			<view style="display: flex;padding: 15upx 5upx 15upx 0upx;font-size: 30upx;">
+				<radio-group name="openWay" style="text-align: left;width: 70%;">
+					<label class="tui-radio" v-if="item.isDefault == '1'">
+						<radio :checked="item.isDefault == 1 ? true : false" color="#557EFD" disabled='true'
+							style="transform:scale(0.8);" />
+						<text style="font-size: 30upx;margin-left: 10upx;">默认地址</text>
+					</label>
+				</radio-group>
+				<view style="display: flex;margin-left: 80upx;margin-top: 5upx;width: 40%;text-align: right;" v-if="isfa !=1">
+					<view style="font-size: 30upx;color: #999999;width: 50%;" @tap='deleteAddressList(item.addressId)'>
+						删除</view>
+					<view style="font-size: 30upx;color: #999999;width: 50%;" @tap='goAddress(item.addressId)'>编辑</view>
+				</view>
+			</view>
+		</view>
+		<view style="position: fixed;bottom: 0rpx;left: 0;right: 0;background: #FFFFFF;height:150rpx;">
+			<button style="background: #557EFD;color: #FFFFFF;margin: 30upx;position: fixed;bottom: 0upx;width: 90%;"
+				@tap="goAddress('')">
+				+新建地址
+			</button>
+		</view>
+		<!-- #endif -->
+		<!-- 悬浮上拉 -->
+		<view class="scroll_top" @tap="topScrollTap" v-bind:class="[scrollTop ? 'active' : '', '']"
+			style="bottom: 56px;"><text class="iconfont icon-shangla"></text></view>
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				openWay: 0,
+				list: [],
+				// loadingType: 0,
+				type: 1,
+				scrollTop: false,
+				page: 1,
+				limit: 5,
+				address: '',
+				isfa: ''
+			}
+		},
+		onShow() {
+			let userId = this.$queue.getData('userId');
+			if (userId) {
+				this.getAddressList('');
+			}
+		},
+		onLoad(e) {
+			this.isfa = e.id
+			
+			console.log(this.isfa)
+			let userId = this.$queue.getData('userId');
+			if (userId) {
+				this.getAddressList('');
+			}
+		},
+		methods: {
+			goBackByAddress(addressId) {
+				console.log(addressId)
+				this.$queue.setData('EditAddress', addressId);
+				console.log(this.isfa)
+				if (this.isfa==0) {
+					console.log('1111111')
+				}if(this.isfa ==1){
+					uni.navigateBack()				
+				}else if(this.isfa== 3){
+					this.updateaddress()
+					// uni.navigateBack()
+				}
+					
+			},
+			updateaddress() {
+				let addressId = this.$queue.getData('EditAddress')
+				let data = {
+					ordersId: this.isfa.order,
+					addressId: addressId
+				}
+				this.$Request.post('/app/orders/updateOrdersAddress', data).then(res => {
+					console.log(res)
+					if (res.code == 0) {
+						uni.showToast({
+							title: '修改成功',
+							icon: 'none'
+						})
+						uni.navigateBack();
+						
+						
+					}
+				})
+
+			},
+			deleteAddressList(id) {
+				console.log(id)
+				var id = id
+				let data = {
+					addressId: id,
+				}
+				uni.showModal({
+					title: '温馨提示',
+					content: '您确定要删除此地址信息吗?',
+					showCancel: true,
+					cancelText: '取消',
+					confirmText: '确认',
+					success: res => {
+						if (res.confirm) {
+							this.$Request.postT('/app/address/deleteAddress', data).then(res => {
+								console.log(res)
+								if (res.code == 0) {
+									this.$queue.showToast("删除成功!");
+									this.getAddressList();
+								} else {
+									this.$queue.showToast(res.msg);
+								}
+							});
+						}
+					}
+				});
+			},
+			getAddressList(type) {
+				// this.loadingType = 1;
+				// uni.showLoading({
+				// 	title: '加载中...'
+				// });
+				let userId = this.$queue.getData('userId');
+				let data = {
+					userId: userId,
+					page: this.page,
+					limit: this.limit
+				}
+				this.$Request.getT('/app/address/selectAddressListById', data).then(res => {
+					console.log(res)
+					if (res.code == 0) {
+						if (this.page == 1) this.list = []
+						this.list = [...this.list, ...res.data.list]; //追加新数据
+
+						// res.data.list.forEach(d => {
+						// 	this.list.push(d);
+						// });
+						// console.log(this.list)
+						// this.loadingType = 0;
+					} else {
+						// this.loadingType = 2;
+					}
+					// uni.hideLoading();
+					// if (type === 'Refresh') {
+					// 	uni.stopPullDownRefresh(); // 停止刷新
+					// }
+				});
+			},
+			goAddress(id) {
+				uni.navigateTo({
+					// url: './EditAddress?id=' + id
+					url: '/my/address/Endaddress?id=' + id
+				});
+			},
+			tabSlect(item) {
+				this.tabIndex = item.id;
+			},
+			selectWay(id) {
+				this.openWay = id;
+			},
+			topScrollTap: function() {
+				uni.pageScrollTo({
+					scrollTop: 0,
+					duration: 300
+				});
+			}
+		}
+	}
+</script>
+
+<style lang="less">
+	// @import '../../../static/less/index.less';
+	// @import '../../../static/css/index.css';
+
+	page {
+		background: #F2F2F2;
+	}
+
+	.tui-line-cell {
+		width: 100%;
+		box-sizing: border-box;
+		display: flex;
+		align-items: center;
+		justify-content: space-between;
+		border-bottom: 2upx solid #f2f2f2;
+		padding: 0 0 16upx 0;
+	}
+
+	.tui-title {
+		line-height: 32rpx;
+		min-width: 120rpx;
+		flex-shrink: 0;
+	}
+
+	.tui-input {
+		font-size: 32rpx;
+		color: #333;
+		padding-left: 20rpx;
+		flex: 1;
+	}
+</style>

+ 56 - 0
my/components/jc-record/jc-record.md

@@ -0,0 +1,56 @@
+### 由于作者久未更新, 本插件修正jsfun-record无法使用等问题, 并优化了部分逻辑
+### [原插件地址](https://ext.dcloud.net.cn/plugin?id=436#)
+
+### 使用方式:
+
+```html
+<jcfun-record
+  voicePath=""
+  :maxTime="15"
+  :minTime="5"
+  @okClick="saveRecord"
+	@show="show"
+	@close="close"
+>
+	<view class="centerwz">录制语音</view>
+</jcfun-record>	
+```
+
+```javascript
+methods:{
+	saveRecord(recordPath) {		
+		console.log("===音频文件地址:"+recordPath+"===")
+		//do... 可以使用 uni.uploadFile 接口上传到服务器
+	},
+	show(){
+		console.log('show')
+	},
+	close(){
+		console.log('close')
+	}
+}
+```
+
+在 ``template`` 中使用组件
+
+
+
+### 属性说明:
+
+|属性名		|类型	|默认值					|说明				|
+|---		|----	|---					|---																											|
+|voicePath		|String	|""						|默认文件地址,可以预览																									|
+|maxTime		|Number	|15|最大时间:单位秒				|
+|defaultArr		|String	|5|最小时间:单位秒|
+
+
+### 事件说明:
+
+|事件称名|说明|
+|---|----|---|
+|okClick|点击确认后可以获得音频的地址	|
+|show|显示事件 |
+|close|关闭事件 |
+
+
+

+ 390 - 0
my/components/jc-record/jc-record.vue

@@ -0,0 +1,390 @@
+<template>
+	<view class="jsfun-record" @tap="showPicker">
+		<slot></slot>
+		<!-- 遮罩层 -->
+		<view class="mask" @tap.stop="closePicker" v-if="isShow" @touchmove.stop.prevent="moveHandle"></view>
+		<!-- 多选控件 -->
+		<view class="conbox record" :class="{'pickerShow':isShow}">
+			<!-- 此处可放置倒计时,可根据需要自行添加 -->
+			<view class="time">
+				{{showRecordTime}}
+			</view>
+			<view class="c999">
+				最短{{minTime}}秒,最长{{maxTime}}秒
+			</view>
+			<view class="record-box" @touchstart="start" @longpress="record" @touchend="end"
+				@touchmove.stop.prevent="moveHandle">
+				<span class="stop" @touchstart.stop="stopVoice" v-if="voicePath && playing==1"></span>
+				<span class="paly" @touchstart.stop="playVoice" v-if="voicePath && playing==0"></span>
+				<canvas class="canvas" canvas-id="canvas">
+					<span class="recording"></span>
+				</canvas>
+				<span class="confirm" @touchstart.stop="okClick" v-if="voicePath"></span>
+			</view>
+			<view class="c666 fz32 domess">长按录音</view>
+		</view>
+	</view>
+</template>
+<script>
+	const recorderManager = uni.getRecorderManager(); //录音
+	var innerAudioContext; //播放
+
+	export default {
+		name: 'jsfun-record',
+		props: {
+			maxTime: { // 录音最大时长,单位秒
+				type: Number,
+				default: 15
+			},
+			minTime: { // 录音最大时长,单位毫秒
+				type: Number,
+				default: 5
+			},
+		},
+		data() {
+			return {
+				isShow: false,
+				frame: 50, // 执行绘画的频率,单位毫秒
+				recordTime: 0, //录音时长
+				recordTime1: 0, //播放录音倒计时
+				isshowyuan: false, //是否显示圆圈
+				playing: 0, //是否播放中
+				timeObj: null, //计时id
+				drawObj: null, //画布动画id
+				countdownObj: null, //倒计时id
+				voicePath: '',
+				ctx: ''
+			}
+		},
+		computed: {
+			showRecordTime() {
+				var strs = "";
+				var m = Math.floor(this.recordTime / 60);
+				if (m < 10) strs = "0" + m;
+
+				var s = this.recordTime % 60;
+				strs += (s < 10) ? ":0" + s : ":" + s;
+
+				return strs
+			},
+			
+		},
+		watch: {},
+		onLoad() {
+			var _this = this;
+
+			//获取录音权限
+			uni.authorize({
+				scope: 'scope.record',
+				success() {}
+			})
+
+			//初始化
+			_this.initValue();
+
+		},
+		mounted() {
+			innerAudioContext = uni.createInnerAudioContext(); //播放
+			let _this = this;
+
+			//录音停止事件
+			recorderManager.onStop(function(res) {
+				console.log('recorder stop' + res.tempFilePath);
+				_this.voicePath = res.tempFilePath;
+			});
+
+			//根据canvas 动态中心点				
+			let query = uni.createSelectorQuery().in(this);
+			query.select(".canvas").boundingClientRect()
+			query.exec(function(res) {
+				_this.tempw = res[0].width; //使用canvas的宽度计算中心点的位置
+				_this.temph = res[0].height;
+			})
+			//根据中心图片的大小计算圆环的大小
+			query.select(".recording").boundingClientRect()
+			query.exec(function(res) {
+				_this.tempw1 = res[0].width; //使用点按图形的宽度计算圆环的宽高
+			})
+		},
+		beforeDestroy() {
+			innerAudioContext.destroy();
+		},
+		methods: {
+			moveHandle() {
+				return false;
+			},
+			//组件数据初始化  进入时、关闭时调用初始化
+			initValue() {
+				this.recordTime = 0
+			},
+			//显示组件
+			showPicker() {
+				setTimeout(() => {
+					this.isShow = true;
+					// this.$emit('show');
+				}, 100);
+			},
+			//关闭组件
+			closePicker() {
+				this.isShow = false;
+				//点遮罩 点取消关闭说明用户不想修改,所以这里对数据进行初始化
+				this.initValue();
+				this.stopVoice();
+			},
+			//点击确定
+			okClick() {
+				// var data = {},list = {},textStr = "",indexStr = "";								
+				this.$emit('okClick', this.voicePath)
+				this.$emit('start', 2)
+				//确定后更新默认初始值,这样再次进入默认初值就是最后选择的
+				// this.defaultArr = textStr;
+				//关闭
+				this.closePicker();
+			},
+			start() {
+				console.log('start')
+				this.stopVoice();
+				this.voicePath = ""; //音频地址				
+				this.recordTime = 0;
+				//生成canvas对象
+				this.ctx = uni.createCanvasContext('canvas',this);
+			},
+			end() {
+				console.log('end')
+				let recordTime = this.recordTime;
+				this.recordTime1 = this.recordTime;
+
+				clearInterval(this.timeObj); //清除计时器
+				clearInterval(this.drawObj); //清除画布动画	
+				// this.recordTime = 0; //清除计时
+				this.isshowyuan = false; //隐藏圆圈
+				//清除canvas内容 方式一:不知道为啥 不起作用
+				//this.ctx.clearRect(0,0,this.ctx.width,this.ctx.height);
+				//清除canvas内容 方式二:填充canvas为白色
+				this.ctx.setFillStyle('#fff')
+				this.ctx.fillRect(0, 0, this.ctx.width, this.ctx.height)
+				this.ctx.draw()
+				
+				if (recordTime < this.minTime) {
+					if (recordTime <= 0) {
+						//==点击事件==;
+						return false;
+					}
+					//==小于5秒==;
+					uni.showToast({
+						title: "不能小于" + this.minTime + "秒,请重新录制",
+						icon: "none"
+					})
+					return false;
+				}
+				recorderManager.stop();
+			},
+			record: function() {
+				console.log('record')
+				let _this = this;
+
+				_this.isshowyuan = true
+				// 开始录音
+				recorderManager.start({
+					format: "mp3"
+				});
+
+				_this.timeObj = setInterval(function() {
+					_this.recordTime++;
+					if (_this.recordTime == _this.maxTime) _this.end();
+				}, 1000);
+
+				//中心点坐标 这里如果直接除2发现位置有偏差,目前还没明白为什么要减1
+				let pianyi = 0
+				switch (uni.getSystemInfoSync().platform) {
+					case 'android':
+						pianyi = 0;
+						break;
+					case 'ios':
+						pianyi = 1; 
+						break;
+					default:
+						pianyi = 1;
+						break;
+				}
+				// #ifdef APP-PLUS
+				let centerX = _this.tempw / 2 + pianyi;
+				let centerY = _this.temph / 2 + pianyi;
+				// #endif
+				// #ifdef MP-WEIXIN
+				let centerX = _this.tempw / 2 + pianyi-1;
+				let centerY = _this.temph / 2 + pianyi-1;
+				// #endif
+				let yuanhuanW = _this.tempw1 / 2 -6; //圆环的半径  中间图片的宽度/2 + 4
+
+				// 录音过程圆圈动画的背景园
+				_this.ctx.beginPath();
+				_this.ctx.setStrokeStyle("#1789FD");
+				_this.ctx.setGlobalAlpha(0.3)
+				_this.ctx.setLineWidth(3);
+				_this.ctx.arc(centerX, centerY, yuanhuanW, 0, 2 * Math.PI);
+				_this.ctx.stroke();
+				_this.ctx.draw();
+
+				// 录音过程圆圈动画
+				let angle = -0.5;
+				_this.drawObj = setInterval(function() {
+					_this.ctx.beginPath();
+					_this.ctx.setStrokeStyle("#1789FD");
+					_this.ctx.setGlobalAlpha(1)
+					_this.ctx.setLineWidth(3);
+					_this.ctx.arc(centerX, centerY, yuanhuanW, -0.5 * Math.PI, (angle += 2 / (_this
+						.maxTime * 1000 / _this.frame)) * Math.PI, false);
+					_this.ctx.stroke();
+					_this.ctx.draw(true);
+				}, _this.frame);
+			},
+			playVoice() {
+				if (this.voicePath && this.playing === 0) {
+					innerAudioContext.src = this.voicePath;
+
+					innerAudioContext.stop(); //todo 第一次play时若不先stop则播放不出来,未知原因
+					innerAudioContext.play();
+
+					this.playing = 1;
+					this.recordTime = this.recordTime1;
+					this.countdownObj = setInterval(() => {
+						this.recordTime--;
+						if (this.recordTime === 0) {
+							this.stopVoice()
+							return;
+						}
+					}, 1000)
+				}
+			},
+			stopVoice() {
+				innerAudioContext.stop();
+				this.playing = 0;
+				this.recordTime = 0;
+				clearInterval(this.countdownObj);
+			},
+
+
+		}
+	}
+</script>
+
+<style lang="scss">
+	.jsfun-record {
+
+		.mask {
+			position: fixed;
+			z-index: 1000;
+			top: 0;
+			right: 0;
+			left: 0;
+			bottom: 0;
+			background: rgba(0, 0, 0, 0.6);
+		}
+
+		.conbox {
+			transition: all .3s ease;
+			transform: translateY(100%);
+
+			&.pickerShow {
+				transform: translateY(0);
+			}
+
+			position: fixed;
+			z-index: 1000;
+			right: 0;
+			left: 0;
+			bottom: 0;
+			background: #fff;
+		}
+
+		.c666 {
+			color: #666;
+		}
+
+		.c999 {
+			color: #999;
+		}
+
+		.fz28 {
+			font-size: 28upx;
+		}
+
+		.fz32 {
+			font-size: 32upx;
+		}
+
+
+		.record {
+			text-align: center;
+
+			.time {
+				text-align: center;
+				font-size: 60upx;
+				color: #000;
+				line-height: 100upx;
+				margin-top: 50upx;
+			}
+
+			.domess {
+				margin-bottom: 50upx;
+			}
+
+
+			.record-box {
+				display: flex;
+				flex-direction: row;
+				justify-content: center;
+			}
+
+			canvas {
+				margin: 10upx 60upx;
+				position: relative;
+				width: 200upx;
+				height: 200upx;
+				z-index: 10;
+
+				.recording {
+					position: absolute;
+					top: 20upx;
+					left: 20upx;
+					width: 160upx;
+					height: 160upx;
+					border: 1px dashed #1789FD;
+					border-radius: 100upx;
+					background: #1789FD url(../../static/jsfun-record/recording.png) no-repeat 50% 50%;
+					background-size: 50% 50%;
+					z-index: 100;
+				}
+			}
+
+			.btncom {
+				margin-top: 70upx;
+				width: 80upx;
+				height: 80upx;
+				border-radius: 80upx;
+			}
+
+			.stop {
+				@extend .btncom;
+				background: #1789FD url(../../static/jsfun-record/stop.png) no-repeat;
+				background-size: 100% 100%;
+			}
+
+			.paly {
+				@extend .btncom;
+				background: #1789FD url(../../static/jsfun-record/play.png) no-repeat;
+				background-size: 100% 100%;
+			}
+
+			.confirm {
+				@extend .btncom;
+				background: url(../../static/jsfun-record/confirm.png) no-repeat 100% 100%;
+				background-size: 100% 100%;
+			}
+
+
+		}
+
+	}
+</style>

+ 4933 - 0
my/components/wangding-pickerAddress/data.js

@@ -0,0 +1,4933 @@
+export default [{
+		"name": "北京市",
+		"city": [{
+			"name": "北京市",
+			"area": [
+				"东城区",
+				"西城区",
+				"崇文区",
+				"宣武区",
+				"朝阳区",
+				"丰台区",
+				"石景山区",
+				"海淀区",
+				"门头沟区",
+				"房山区",
+				"通州区",
+				"顺义区",
+				"昌平区",
+				"大兴区",
+				"平谷区",
+				"怀柔区",
+				"密云县",
+				"延庆县"
+			]
+		}]
+	},
+	{
+		"name": "天津市",
+		"city": [{
+			"name": "天津市",
+			"area": [
+				"和平区",
+				"河东区",
+				"河西区",
+				"南开区",
+				"河北区",
+				"红桥区",
+				"塘沽区",
+				"汉沽区",
+				"大港区",
+				"东丽区",
+				"西青区",
+				"津南区",
+				"北辰区",
+				"武清区",
+				"宝坻区",
+				"宁河县",
+				"静海县",
+				"蓟  县"
+			]
+		}]
+	},
+	{
+		"name": "河北省",
+		"city": [{
+				"name": "石家庄市",
+				"area": [
+					"长安区",
+					"桥东区",
+					"桥西区",
+					"新华区",
+					"郊  区",
+					"井陉矿区",
+					"井陉县",
+					"正定县",
+					"栾城县",
+					"行唐县",
+					"灵寿县",
+					"高邑县",
+					"深泽县",
+					"赞皇县",
+					"无极县",
+					"平山县",
+					"元氏县",
+					"赵  县",
+					"辛集市",
+					"藁",
+					"晋州市",
+					"新乐市",
+					"鹿泉市"
+				]
+			},
+			{
+				"name": "唐山市",
+				"area": [
+					"路南区",
+					"路北区",
+					"古冶区",
+					"开平区",
+					"新  区",
+					"丰润县",
+					"滦  县",
+					"滦南县",
+					"乐亭县",
+					"迁西县",
+					"玉田县",
+					"唐海县",
+					"遵化市",
+					"丰南市",
+					"迁安市"
+				]
+			},
+			{
+				"name": "秦皇岛市",
+				"area": [
+					"海港区",
+					"山海关区",
+					"北戴河区",
+					"青龙满族自治县",
+					"昌黎县",
+					"抚宁县",
+					"卢龙县"
+				]
+			},
+			{
+				"name": "邯郸市",
+				"area": [
+					"邯山区",
+					"丛台区",
+					"复兴区",
+					"峰峰矿区",
+					"邯郸县",
+					"临漳县",
+					"成安县",
+					"大名县",
+					"涉  县",
+					"磁  县",
+					"肥乡县",
+					"永年县",
+					"邱  县",
+					"鸡泽县",
+					"广平县",
+					"馆陶县",
+					"魏  县",
+					"曲周县",
+					"武安市"
+				]
+			},
+			{
+				"name": "邢台市",
+				"area": [
+					"桥东区",
+					"桥西区",
+					"邢台县",
+					"临城县",
+					"内丘县",
+					"柏乡县",
+					"隆尧县",
+					"任  县",
+					"南和县",
+					"宁晋县",
+					"巨鹿县",
+					"新河县",
+					"广宗县",
+					"平乡县",
+					"威  县",
+					"清河县",
+					"临西县",
+					"南宫市",
+					"沙河市"
+				]
+			},
+			{
+				"name": "保定市",
+				"area": [
+					"新市区",
+					"北市区",
+					"南市区",
+					"满城县",
+					"清苑县",
+					"涞水县",
+					"阜平县",
+					"徐水县",
+					"定兴县",
+					"唐  县",
+					"高阳县",
+					"容城县",
+					"涞源县",
+					"望都县",
+					"安新县",
+					"易  县",
+					"曲阳县",
+					"蠡  县",
+					"顺平县",
+					"博野",
+					"雄县",
+					"涿州市",
+					"定州市",
+					"安国市",
+					"高碑店市"
+				]
+			},
+			{
+				"name": "张家口",
+				"area": [
+					"桥东区",
+					"桥西区",
+					"宣化区",
+					"下花园区",
+					"宣化县",
+					"张北县",
+					"康保县",
+					"沽源县",
+					"尚义县",
+					"蔚  县",
+					"阳原县",
+					"怀安县",
+					"万全县",
+					"怀来县",
+					"涿鹿县",
+					"赤城县",
+					"崇礼县"
+				]
+			},
+			{
+				"name": "承德市",
+				"area": [
+					"双桥区",
+					"双滦区",
+					"鹰手营子矿区",
+					"承德县",
+					"兴隆县",
+					"平泉县",
+					"滦平县",
+					"隆化县",
+					"丰宁满族自治县",
+					"宽城满族自治县",
+					"围场满族蒙古族自治县"
+				]
+			},
+			{
+				"name": "沧州市",
+				"area": [
+					"新华区",
+					"运河区",
+					"沧  县",
+					"青  县",
+					"东光县",
+					"海兴县",
+					"盐山县",
+					"肃宁县",
+					"南皮县",
+					"吴桥县",
+					"献  县",
+					"孟村回族自治县",
+					"泊头市",
+					"任丘市",
+					"黄骅市",
+					"河间市"
+				]
+			},
+			{
+				"name": "廊坊市",
+				"area": [
+					"安次区",
+					"固安县",
+					"永清县",
+					"香河县",
+					"大城县",
+					"文安县",
+					"大厂回族自治县",
+					"霸州市",
+					"三河市"
+				]
+			},
+			{
+				"name": "衡水市",
+				"area": [
+					"桃城区",
+					"枣强县",
+					"武邑县",
+					"武强县",
+					"饶阳县",
+					"安平县",
+					"故城县",
+					"景  县",
+					"阜城县",
+					"冀州市",
+					"深州市"
+				]
+			}
+		]
+	},
+	{
+		"name": "山西省",
+		"city": [{
+				"name": "太原市",
+				"area": [
+					"小店区",
+					"迎泽区",
+					"杏花岭区",
+					"尖草坪区",
+					"万柏林区",
+					"晋源区",
+					"清徐县",
+					"阳曲县",
+					"娄烦县",
+					"古交市"
+				]
+			},
+			{
+				"name": "大同市",
+				"area": [
+					"城  区",
+					"矿  区",
+					"南郊区",
+					"新荣区",
+					"阳高县",
+					"天镇县",
+					"广灵县",
+					"灵丘县",
+					"浑源县",
+					"左云县",
+					"大同县"
+				]
+			},
+			{
+				"name": "阳泉市",
+				"area": [
+					"城  区",
+					"矿  区",
+					"郊  区",
+					"平定县",
+					"盂  县"
+				]
+			},
+			{
+				"name": "长治市",
+				"area": [
+					"城  区",
+					"郊  区",
+					"长治县",
+					"襄垣县",
+					"屯留县",
+					"平顺县",
+					"黎城县",
+					"壶关县",
+					"长子县",
+					"武乡县",
+					"沁  县",
+					"沁源县",
+					"潞城市"
+				]
+			},
+			{
+				"name": "晋城市",
+				"area": [
+					"城  区",
+					"沁水县",
+					"阳城县",
+					"陵川县",
+					"泽州县",
+					"高平市"
+				]
+			},
+			{
+				"name": "朔州市",
+				"area": [
+					"朔城区",
+					"平鲁区",
+					"山阴县",
+					"应  县",
+					"右玉县",
+					"怀仁县"
+				]
+			},
+			{
+				"name": "忻州市",
+				"area": [
+					"忻府区",
+					"原平市",
+					"定襄县",
+					"五台县",
+					"代  县",
+					"繁峙县",
+					"宁武县",
+					"静乐县",
+					"神池县",
+					"五寨县",
+					"岢岚县",
+					"河曲县",
+					"保德县",
+					"偏关县"
+				]
+			},
+			{
+				"name": "吕梁市",
+				"area": [
+					"离石区",
+					"孝义市",
+					"汾阳市",
+					"文水县",
+					"交城县",
+					"兴  县",
+					"临  县",
+					"柳林县",
+					"石楼县",
+					"岚  县",
+					"方山县",
+					"中阳县",
+					"交口县"
+				]
+			},
+			{
+				"name": "晋中市",
+				"area": [
+					"榆次市",
+					"介休市",
+					"榆社县",
+					"左权县",
+					"和顺县",
+					"昔阳县",
+					"寿阳县",
+					"太谷县",
+					"祁  县",
+					"平遥县",
+					"灵石县"
+				]
+			},
+			{
+				"name": "临汾市",
+				"area": [
+					"临汾市",
+					"侯马市",
+					"霍州市",
+					"曲沃县",
+					"翼城县",
+					"襄汾县",
+					"洪洞县",
+					"古  县",
+					"安泽县",
+					"浮山县",
+					"吉  县",
+					"乡宁县",
+					"蒲  县",
+					"大宁县",
+					"永和县",
+					"隰  县",
+					"汾西县"
+				]
+			},
+			{
+				"name": "运城市",
+				"area": [
+					"运城市",
+					"永济市",
+					"河津市",
+					"芮城县",
+					"临猗县",
+					"万荣县",
+					"新绛县",
+					"稷山县",
+					"闻喜县",
+					"夏  县",
+					"绛  县",
+					"平陆县",
+					"垣曲县"
+				]
+			}
+		]
+	},
+	{
+		"name": "内蒙古",
+		"city": [{
+				"name": "呼和浩特市",
+				"area": [
+					"新城区",
+					"回民区",
+					"玉泉区",
+					"郊  区",
+					"土默特左旗",
+					"托克托县",
+					"和林格尔县",
+					"清水河县",
+					"武川县"
+				]
+			},
+			{
+				"name": "包头市",
+				"area": [
+					"东河区",
+					"昆都伦区",
+					"青山区",
+					"石拐矿区",
+					"白云矿区",
+					"郊  区",
+					"土默特右旗",
+					"固阳县",
+					"达尔罕茂明安联合旗"
+				]
+			},
+			{
+				"name": "乌海市",
+				"area": [
+					"海勃湾区",
+					"海南区",
+					"乌达区"
+				]
+			},
+			{
+				"name": "赤峰市",
+				"area": [
+					"红山区",
+					"元宝山区",
+					"松山区",
+					"阿鲁科尔沁旗",
+					"巴林左旗",
+					"巴林右旗",
+					"林西县",
+					"克什克腾旗",
+					"翁牛特旗",
+					"喀喇沁旗",
+					"宁城县",
+					"敖汉旗"
+				]
+			},
+			{
+				"name": "呼伦贝尔市",
+				"area": [
+					"海拉尔市",
+					"满洲里市",
+					"扎兰屯市",
+					"牙克石市",
+					"根河市",
+					"额尔古纳市",
+					"阿荣旗",
+					"莫力达瓦达斡尔族自治旗",
+					"鄂伦春自治旗",
+					"鄂温克族自治旗",
+					"新巴尔虎右旗",
+					"新巴尔虎左旗",
+					"陈巴尔虎旗"
+				]
+			},
+			{
+				"name": "兴安盟",
+				"area": [
+					"乌兰浩特市",
+					"阿尔山市",
+					"科尔沁右翼前旗",
+					"科尔沁右翼中旗",
+					"扎赉特旗",
+					"突泉县"
+				]
+			},
+			{
+				"name": "通辽市",
+				"area": [
+					"科尔沁区",
+					"霍林郭勒市",
+					"科尔沁左翼中旗",
+					"科尔沁左翼后旗",
+					"开鲁县",
+					"库伦旗",
+					"奈曼旗",
+					"扎鲁特旗"
+				]
+			},
+			{
+				"name": "锡林郭勒盟",
+				"area": [
+					"二连浩特市",
+					"锡林浩特市",
+					"阿巴嘎旗",
+					"苏尼特左旗",
+					"苏尼特右旗",
+					"东乌珠穆沁旗",
+					"西乌珠穆沁旗",
+					"太仆寺旗",
+					"镶黄旗",
+					"正镶白旗",
+					"正蓝旗",
+					"多伦县"
+				]
+			},
+			{
+				"name": "乌兰察布盟",
+				"area": [
+					"集宁市",
+					"丰镇市",
+					"卓资县",
+					"化德县",
+					"商都县",
+					"兴和县",
+					"凉城县",
+					"察哈尔右翼前旗",
+					"察哈尔右翼中旗",
+					"察哈尔右翼后旗",
+					"四子王旗"
+				]
+			},
+			{
+				"name": "伊克昭盟",
+				"area": [
+					"东胜市",
+					"达拉特旗",
+					"准格尔旗",
+					"鄂托克前旗",
+					"鄂托克旗",
+					"杭锦旗",
+					"乌审旗",
+					"伊金霍洛旗"
+				]
+			},
+			{
+				"name": "巴彦淖尔盟",
+				"area": [
+					"临河市",
+					"五原县",
+					"磴口县",
+					"乌拉特前旗",
+					"乌拉特中旗",
+					"乌拉特后旗",
+					"杭锦后旗"
+				]
+			},
+			{
+				"name": "阿拉善盟",
+				"area": [
+					"阿拉善左旗",
+					"阿拉善右旗",
+					"额济纳旗"
+				]
+			}
+		]
+	},
+	{
+		"name": "辽宁省",
+		"city": [{
+				"name": "沈阳市",
+				"area": [
+					"沈河区",
+					"皇姑区",
+					"和平区",
+					"大东区",
+					"铁西区",
+					"苏家屯区",
+					"东陵区",
+					"于洪区",
+					"新民市",
+					"法库县",
+					"辽中县",
+					"康平县",
+					"新城子区"
+				]
+			},
+			{
+				"name": "大连市",
+				"area": [
+					"西岗区",
+					"中山区",
+					"沙河口区",
+					"甘井子区",
+					"旅顺口区",
+					"金州区",
+					"瓦房店市",
+					"普兰店市",
+					"庄河市",
+					"长海县"
+				]
+			},
+			{
+				"name": "鞍山市",
+				"area": [
+					"铁东区",
+					"铁西区",
+					"立山区",
+					"千山区",
+					"海城市",
+					"台安县",
+					"岫岩满族自治县"
+				]
+			},
+			{
+				"name": "抚顺市",
+				"area": [
+					"顺城区",
+					"新抚区",
+					"东洲区",
+					"望花区",
+					"抚顺县",
+					"清原满族自治县",
+					"新宾满族自治县"
+				]
+			},
+			{
+				"name": "本溪市",
+				"area": [
+					"平山区",
+					"明山区",
+					"溪湖区",
+					"南芬区",
+					"本溪满族自治县",
+					"桓仁满族自治县"
+				]
+			},
+			{
+				"name": "丹东市",
+				"area": [
+					"振兴区",
+					"元宝区",
+					"振安区",
+					"东港市",
+					"凤城市",
+					"宽甸满族自治县"
+				]
+			},
+			{
+				"name": "锦州市",
+				"area": [
+					"太和区",
+					"古塔区",
+					"凌河区",
+					"凌海市",
+					"黑山县",
+					"义县",
+					"北宁市"
+				]
+			},
+			{
+				"name": "营口市",
+				"area": [
+					"站前区",
+					"西市区",
+					"鲅鱼圈区",
+					"老边区",
+					"大石桥市",
+					"盖州市"
+				]
+			},
+			{
+				"name": "阜新市",
+				"area": [
+					"海州区",
+					"新邱区",
+					"太平区",
+					"清河门区",
+					"细河区",
+					"彰武县",
+					"阜新蒙古族自治县"
+				]
+			},
+			{
+				"name": "辽阳市",
+				"area": [
+					"白塔区",
+					"文圣区",
+					"宏伟区",
+					"太子河区",
+					"弓长岭区",
+					"灯塔市",
+					"辽阳县"
+				]
+			},
+			{
+				"name": "盘锦",
+				"area": [
+					"双台子区",
+					"兴隆台区",
+					"盘山县",
+					"大洼县"
+				]
+			},
+			{
+				"name": "铁岭市",
+				"area": [
+					"银州区",
+					"清河区",
+					"调兵山市",
+					"开原市",
+					"铁岭县",
+					"昌图县",
+					"西丰县"
+				]
+			},
+			{
+				"name": "朝阳市",
+				"area": [
+					"双塔区",
+					"龙城区",
+					"凌源市",
+					"北票市",
+					"朝阳县",
+					"建平县",
+					"喀喇沁左翼蒙古族自治县"
+				]
+			},
+			{
+				"name": "葫芦岛市",
+				"area": [
+					"龙港区",
+					"南票区",
+					"连山区",
+					"兴城市",
+					"绥中县",
+					"建昌县"
+				]
+			}
+		]
+	},
+	{
+		"name": "吉林省",
+		"city": [{
+				"name": "长春市",
+				"area": [
+					"朝阳区",
+					"宽城区",
+					"二道区",
+					"南关区",
+					"绿园区",
+					"双阳区",
+					"九台市",
+					"榆树市",
+					"德惠市",
+					"农安县"
+				]
+			},
+			{
+				"name": "吉林市",
+				"area": [
+					"船营区",
+					"昌邑区",
+					"龙潭区",
+					"丰满区",
+					"舒兰市",
+					"桦甸市",
+					"蛟河市",
+					"磐石市",
+					"永吉县"
+				]
+			},
+			{
+				"name": "四平",
+				"area": [
+					"铁西区",
+					"铁东区",
+					"公主岭市",
+					"双辽市",
+					"梨树县",
+					"伊通满族自治县"
+				]
+			},
+			{
+				"name": "辽源市",
+				"area": [
+					"龙山区",
+					"西安区",
+					"东辽县",
+					"东丰县"
+				]
+			},
+			{
+				"name": "通化市",
+				"area": [
+					"东昌区",
+					"二道江区",
+					"梅河口市",
+					"集安市",
+					"通化县",
+					"辉南县",
+					"柳河县"
+				]
+			},
+			{
+				"name": "白山市",
+				"area": [
+					"八道江区",
+					"江源区",
+					"临江市",
+					"靖宇县",
+					"抚松县",
+					"长白朝鲜族自治县"
+				]
+			},
+			{
+				"name": "松原市",
+				"area": [
+					"宁江区",
+					"乾安县",
+					"长岭县",
+					"扶余县",
+					"前郭尔罗斯蒙古族自治县"
+				]
+			},
+			{
+				"name": "白城市",
+				"area": [
+					"洮北区",
+					"大安市",
+					"洮南市",
+					"镇赉县",
+					"通榆县"
+				]
+			},
+			{
+				"name": "延边朝鲜族自治州",
+				"area": [
+					"延吉市",
+					"图们市",
+					"敦化市",
+					"龙井市",
+					"珲春市",
+					"和龙市",
+					"安图县",
+					"汪清县"
+				]
+			}
+		]
+	},
+	{
+		"name": "黑龙江省",
+		"city": [{
+				"name": "哈尔滨市",
+				"area": [
+					"松北区",
+					"道里区",
+					"南岗区",
+					"平房区",
+					"香坊区",
+					"道外区",
+					"呼兰区",
+					"阿城区",
+					"双城市",
+					"尚志市",
+					"五常市",
+					"宾县",
+					"方正县",
+					"通河县",
+					"巴彦县",
+					"延寿县",
+					"木兰县",
+					"依兰县"
+				]
+			},
+			{
+				"name": "齐齐哈尔市",
+				"area": [
+					"龙沙区",
+					"昂昂溪区",
+					"铁锋区",
+					"建华区",
+					"富拉尔基区",
+					"碾子山区",
+					"梅里斯达斡尔族区",
+					"讷河市",
+					"富裕县",
+					"拜泉县",
+					"甘南县",
+					"依安县",
+					"克山县",
+					"泰来县",
+					"克东县",
+					"龙江县"
+				]
+			},
+			{
+				"name": "鹤岗市",
+				"area": [
+					"兴山区",
+					"工农区",
+					"南山区",
+					"兴安区",
+					"向阳区",
+					"东山区",
+					"萝北县",
+					"绥滨县"
+				]
+			},
+			{
+				"name": "双鸭山",
+				"area": [
+					"尖山区",
+					"岭东区",
+					"四方台区",
+					"宝山区",
+					"集贤县",
+					"宝清县",
+					"友谊县",
+					"饶河县"
+				]
+			},
+			{
+				"name": "鸡西市",
+				"area": [
+					"鸡冠区",
+					"恒山区",
+					"城子河区",
+					"滴道区",
+					"梨树区",
+					"麻山区",
+					"密山市",
+					"虎林市",
+					"鸡东县"
+				]
+			},
+			{
+				"name": "大庆市",
+				"area": [
+					"萨尔图区",
+					"红岗区",
+					"龙凤区",
+					"让胡路区",
+					"大同区",
+					"林甸县",
+					"肇州县",
+					"肇源县",
+					"杜尔伯特蒙古族自治县"
+				]
+			},
+			{
+				"name": "伊春市",
+				"area": [
+					"伊春区",
+					"带岭区",
+					"南岔区",
+					"金山屯区",
+					"西林区",
+					"美溪区",
+					"乌马河区",
+					"翠峦区",
+					"友好区",
+					"上甘岭区",
+					"五营区",
+					"红星区",
+					"新青区",
+					"汤旺河区",
+					"乌伊岭区",
+					"铁力市",
+					"嘉荫县"
+				]
+			},
+			{
+				"name": "牡丹江市",
+				"area": [
+					"爱民区",
+					"东安区",
+					"阳明区",
+					"西安区",
+					"绥芬河市",
+					"宁安市",
+					"海林市",
+					"穆棱市",
+					"林口县",
+					"东宁县"
+				]
+			},
+			{
+				"name": "佳木斯市",
+				"area": [
+					"向阳区",
+					"前进区",
+					"东风区",
+					"郊区",
+					"同江市",
+					"富锦市",
+					"桦川县",
+					"抚远县",
+					"桦南县",
+					"汤原县"
+				]
+			},
+			{
+				"name": "七台河市",
+				"area": [
+					"桃山区",
+					"新兴区",
+					"茄子河区",
+					"勃利县"
+				]
+			},
+			{
+				"name": "黑河市",
+				"area": [
+					"爱辉区",
+					"北安市",
+					"五大连池市",
+					"逊克县",
+					"嫩江县",
+					"孙吴县"
+				]
+			},
+			{
+				"name": "绥化市",
+				"area": [
+					"北林区",
+					"安达市",
+					"肇东市",
+					"海伦市",
+					"绥棱县",
+					"兰西县",
+					"明水县",
+					"青冈县",
+					"庆安县",
+					"望奎县"
+				]
+			},
+			{
+				"name": "大兴安岭地区",
+				"area": [
+					"呼玛县",
+					"塔河县",
+					"漠河县",
+					"大兴安岭辖区"
+				]
+			}
+		]
+	},
+	{
+		"name": "上海市",
+		"city": [{
+			"name": "上海市",
+			"area": [
+				"黄浦区",
+				"卢湾区",
+				"徐汇区",
+				"长宁区",
+				"静安区",
+				"普陀区",
+				"闸北区",
+				"虹口区",
+				"杨浦区",
+				"宝山区",
+				"闵行区",
+				"嘉定区",
+				"松江区",
+				"金山区",
+				"青浦区",
+				"南汇区",
+				"奉贤区",
+				"浦东新区",
+				"崇明县"
+			]
+		}]
+	},
+	{
+		"name": "江苏省",
+		"city": [{
+				"name": "南京市",
+				"area": [
+					"玄武区",
+					"白下区",
+					"秦淮区",
+					"建邺区",
+					"鼓楼区",
+					"下关区",
+					"栖霞区",
+					"雨花台区",
+					"浦口区",
+					"江宁区",
+					"六合区",
+					"溧水县",
+					"高淳县"
+				]
+			},
+			{
+				"name": "苏州市",
+				"area": [
+					"金阊区",
+					"平江区",
+					"沧浪区",
+					"虎丘区",
+					"吴中区",
+					"相城区",
+					"常熟市",
+					"张家港市",
+					"昆山市",
+					"吴江市",
+					"太仓市"
+				]
+			},
+			{
+				"name": "无锡市",
+				"area": [
+					"崇安区",
+					"南长区",
+					"北塘区",
+					"滨湖区",
+					"锡山区",
+					"惠山区",
+					"江阴市",
+					"宜兴市"
+				]
+			},
+			{
+				"name": "常州市",
+				"area": [
+					"钟楼区",
+					"天宁区",
+					"戚墅堰区",
+					"新北区",
+					"武进区",
+					"金坛市",
+					"溧阳市"
+				]
+			},
+			{
+				"name": "镇江市",
+				"area": [
+					"京口区",
+					"润州区",
+					"丹徒区",
+					"丹阳市",
+					"扬中市",
+					"句容市"
+				]
+			},
+			{
+				"name": "南通市",
+				"area": [
+					"崇川区",
+					"港闸区",
+					"通州市",
+					"如皋市",
+					"海门市",
+					"启东市",
+					"海安县",
+					"如东县"
+				]
+			},
+			{
+				"name": "泰州市",
+				"area": [
+					"海陵区",
+					"高港区",
+					"姜堰市",
+					"泰兴市",
+					"靖江市",
+					"兴化市"
+				]
+			},
+			{
+				"name": "扬州市",
+				"area": [
+					"广陵区",
+					"维扬区",
+					"邗江区",
+					"江都市",
+					"仪征市",
+					"高邮市",
+					"宝应县"
+				]
+			},
+			{
+				"name": "盐城市",
+				"area": [
+					"亭湖区",
+					"盐都区",
+					"大丰市",
+					"东台市",
+					"建湖县",
+					"射阳县",
+					"阜宁县",
+					"滨海县",
+					"响水县"
+				]
+			},
+			{
+				"name": "连云港市",
+				"area": [
+					"新浦区",
+					"海州区",
+					"连云区",
+					"东海县",
+					"灌云县",
+					"赣榆县",
+					"灌南县"
+				]
+			},
+			{
+				"name": "徐州市",
+				"area": [
+					"云龙区",
+					"鼓楼区",
+					"九里区",
+					"泉山区",
+					"贾汪区",
+					"邳州市",
+					"新沂市",
+					"铜山县",
+					"睢宁县",
+					"沛县",
+					"丰县"
+				]
+			},
+			{
+				"name": "淮安市",
+				"area": [
+					"清河区",
+					"清浦区",
+					"楚州区",
+					"淮阴区",
+					"涟水县",
+					"洪泽县",
+					"金湖县",
+					"盱眙县"
+				]
+			},
+			{
+				"name": "宿迁市",
+				"area": [
+					"宿城区",
+					"宿豫区",
+					"沭阳县",
+					"泗阳县",
+					"泗洪县"
+				]
+			}
+		]
+	},
+	{
+		"name": "浙江省",
+		"city": [{
+				"name": "杭州市",
+				"area": [
+					"拱墅区",
+					"西湖区",
+					"上城区",
+					"下城区",
+					"江干区",
+					"滨江区",
+					"余杭区",
+					"萧山区",
+					"建德市",
+					"富阳市",
+					"临安市",
+					"桐庐县",
+					"淳安县"
+				]
+			},
+			{
+				"name": "宁波市",
+				"area": [
+					"海曙区",
+					"江东区",
+					"江北区",
+					"镇海区",
+					"北仑区",
+					"鄞州区",
+					"余姚市",
+					"慈溪市",
+					"奉化市",
+					"宁海县",
+					"象山县"
+				]
+			},
+			{
+				"name": "温州市",
+				"area": [
+					"鹿城区",
+					"龙湾区",
+					"瓯海区",
+					"瑞安市",
+					"乐清市",
+					"永嘉县",
+					"洞头县",
+					"平阳县",
+					"苍南县",
+					"文成县",
+					"泰顺县"
+				]
+			},
+			{
+				"name": "嘉兴市",
+				"area": [
+					"秀城区",
+					"秀洲区",
+					"海宁市",
+					"平湖市",
+					"桐乡市",
+					"嘉善县",
+					"海盐县"
+				]
+			},
+			{
+				"name": "湖州市",
+				"area": [
+					"吴兴区",
+					"南浔区",
+					"长兴县",
+					"德清县",
+					"安吉县"
+				]
+			},
+			{
+				"name": "绍兴市",
+				"area": [
+					"越城区",
+					"诸暨市",
+					"上虞市",
+					"嵊州市",
+					"绍兴县",
+					"新昌县"
+				]
+			},
+			{
+				"name": "金华市",
+				"area": [
+					"婺城区",
+					"金东区",
+					"兰溪市",
+					"义乌市",
+					"东阳市",
+					"永康市",
+					"武义县",
+					"浦江县",
+					"磐安县"
+				]
+			},
+			{
+				"name": "衢州市",
+				"area": [
+					"柯城区",
+					"衢江区",
+					"江山市",
+					"龙游县",
+					"常山县",
+					"开化县"
+				]
+			},
+			{
+				"name": "舟山市",
+				"area": [
+					"定海区",
+					"普陀区",
+					"岱山县",
+					"嵊泗县"
+				]
+			},
+			{
+				"name": "台州市",
+				"area": [
+					"椒江区",
+					"黄岩区",
+					"路桥区",
+					"临海市",
+					"温岭市",
+					"玉环县",
+					"天台县",
+					"仙居县",
+					"三门县"
+				]
+			},
+			{
+				"name": "丽水市",
+				"area": [
+					"莲都区",
+					"龙泉市",
+					"缙云县",
+					"青田县",
+					"云和县",
+					"遂昌县",
+					"松阳县",
+					"庆元县",
+					"景宁畲族自治县"
+				]
+			}
+		]
+	},
+	{
+		"name": "安徽省",
+		"city": [{
+				"name": "合肥市",
+				"area": [
+					"庐阳区",
+					"瑶海区",
+					"蜀山区",
+					"包河区",
+					"长丰县",
+					"肥东县",
+					"肥西县"
+				]
+			},
+			{
+				"name": "芜湖市",
+				"area": [
+					"镜湖区",
+					"弋江区",
+					"鸠江区",
+					"三山区",
+					"芜湖县",
+					"南陵县",
+					"繁昌县"
+				]
+			},
+			{
+				"name": "蚌埠市",
+				"area": [
+					"蚌山区",
+					"龙子湖区",
+					"禹会区",
+					"淮上区",
+					"怀远县",
+					"固镇县",
+					"五河县"
+				]
+			},
+			{
+				"name": "淮南市",
+				"area": [
+					"田家庵区",
+					"大通区",
+					"谢家集区",
+					"八公山区",
+					"潘集区",
+					"凤台县"
+				]
+			},
+			{
+				"name": "马鞍山市",
+				"area": [
+					"雨山区",
+					"花山区",
+					"金家庄区",
+					"当涂县"
+				]
+			},
+			{
+				"name": "淮北市",
+				"area": [
+					"相山区",
+					"杜集区",
+					"烈山区",
+					"濉溪县"
+				]
+			},
+			{
+				"name": "铜陵市",
+				"area": [
+					"铜官山区",
+					"狮子山区",
+					"郊区",
+					"铜陵县"
+				]
+			},
+			{
+				"name": "安庆市",
+				"area": [
+					"迎江区",
+					"大观区",
+					"宜秀区",
+					"桐城市",
+					"宿松县",
+					"枞阳县",
+					"太湖县",
+					"怀宁县",
+					"岳西县",
+					"望江县",
+					"潜山县"
+				]
+			},
+			{
+				"name": "黄山市",
+				"area": [
+					"屯溪区",
+					"黄山区",
+					"徽州区",
+					"休宁县",
+					"歙县",
+					"祁门县",
+					"黟县"
+				]
+			},
+			{
+				"name": "滁州市",
+				"area": [
+					"琅琊区",
+					"南谯区",
+					"天长市",
+					"明光市",
+					"全椒县",
+					"来安县",
+					"定远县",
+					"凤阳县"
+				]
+			},
+			{
+				"name": "阜阳市",
+				"area": [
+					"颍州区",
+					"颍东区",
+					"颍泉区",
+					"界首市",
+					"临泉县",
+					"颍上县",
+					"阜南县",
+					"太和县"
+				]
+			},
+			{
+				"name": "宿州市",
+				"area": [
+					"埇桥区",
+					"萧县",
+					"泗县",
+					"砀山县",
+					"灵璧县"
+				]
+			},
+			{
+				"name": "巢湖市",
+				"area": [
+					"居巢区",
+					"含山县",
+					"无为县",
+					"庐江县",
+					"和县"
+				]
+			},
+			{
+				"name": "六安市",
+				"area": [
+					"金安区",
+					"裕安区",
+					"寿县",
+					"霍山县",
+					"霍邱县",
+					"舒城县",
+					"金寨县"
+				]
+			},
+			{
+				"name": "亳州市",
+				"area": [
+					"谯城区",
+					"利辛县",
+					"涡阳县",
+					"蒙城县"
+				]
+			},
+			{
+				"name": "池州市",
+				"area": [
+					"贵池区",
+					"东至县",
+					"石台县",
+					"青阳县"
+				]
+			},
+			{
+				"name": "宣城市",
+				"area": [
+					"宣州区",
+					"宁国市",
+					"广德县",
+					"郎溪县",
+					"泾县",
+					"旌德县",
+					"绩溪县"
+				]
+			}
+		]
+	},
+	{
+		"name": "福建省",
+		"city": [{
+				"name": "福州市",
+				"area": [
+					"鼓楼区",
+					"台江区",
+					"仓山区",
+					"马尾区",
+					"晋安区",
+					"福清市",
+					"长乐市",
+					"闽侯县",
+					"闽清县",
+					"永泰县",
+					"连江县",
+					"罗源县",
+					"平潭县"
+				]
+			},
+			{
+				"name": "厦门市",
+				"area": [
+					"思明区",
+					"海沧区",
+					"湖里区",
+					"集美区",
+					"同安区",
+					"翔安区"
+				]
+			},
+			{
+				"name": "莆田市",
+				"area": [
+					"城厢区",
+					"涵江区",
+					"荔城区",
+					"秀屿区",
+					"仙游县"
+				]
+			},
+			{
+				"name": "三明市",
+				"area": [
+					"梅列区",
+					"三元区",
+					"永安市",
+					"明溪县",
+					"将乐县",
+					"大田县",
+					"宁化县",
+					"建宁县",
+					"沙县",
+					"尤溪县",
+					"清流县",
+					"泰宁县"
+				]
+			},
+			{
+				"name": "泉州市",
+				"area": [
+					"鲤城区",
+					"丰泽区",
+					"洛江区",
+					"泉港区",
+					"石狮市",
+					"晋江市",
+					"南安市",
+					"惠安县",
+					"永春县",
+					"安溪县",
+					"德化县",
+					"金门县"
+				]
+			},
+			{
+				"name": "漳州市",
+				"area": [
+					"芗城区",
+					"龙文区",
+					"龙海市",
+					"平和县",
+					"南靖县",
+					"诏安县",
+					"漳浦县",
+					"华安县",
+					"东山县",
+					"长泰县",
+					"云霄县"
+				]
+			},
+			{
+				"name": "南平市",
+				"area": [
+					"延平区",
+					"建瓯市",
+					"邵武市",
+					"武夷山市",
+					"建阳市",
+					"松溪县",
+					"光泽县",
+					"顺昌县",
+					"浦城县",
+					"政和县"
+				]
+			},
+			{
+				"name": "龙岩市",
+				"area": [
+					"新罗区",
+					"漳平市",
+					"长汀县",
+					"武平县",
+					"上杭县",
+					"永定县",
+					"连城县"
+				]
+			},
+			{
+				"name": "宁德市",
+				"area": [
+					"蕉城区",
+					"福安市",
+					"福鼎市",
+					"寿宁县",
+					"霞浦县",
+					"柘荣县",
+					"屏南县",
+					"古田县",
+					"周宁县"
+				]
+			}
+		]
+	},
+	{
+		"name": "江西省",
+		"city": [{
+				"name": "南昌市",
+				"area": [
+					"东湖区",
+					"西湖区",
+					"青云谱区",
+					"湾里区",
+					"青山湖区",
+					"新建县",
+					"南昌县",
+					"进贤县",
+					"安义县"
+				]
+			},
+			{
+				"name": "景德镇市",
+				"area": [
+					"珠山区",
+					"昌江区",
+					"乐平市",
+					"浮梁县"
+				]
+			},
+			{
+				"name": "萍乡市",
+				"area": [
+					"安源区",
+					"湘东区",
+					"莲花县",
+					"上栗县",
+					"芦溪县"
+				]
+			},
+			{
+				"name": "九江市",
+				"area": [
+					"浔阳区",
+					"庐山区",
+					"瑞昌市",
+					"九江县",
+					"星子县",
+					"武宁县",
+					"彭泽县",
+					"永修县",
+					"修水县",
+					"湖口县",
+					"德安县",
+					"都昌县"
+				]
+			},
+			{
+				"name": "新余市",
+				"area": [
+					"渝水区",
+					"分宜县"
+				]
+			},
+			{
+				"name": "鹰潭市",
+				"area": [
+					"月湖区",
+					"贵溪市",
+					"余江县"
+				]
+			},
+			{
+				"name": "赣州市",
+				"area": [
+					"章贡区",
+					"瑞金市",
+					"南康市",
+					"石城县",
+					"安远县",
+					"赣县",
+					"宁都县",
+					"寻乌县",
+					"兴国县",
+					"定南县",
+					"上犹县",
+					"于都县",
+					"龙南县",
+					"崇义县",
+					"信丰县",
+					"全南县",
+					"大余县",
+					"会昌县"
+				]
+			},
+			{
+				"name": "吉安市",
+				"area": [
+					"吉州区",
+					"青原区",
+					"井冈山市",
+					"吉安县",
+					"永丰县",
+					"永新县",
+					"新干县",
+					"泰和县",
+					"峡江县",
+					"遂川县",
+					"安福县",
+					"吉水县",
+					"万安县"
+				]
+			},
+			{
+				"name": "宜春市",
+				"area": [
+					"袁州区",
+					"丰城市",
+					"樟树市",
+					"高安市",
+					"铜鼓县",
+					"靖安县",
+					"宜丰县",
+					"奉新县",
+					"万载县",
+					"上高县"
+				]
+			},
+			{
+				"name": "抚州市",
+				"area": [
+					"临川区",
+					"南丰县",
+					"乐安县",
+					"金溪县",
+					"南城县",
+					"东乡县",
+					"资溪县",
+					"宜黄县",
+					"广昌县",
+					"黎川县",
+					"崇仁县"
+				]
+			},
+			{
+				"name": "上饶市",
+				"area": [
+					"信州区",
+					"德兴市",
+					"上饶县",
+					"广丰县",
+					"鄱阳县",
+					"婺源县",
+					"铅山县",
+					"余干县",
+					"横峰县",
+					"弋阳县",
+					"玉山县",
+					"万年县"
+				]
+			}
+		]
+	},
+	{
+		"name": "山东省",
+		"city": [{
+				"name": "济南市",
+				"area": [
+					"市中区",
+					"历下区",
+					"天桥区",
+					"槐荫区",
+					"历城区",
+					"长清区",
+					"章丘市",
+					"平阴县",
+					"济阳县",
+					"商河县"
+				]
+			},
+			{
+				"name": "青岛市",
+				"area": [
+					"市南区",
+					"市北区",
+					"城阳区",
+					"四方区",
+					"李沧区",
+					"黄岛区",
+					"崂山区",
+					"胶南市",
+					"胶州市",
+					"平度市",
+					"莱西市",
+					"即墨市"
+				]
+			},
+			{
+				"name": "淄博市",
+				"area": [
+					"张店区",
+					"临淄区",
+					"淄川区",
+					"博山区",
+					"周村区",
+					"桓台县",
+					"高青县",
+					"沂源县"
+				]
+			},
+			{
+				"name": "枣庄市",
+				"area": [
+					"市中区",
+					"山亭区",
+					"峄城区",
+					"台儿庄区",
+					"薛城区",
+					"滕州市"
+				]
+			},
+			{
+				"name": "东营市",
+				"area": [
+					"东营区",
+					"河口区",
+					"垦利县",
+					"广饶县",
+					"利津县"
+				]
+			},
+			{
+				"name": "烟台市",
+				"area": [
+					"芝罘区",
+					"福山区",
+					"牟平区",
+					"莱山区",
+					"龙口市",
+					"莱阳市",
+					"莱州市",
+					"招远市",
+					"蓬莱市",
+					"栖霞市",
+					"海阳市",
+					"长岛县"
+				]
+			},
+			{
+				"name": "潍坊市",
+				"area": [
+					"潍城区",
+					"寒亭区",
+					"坊子区",
+					"奎文区",
+					"青州市",
+					"诸城市",
+					"寿光市",
+					"安丘市",
+					"高密市",
+					"昌邑市",
+					"昌乐县",
+					"临朐县"
+				]
+			},
+			{
+				"name": "济宁市",
+				"area": [
+					"市中区",
+					"任城区",
+					"曲阜市",
+					"兖州市",
+					"邹城市",
+					"鱼台县",
+					"金乡县",
+					"嘉祥县",
+					"微山县",
+					"汶上县",
+					"泗水县",
+					"梁山县"
+				]
+			},
+			{
+				"name": "泰安市",
+				"area": [
+					"泰山区",
+					"岱岳区",
+					"新泰市",
+					"肥城市",
+					"宁阳县",
+					"东平县"
+				]
+			},
+			{
+				"name": "威海市",
+				"area": [
+					"环翠区",
+					"乳山市",
+					"文登市",
+					"荣成市"
+				]
+			},
+			{
+				"name": "日照市",
+				"area": [
+					"东港区",
+					"岚山区",
+					"五莲县",
+					"莒县"
+				]
+			},
+			{
+				"name": "莱芜市",
+				"area": [
+					"莱城区",
+					"钢城区"
+				]
+			},
+			{
+				"name": "临沂市",
+				"area": [
+					"兰山区",
+					"罗庄区",
+					"河东区",
+					"沂南县",
+					"郯城县",
+					"沂水县",
+					"苍山县",
+					"费县",
+					"平邑县",
+					"莒南县",
+					"蒙阴县",
+					"临沭县"
+				]
+			},
+			{
+				"name": "德州市",
+				"area": [
+					"德城区",
+					"乐陵市",
+					"禹城市",
+					"陵县",
+					"宁津县",
+					"齐河县",
+					"武城县",
+					"庆云县",
+					"平原县",
+					"夏津县",
+					"临邑县"
+				]
+			},
+			{
+				"name": "聊城市",
+				"area": [
+					"东昌府区",
+					"临清市",
+					"高唐县",
+					"阳谷县",
+					"茌平县",
+					"莘县",
+					"东阿县",
+					"冠县"
+				]
+			},
+			{
+				"name": "滨州市",
+				"area": [
+					"滨城区",
+					"邹平县",
+					"沾化县",
+					"惠民县",
+					"博兴县",
+					"阳信县",
+					"无棣县"
+				]
+			},
+			{
+				"name": "菏泽市",
+				"area": [
+					"牡丹区",
+					"鄄城县",
+					"单县",
+					"郓城县",
+					"曹县",
+					"定陶县",
+					"巨野县",
+					"东明县",
+					"成武县"
+				]
+			}
+		]
+	},
+	{
+		"name": "河南省",
+		"city": [{
+				"name": "郑州市",
+				"area": [
+					"中原区",
+					"金水区",
+					"二七区",
+					"管城回族区",
+					"上街区",
+					"惠济区",
+					"巩义市",
+					"新郑市",
+					"新密市",
+					"登封市",
+					"荥阳市",
+					"中牟县"
+				]
+			},
+			{
+				"name": "开封市",
+				"area": [
+					"鼓楼区",
+					"龙亭区",
+					"顺河回族区",
+					"禹王台区",
+					"金明区",
+					"开封县",
+					"尉氏县",
+					"兰考县",
+					"杞县",
+					"通许县"
+				]
+			},
+			{
+				"name": "洛阳市",
+				"area": [
+					"西工区",
+					"老城区",
+					"涧西区",
+					"瀍河回族区",
+					"洛龙区",
+					"吉利区",
+					"偃师市",
+					"孟津县",
+					"汝阳县",
+					"伊川县",
+					"洛宁县",
+					"嵩县",
+					"宜阳县",
+					"新安县",
+					"栾川县"
+				]
+			},
+			{
+				"name": "平顶山市",
+				"area": [
+					"新华区",
+					"卫东区",
+					"湛河区",
+					"石龙区",
+					"汝州市",
+					"舞钢市",
+					"宝丰县",
+					"叶县",
+					"郏县",
+					"鲁山县"
+				]
+			},
+			{
+				"name": "安阳市",
+				"area": [
+					"北关区",
+					"文峰区",
+					"殷都区",
+					"龙安区",
+					"林州市",
+					"安阳县",
+					"滑县",
+					"内黄县",
+					"汤阴县"
+				]
+			},
+			{
+				"name": "鹤壁市",
+				"area": [
+					"淇滨区",
+					"山城区",
+					"鹤山区",
+					"浚县",
+					"淇县"
+				]
+			},
+			{
+				"name": "新乡市",
+				"area": [
+					"卫滨区",
+					"红旗区",
+					"凤泉区",
+					"牧野区",
+					"卫辉市",
+					"辉县市",
+					"新乡县",
+					"获嘉县",
+					"原阳县",
+					"长垣县",
+					"封丘县",
+					"延津县"
+				]
+			},
+			{
+				"name": "焦作市",
+				"area": [
+					"解放区",
+					"中站区",
+					"马村区",
+					"山阳区",
+					"沁阳市",
+					"孟州市",
+					"修武县",
+					"温县",
+					"武陟县",
+					"博爱县"
+				]
+			},
+			{
+				"name": "濮阳市",
+				"area": [
+					"华龙区",
+					"濮阳县",
+					"南乐县",
+					"台前县",
+					"清丰县",
+					"范县"
+				]
+			},
+			{
+				"name": "许昌市",
+				"area": [
+					"魏都区",
+					"禹州市",
+					"长葛市",
+					"许昌县",
+					"鄢陵县",
+					"襄城县"
+				]
+			},
+			{
+				"name": "漯河市",
+				"area": [
+					"源汇区",
+					"郾城区",
+					"召陵区",
+					"临颍县",
+					"舞阳县"
+				]
+			},
+			{
+				"name": "三门峡市",
+				"area": [
+					"湖滨区",
+					"义马市",
+					"灵宝市",
+					"渑池县",
+					"卢氏县",
+					"陕县"
+				]
+			},
+			{
+				"name": "南阳市",
+				"area": [
+					"卧龙区",
+					"宛城区",
+					"邓州市",
+					"桐柏县",
+					"方城县",
+					"淅川县",
+					"镇平县",
+					"唐河县",
+					"南召县",
+					"内乡县",
+					"新野县",
+					"社旗县",
+					"西峡县"
+				]
+			},
+			{
+				"name": "商丘市",
+				"area": [
+					"梁园区",
+					"睢阳区",
+					"永城市",
+					"宁陵县",
+					"虞城县",
+					"民权县",
+					"夏邑县",
+					"柘城县",
+					"睢县"
+				]
+			},
+			{
+				"name": "信阳市",
+				"area": [
+					"浉河区",
+					"平桥区",
+					"潢川县",
+					"淮滨县",
+					"息县",
+					"新县",
+					"商城县",
+					"固始县",
+					"罗山县",
+					"光山县"
+				]
+			},
+			{
+				"name": "周口市",
+				"area": [
+					"川汇区",
+					"项城市",
+					"商水县",
+					"淮阳县",
+					"太康县",
+					"鹿邑县",
+					"西华县",
+					"扶沟县",
+					"沈丘县",
+					"郸城县"
+				]
+			},
+			{
+				"name": "驻马店市",
+				"area": [
+					"驿城区",
+					"确山县",
+					"新蔡县",
+					"上蔡县",
+					"西平县",
+					"泌阳县",
+					"平舆县",
+					"汝南县",
+					"遂平县",
+					"正阳县"
+				]
+			},
+			{
+				"name": "焦作市",
+				"area": [
+					"济源市"
+				]
+			}
+		]
+	},
+	{
+		"name": "湖北省",
+		"city": [{
+				"name": "武汉市",
+				"area": [
+					"江岸区",
+					"武昌区",
+					"江汉区",
+					"硚口区",
+					"汉阳区",
+					"青山区",
+					"洪山区",
+					"东西湖区",
+					"汉南区",
+					"蔡甸区",
+					"江夏区",
+					"黄陂区",
+					"新洲区"
+				]
+			},
+			{
+				"name": "黄石市",
+				"area": [
+					"黄石港区",
+					"西塞山区",
+					"下陆区",
+					"铁山区",
+					"大冶市",
+					"阳新县"
+				]
+			},
+			{
+				"name": "十堰市",
+				"area": [
+					"张湾区",
+					"茅箭区",
+					"丹江口市",
+					"郧县",
+					"竹山县",
+					"房县",
+					"郧西县",
+					"竹溪县"
+				]
+			},
+			{
+				"name": "荆州市",
+				"area": [
+					"沙市区",
+					"荆州区",
+					"洪湖市",
+					"石首市",
+					"松滋市",
+					"监利县",
+					"公安县",
+					"江陵县"
+				]
+			},
+			{
+				"name": "宜昌市",
+				"area": [
+					"西陵区",
+					"伍家岗区",
+					"点军区",
+					"猇亭区",
+					"夷陵区",
+					"宜都市",
+					"当阳市",
+					"枝江市",
+					"秭归县",
+					"远安县",
+					"兴山县",
+					"五峰土家族自治县",
+					"长阳土家族自治县"
+				]
+			},
+			{
+				"name": "襄樊市",
+				"area": [
+					"襄城区",
+					"樊城区",
+					"襄阳区",
+					"老河口市",
+					"枣阳市",
+					"宜城市",
+					"南漳县",
+					"谷城县",
+					"保康县"
+				]
+			},
+			{
+				"name": "鄂州市",
+				"area": [
+					"鄂城区",
+					"华容区",
+					"梁子湖区"
+				]
+			},
+			{
+				"name": "荆门市",
+				"area": [
+					"东宝区",
+					"掇刀区",
+					"钟祥市",
+					"京山县",
+					"沙洋县"
+				]
+			},
+			{
+				"name": "孝感市",
+				"area": [
+					"孝南区",
+					"应城市",
+					"安陆市",
+					"汉川市",
+					"云梦县",
+					"大悟县",
+					"孝昌县"
+				]
+			},
+			{
+				"name": "黄冈市",
+				"area": [
+					"黄州区",
+					"麻城市",
+					"武穴市",
+					"红安县",
+					"罗田县",
+					"浠水县",
+					"蕲春县",
+					"黄梅县",
+					"英山县",
+					"团风县"
+				]
+			},
+			{
+				"name": "咸宁市",
+				"area": [
+					"咸安区",
+					"赤壁市",
+					"嘉鱼县",
+					"通山县",
+					"崇阳县",
+					"通城县"
+				]
+			},
+			{
+				"name": "随州市",
+				"area": [
+					"曾都区",
+					"广水市"
+				]
+			},
+			{
+				"name": "恩施土家族苗族自治州",
+				"area": [
+					"恩施市",
+					"利川市",
+					"建始县",
+					"来凤县",
+					"巴东县",
+					"鹤峰县",
+					"宣恩县",
+					"咸丰县"
+				]
+			},
+			{
+				"name": "仙桃市",
+				"area": [
+					"仙桃"
+				]
+			},
+			{
+				"name": "天门市",
+				"area": [
+					"天门"
+				]
+			},
+			{
+				"name": "潜江市",
+				"area": [
+					"潜江"
+				]
+			},
+			{
+				"name": "神农架林区",
+				"area": [
+					"神农架林区"
+				]
+			}
+		]
+	},
+	{
+		"name": "湖南省",
+		"city": [{
+				"name": "长沙市",
+				"area": [
+					"岳麓区",
+					"芙蓉区",
+					"天心区",
+					"开福区",
+					"雨花区",
+					"浏阳市",
+					"长沙县",
+					"望城县",
+					"宁乡县"
+				]
+			},
+			{
+				"name": "株洲市",
+				"area": [
+					"天元区",
+					"荷塘区",
+					"芦淞区",
+					"石峰区",
+					"醴陵市",
+					"株洲县",
+					"炎陵县",
+					"茶陵县",
+					"攸县"
+				]
+			},
+			{
+				"name": "湘潭市",
+				"area": [
+					"岳塘区",
+					"雨湖区",
+					"湘乡市",
+					"韶山市",
+					"湘潭县"
+				]
+			},
+			{
+				"name": "衡阳市",
+				"area": [
+					"雁峰区",
+					"珠晖区",
+					"石鼓区",
+					"蒸湘区",
+					"南岳区",
+					"耒阳市",
+					"常宁市",
+					"衡阳县",
+					"衡东县",
+					"衡山县",
+					"衡南县",
+					"祁东县"
+				]
+			},
+			{
+				"name": "邵阳市",
+				"area": [
+					"双清区",
+					"大祥区",
+					"北塔区",
+					"武冈市",
+					"邵东县",
+					"洞口县",
+					"新邵县",
+					"绥宁县",
+					"新宁县",
+					"邵阳县",
+					"隆回县",
+					"城步苗族自治县"
+				]
+			},
+			{
+				"name": "岳阳市",
+				"area": [
+					"岳阳楼区",
+					"云溪区",
+					"君山区",
+					"临湘市",
+					"汨罗市",
+					"岳阳县",
+					"湘阴县",
+					"平江县",
+					"华容县"
+				]
+			},
+			{
+				"name": "常德市",
+				"area": [
+					"武陵区",
+					"鼎城区",
+					"津市市",
+					"澧县",
+					"临澧县",
+					"桃源县",
+					"汉寿县",
+					"安乡县",
+					"石门县"
+				]
+			},
+			{
+				"name": "张家界市",
+				"area": [
+					"永定区",
+					"武陵源区",
+					"慈利县",
+					"桑植县"
+				]
+			},
+			{
+				"name": "益阳市",
+				"area": [
+					"赫山区",
+					"资阳区",
+					"沅江市",
+					"桃江县",
+					"南县",
+					"安化县"
+				]
+			},
+			{
+				"name": "郴州市",
+				"area": [
+					"北湖区",
+					"苏仙区",
+					"资兴市",
+					"宜章县",
+					"汝城县",
+					"安仁县",
+					"嘉禾县",
+					"临武县",
+					"桂东县",
+					"永兴县",
+					"桂阳县"
+				]
+			},
+			{
+				"name": "永州市",
+				"area": [
+					"冷水滩区",
+					"零陵区",
+					"祁阳县",
+					"蓝山县",
+					"宁远县",
+					"新田县",
+					"东安县",
+					"江永县",
+					"道县",
+					"双牌县",
+					"江华瑶族自治县"
+				]
+			},
+			{
+				"name": "怀化市",
+				"area": [
+					"鹤城区",
+					"洪江市",
+					"会同县",
+					"沅陵县",
+					"辰溪县",
+					"溆浦县",
+					"中方县",
+					"新晃侗族自治县",
+					"芷江侗族自治县",
+					"通道侗族自治县",
+					"靖州苗族侗族自治县",
+					"麻阳苗族自治县"
+				]
+			},
+			{
+				"name": "娄底市",
+				"area": [
+					"娄星区",
+					"冷水江市",
+					"涟源市",
+					"新化县",
+					"双峰县"
+				]
+			},
+			{
+				"name": "湘西土家族苗族自治州",
+				"area": [
+					"吉首市",
+					"古丈县",
+					"龙山县",
+					"永顺县",
+					"凤凰县",
+					"泸溪县",
+					"保靖县",
+					"花垣县"
+				]
+			}
+		]
+	},
+	{
+		"name": "广东省",
+		"city": [{
+				"name": "广州市",
+				"area": [
+					"越秀区",
+					"荔湾区",
+					"海珠区",
+					"天河区",
+					"白云区",
+					"黄埔区",
+					"番禺区",
+					"花都区",
+					"南沙区",
+					"萝岗区",
+					"增城市",
+					"从化市"
+				]
+			},
+			{
+				"name": "深圳市",
+				"area": [
+					"罗湖区",
+					"福田区",
+					"南山区",
+					"宝安区",
+					"龙岗区",
+					"盐田区",
+					"龙华区",
+					"坪山区"
+				]
+			},
+			{
+				"name": "东莞市",
+				"area": [
+					"东城街道",
+					"南城街道",
+					"万江街道",
+					"莞城街道",
+					"石碣镇",
+					"石龙镇",
+					"茶山镇",
+					"石排镇",
+					"企石镇",
+					"横沥镇",
+					"桥头镇",
+					"谢岗镇",
+					"东坑镇",
+					"常平镇",
+					"寮步镇",
+					"樟木头镇",
+					"大朗镇",
+					"黄江镇",
+					"清溪镇",
+					"塘厦镇",
+					"凤岗镇",
+					"大岭山镇",
+					"长安镇",
+					"虎门镇",
+					"厚街镇",
+					"沙田镇",
+					"道滘镇",
+					"洪梅镇",
+					"麻涌镇",
+					"望牛墩镇",
+					"中堂镇",
+					"高埗镇",
+					"松山湖",
+					"东莞港",
+					"东莞生态园"
+				]
+			},
+			{
+				"name": "中山市",
+				"area": [
+					"石岐街道",
+					"东区街道",
+					"中山港街道",
+					"西区街道",
+					"南区街道",
+					"五桂山街道",
+					"小榄镇",
+					"黄圃镇",
+					"民众镇",
+					"东凤镇",
+					"东升镇",
+					"古镇镇",
+					"沙溪镇",
+					"坦洲镇",
+					"港口镇",
+					"三角镇",
+					"横栏镇",
+					"南头镇",
+					"阜沙镇",
+					"南朗镇",
+					"三乡镇",
+					"板芙镇",
+					"大涌镇",
+					"神湾镇"
+				]
+			},
+			{
+				"name": "潮州市",
+				"area": [
+					"市辖区",
+					"湘桥区",
+					"潮安县",
+					"饶平县"
+				]
+			},
+			{
+				"name": "揭阳市",
+				"area": [
+					"榕城区",
+					"揭东县",
+					"揭西县",
+					"惠来县",
+					"普宁市"
+				]
+			},
+			{
+				"name": "云浮市",
+				"area": [
+					"云城区",
+					"新兴县",
+					"郁南县",
+					"云安县",
+					"罗定市"
+				]
+			},
+			{
+				"name": "珠海市",
+				"area": [
+					"香洲区",
+					"斗门区",
+					"金湾区"
+				]
+			},
+			{
+				"name": "汕头市",
+				"area": [
+					"金平区",
+					"濠江区",
+					"龙湖区",
+					"潮阳区",
+					"潮南区",
+					"澄海区",
+					"南澳县"
+				]
+			},
+			{
+				"name": "韶关市",
+				"area": [
+					"浈江区",
+					"武江区",
+					"曲江区",
+					"乐昌市",
+					"南雄市",
+					"始兴县",
+					"仁化县",
+					"翁源县",
+					"新丰县",
+					"乳源瑶族自治县"
+				]
+			},
+			{
+				"name": "佛山市",
+				"area": [
+					"禅城区",
+					"南海区",
+					"顺德区",
+					"三水区",
+					"高明区"
+				]
+			},
+			{
+				"name": "江门市",
+				"area": [
+					"蓬江区",
+					"江海区",
+					"新会区",
+					"恩平市",
+					"台山市",
+					"开平市",
+					"鹤山市"
+				]
+			},
+			{
+				"name": "湛江市",
+				"area": [
+					"赤坎区",
+					"霞山区",
+					"坡头区",
+					"麻章区",
+					"吴川市",
+					"廉江市",
+					"雷州市",
+					"遂溪县",
+					"徐闻县"
+				]
+			},
+			{
+				"name": "茂名市",
+				"area": [
+					"茂南区",
+					"茂港区",
+					"化州市",
+					"信宜市",
+					"高州市",
+					"电白县"
+				]
+			},
+			{
+				"name": "肇庆市",
+				"area": [
+					"端州区",
+					"鼎湖区",
+					"高要市",
+					"四会市",
+					"广宁县",
+					"怀集县",
+					"封开县",
+					"德庆县"
+				]
+			},
+			{
+				"name": "惠州市",
+				"area": [
+					"惠城区",
+					"惠阳区",
+					"博罗县",
+					"惠东县",
+					"龙门县"
+				]
+			},
+			{
+				"name": "梅州市",
+				"area": [
+					"梅江区",
+					"兴宁市",
+					"梅县",
+					"大埔县",
+					"丰顺县",
+					"五华县",
+					"平远县",
+					"蕉岭县"
+				]
+			},
+			{
+				"name": "汕尾市",
+				"area": [
+					"城区",
+					"陆丰市",
+					"海丰县",
+					"陆河县"
+				]
+			},
+			{
+				"name": "河源市",
+				"area": [
+					"源城区",
+					"紫金县",
+					"龙川县",
+					"连平县",
+					"和平县",
+					"东源县"
+				]
+			},
+			{
+				"name": "阳江市",
+				"area": [
+					"江城区",
+					"阳春市",
+					"阳西县",
+					"阳东县"
+				]
+			},
+			{
+				"name": "清远市",
+				"area": [
+					"清城区",
+					"英德市",
+					"连州市",
+					"佛冈县",
+					"阳山县",
+					"清新县",
+					"连山壮族瑶族自治县",
+					"连南瑶族自治县"
+				]
+			}
+		]
+	},
+	{
+		"name": "广西",
+		"city": [{
+				"name": "南宁市",
+				"area": [
+					"青秀区",
+					"兴宁区",
+					"西乡塘区",
+					"良庆区",
+					"江南区",
+					"邕宁区",
+					"武鸣县",
+					"隆安县",
+					"马山县",
+					"上林县",
+					"宾阳县",
+					"横县"
+				]
+			},
+			{
+				"name": "柳州市",
+				"area": [
+					"城中区",
+					"鱼峰区",
+					"柳北区",
+					"柳南区",
+					"柳江县",
+					"柳城县",
+					"鹿寨县",
+					"融安县",
+					"融水苗族自治县",
+					"三江侗族自治县"
+				]
+			},
+			{
+				"name": "桂林市",
+				"area": [
+					"象山区",
+					"秀峰区",
+					"叠彩区",
+					"七星区",
+					"雁山区",
+					"阳朔县",
+					"临桂县",
+					"灵川县",
+					"全州县",
+					"平乐县",
+					"兴安县",
+					"灌阳县",
+					"荔浦县",
+					"资源县",
+					"永福县",
+					"龙胜各族自治县",
+					"恭城瑶族自治县"
+				]
+			},
+			{
+				"name": "梧州市",
+				"area": [
+					"万秀区",
+					"蝶山区",
+					"长洲区",
+					"岑溪市",
+					"苍梧县",
+					"藤县",
+					"蒙山县"
+				]
+			},
+			{
+				"name": "北海市",
+				"area": [
+					"海城区",
+					"银海区",
+					"铁山港区",
+					"合浦县"
+				]
+			},
+			{
+				"name": "防城港市",
+				"area": [
+					"港口区",
+					"防城区",
+					"东兴市",
+					"上思县"
+				]
+			},
+			{
+				"name": "钦州市",
+				"area": [
+					"钦南区",
+					"钦北区",
+					"灵山县",
+					"浦北县"
+				]
+			},
+			{
+				"name": "贵港市",
+				"area": [
+					"港北区",
+					"港南区",
+					"覃塘区",
+					"桂平市",
+					"平南县"
+				]
+			},
+			{
+				"name": "玉林市",
+				"area": [
+					"玉州区",
+					"北流市",
+					"容县",
+					"陆川县",
+					"博白县",
+					"兴业县"
+				]
+			},
+			{
+				"name": "百色市",
+				"area": [
+					"右江区",
+					"凌云县",
+					"平果县",
+					"西林县",
+					"乐业县",
+					"德保县",
+					"田林县",
+					"田阳县",
+					"靖西县",
+					"田东县",
+					"那坡县",
+					"隆林各族自治县"
+				]
+			},
+			{
+				"name": "贺州市",
+				"area": [
+					"八步区",
+					"钟山县",
+					"昭平县",
+					"富川瑶族自治县"
+				]
+			},
+			{
+				"name": "河池市",
+				"area": [
+					"金城江区",
+					"宜州市",
+					"天峨县",
+					"凤山县",
+					"南丹县",
+					"东兰县",
+					"都安瑶族自治县",
+					"罗城仫佬族自治县",
+					"巴马瑶族自治县",
+					"环江毛南族自治县",
+					"大化瑶族自治县"
+				]
+			},
+			{
+				"name": "来宾市",
+				"area": [
+					"兴宾区",
+					"合山市",
+					"象州县",
+					"武宣县",
+					"忻城县",
+					"金秀瑶族自治县"
+				]
+			},
+			{
+				"name": "崇左市",
+				"area": [
+					"江州区",
+					"凭祥市",
+					"宁明县",
+					"扶绥县",
+					"龙州县",
+					"大新县",
+					"天等县"
+				]
+			}
+		]
+	},
+	{
+		"name": "海南省",
+		"city": [{
+				"name": "海口市",
+				"area": [
+					"龙华区",
+					"秀英区",
+					"琼山区",
+					"美兰区"
+				]
+			},
+			{
+				"name": "三亚市",
+				"area": [
+					"市辖区",
+					"海棠区",
+					"吉阳区",
+					"天涯区",
+					"崖州区"
+				]
+			},
+			{
+				"name": "五指山市",
+				"area": [
+					"五指山"
+				]
+			},
+			{
+				"name": "琼海市",
+				"area": [
+					"琼海"
+				]
+			},
+			{
+				"name": "儋州市",
+				"area": [
+					"儋州"
+				]
+			},
+			{
+				"name": "文昌市",
+				"area": [
+					"文昌"
+				]
+			},
+			{
+				"name": "万宁市",
+				"area": [
+					"万宁"
+				]
+			},
+			{
+				"name": "东方市",
+				"area": [
+					"东方"
+				]
+			},
+			{
+				"name": "澄迈县",
+				"area": [
+					"澄迈县"
+				]
+			},
+			{
+				"name": "定安县",
+				"area": [
+					"定安县"
+				]
+			},
+			{
+				"name": "屯昌县",
+				"area": [
+					"屯昌县"
+				]
+			},
+			{
+				"name": "临高县",
+				"area": [
+					"临高县"
+				]
+			},
+			{
+				"name": "白沙黎族自治县",
+				"area": [
+					"白沙黎族自治县"
+				]
+			},
+			{
+				"name": "昌江黎族自治县",
+				"area": [
+					"昌江黎族自治县"
+				]
+			},
+			{
+				"name": "乐东黎族自治县",
+				"area": [
+					"乐东黎族自治县"
+				]
+			},
+			{
+				"name": "陵水黎族自治县",
+				"area": [
+					"陵水黎族自治县"
+				]
+			},
+			{
+				"name": "保亭黎族苗族自治县",
+				"area": [
+					"保亭黎族苗族自治县"
+				]
+			},
+			{
+				"name": "琼中黎族苗族自治县",
+				"area": [
+					"琼中黎族苗族自治县"
+				]
+			}
+		]
+	},
+	{
+		"name": "重庆市",
+		"city": [{
+			"name": "重庆市",
+			"area": [
+				"渝中区",
+				"大渡口区",
+				"江北区",
+				"南岸区",
+				"北碚区",
+				"渝北区",
+				"巴南区",
+				"长寿区",
+				"双桥区",
+				"沙坪坝区",
+				"万盛区",
+				"万州区",
+				"涪陵区",
+				"黔江区",
+				"永川区",
+				"合川区",
+				"江津区",
+				"九龙坡区",
+				"南川区",
+				"綦江县",
+				"潼南县",
+				"荣昌县",
+				"璧山县",
+				"大足县",
+				"铜梁县",
+				"梁平县",
+				"开县",
+				"忠县",
+				"城口县",
+				"垫江县",
+				"武隆县",
+				"丰都县",
+				"奉节县",
+				"云阳县",
+				"巫溪县",
+				"巫山县",
+				"石柱土家族自治县",
+				"秀山土家族苗族自治县",
+				"酉阳土家族苗族自治县",
+				"彭水苗族土家族自治县"
+			]
+		}]
+	},
+	{
+		"name": "四川省",
+		"city": [{
+				"name": "成都市",
+				"area": [
+					"青羊区",
+					"锦江区",
+					"金牛区",
+					"武侯区",
+					"成华区",
+					"龙泉驿区",
+					"青白江区",
+					"新都区",
+					"温江区",
+					"都江堰市",
+					"彭州市",
+					"邛崃市",
+					"崇州市",
+					"金堂县",
+					"郫县",
+					"新津县",
+					"双流县",
+					"蒲江县",
+					"大邑县"
+				]
+			},
+			{
+				"name": "自贡市",
+				"area": [
+					"大安区",
+					"自流井区",
+					"贡井区",
+					"沿滩区",
+					"荣县",
+					"富顺县"
+				]
+			},
+			{
+				"name": "攀枝花市",
+				"area": [
+					"仁和区",
+					"米易县",
+					"盐边县",
+					"东区",
+					"西区"
+				]
+			},
+			{
+				"name": "泸州市",
+				"area": [
+					"江阳区",
+					"纳溪区",
+					"龙马潭区",
+					"泸县",
+					"合江县",
+					"叙永县",
+					"古蔺县"
+				]
+			},
+			{
+				"name": "德阳市",
+				"area": [
+					"旌阳区",
+					"广汉市",
+					"什邡市",
+					"绵竹市",
+					"罗江县",
+					"中江县"
+				]
+			},
+			{
+				"name": "绵阳市",
+				"area": [
+					"涪城区",
+					"游仙区",
+					"江油市",
+					"盐亭县",
+					"三台县",
+					"平武县",
+					"安县",
+					"梓潼县",
+					"北川羌族自治县"
+				]
+			},
+			{
+				"name": "广元市",
+				"area": [
+					"元坝区",
+					"朝天区",
+					"青川县",
+					"旺苍县",
+					"剑阁县",
+					"苍溪县",
+					"市中区"
+				]
+			},
+			{
+				"name": "遂宁市",
+				"area": [
+					"船山区",
+					"安居区",
+					"射洪县",
+					"蓬溪县",
+					"大英县"
+				]
+			},
+			{
+				"name": "内江市",
+				"area": [
+					"市中区",
+					"东兴区",
+					"资中县",
+					"隆昌县",
+					"威远县"
+				]
+			},
+			{
+				"name": "乐山市",
+				"area": [
+					"市中区",
+					"五通桥区",
+					"沙湾区",
+					"金口河区",
+					"峨眉山市",
+					"夹江县",
+					"井研县",
+					"犍为县",
+					"沐川县",
+					"马边彝族自治县",
+					"峨边彝族自治县"
+				]
+			},
+			{
+				"name": "南充",
+				"area": [
+					"顺庆区",
+					"高坪区",
+					"嘉陵区",
+					"阆中市",
+					"营山县",
+					"蓬安县",
+					"仪陇县",
+					"南部县",
+					"西充县"
+				]
+			},
+			{
+				"name": "眉山市",
+				"area": [
+					"东坡区",
+					"仁寿县",
+					"彭山县",
+					"洪雅县",
+					"丹棱县",
+					"青神县"
+				]
+			},
+			{
+				"name": "宜宾市",
+				"area": [
+					"翠屏区",
+					"宜宾县",
+					"兴文县",
+					"南溪县",
+					"珙县",
+					"长宁县",
+					"高县",
+					"江安县",
+					"筠连县",
+					"屏山县"
+				]
+			},
+			{
+				"name": "广安市",
+				"area": [
+					"广安区",
+					"华蓥市",
+					"岳池县",
+					"邻水县",
+					"武胜县"
+				]
+			},
+			{
+				"name": "达州市",
+				"area": [
+					"通川区",
+					"万源市",
+					"达县",
+					"渠县",
+					"宣汉县",
+					"开江县",
+					"大竹县"
+				]
+			},
+			{
+				"name": "雅安市",
+				"area": [
+					"雨城区",
+					"芦山县",
+					"石棉县",
+					"名山县",
+					"天全县",
+					"荥经县",
+					"宝兴县",
+					"汉源县"
+				]
+			},
+			{
+				"name": "巴中市",
+				"area": [
+					"巴州区",
+					"南江县",
+					"平昌县",
+					"通江县"
+				]
+			},
+			{
+				"name": "资阳市",
+				"area": [
+					"雁江区",
+					"简阳市",
+					"安岳县",
+					"乐至县"
+				]
+			},
+			{
+				"name": "阿坝藏族羌族自治州",
+				"area": [
+					"马尔康县",
+					"九寨沟县",
+					"红原县",
+					"汶川县",
+					"阿坝县",
+					"理县",
+					"若尔盖县",
+					"小金县",
+					"黑水县",
+					"金川县",
+					"松潘县",
+					"壤塘县",
+					"茂县"
+				]
+			},
+			{
+				"name": "甘孜藏族自治州",
+				"area": [
+					"康定县",
+					"丹巴县",
+					"炉霍县",
+					"九龙县",
+					"甘孜县",
+					"雅江县",
+					"新龙县",
+					"道孚县",
+					"白玉县",
+					"理塘县",
+					"德格县",
+					"乡城县",
+					"石渠县",
+					"稻城县",
+					"色达县",
+					"巴塘县",
+					"泸定县",
+					"得荣县"
+				]
+			},
+			{
+				"name": "凉山彝族自治州",
+				"area": [
+					"西昌市",
+					"美姑县",
+					"昭觉县",
+					"金阳县",
+					"甘洛县",
+					"布拖县",
+					"雷波县",
+					"普格县",
+					"宁南县",
+					"喜德县",
+					"会东县",
+					"越西县",
+					"会理县",
+					"盐源县",
+					"德昌县",
+					"冕宁县",
+					"木里藏族自治县"
+				]
+			}
+		]
+	},
+	{
+		"name": "贵州省",
+		"city": [{
+				"name": "贵阳市",
+				"area": [
+					"南明区",
+					"云岩区",
+					"花溪区",
+					"乌当区",
+					"白云区",
+					"小河区",
+					"清镇市",
+					"开阳县",
+					"修文县",
+					"息烽县"
+				]
+			},
+			{
+				"name": "六盘水市",
+				"area": [
+					"钟山区",
+					"水城县",
+					"盘县",
+					"六枝特区"
+				]
+			},
+			{
+				"name": "遵义市",
+				"area": [
+					"红花岗区",
+					"汇川区",
+					"赤水市",
+					"仁怀市",
+					"遵义县",
+					"绥阳县",
+					"桐梓县",
+					"习水县",
+					"凤冈县",
+					"正安县",
+					"余庆县",
+					"湄潭县",
+					"道真仡佬族苗族自治县",
+					"务川仡佬族苗族自治县"
+				]
+			},
+			{
+				"name": "安顺市",
+				"area": [
+					"西秀区",
+					"普定县",
+					"平坝县",
+					"镇宁布依族苗族自治县",
+					"紫云苗族布依族自治县",
+					"关岭布依族苗族自治县"
+				]
+			},
+			{
+				"name": "铜仁地区",
+				"area": [
+					"铜仁市",
+					"德江县",
+					"江口县",
+					"思南县",
+					"石阡县",
+					"玉屏侗族自治县",
+					"松桃苗族自治县",
+					"印江土家族苗族自治县",
+					"沿河土家族自治县",
+					"万山特区"
+				]
+			},
+			{
+				"name": "毕节地区",
+				"area": [
+					"毕节市",
+					"黔西县",
+					"大方县",
+					"织金县",
+					"金沙县",
+					"赫章县",
+					"纳雍县",
+					"威宁彝族回族苗族自治县"
+				]
+			},
+			{
+				"name": "黔西南布依族苗族自治州",
+				"area": [
+					"兴义市",
+					"望谟县",
+					"兴仁县",
+					"普安县",
+					"册亨县",
+					"晴隆县",
+					"贞丰县",
+					"安龙县"
+				]
+			},
+			{
+				"name": "黔东南苗族侗族自治州",
+				"area": [
+					"凯里市",
+					"施秉县",
+					"从江县",
+					"锦屏县",
+					"镇远县",
+					"麻江县",
+					"台江县",
+					"天柱县",
+					"黄平县",
+					"榕江县",
+					"剑河县",
+					"三穗县",
+					"雷山县",
+					"黎平县",
+					"岑巩县",
+					"丹寨县"
+				]
+			},
+			{
+				"name": "黔南布依族苗族自治州",
+				"area": [
+					"都匀市",
+					"福泉市",
+					"贵定县",
+					"惠水县",
+					"罗甸县",
+					"瓮安县",
+					"荔波县",
+					"龙里县",
+					"平塘县",
+					"长顺县",
+					"独山县",
+					"三都水族自治县"
+				]
+			}
+		]
+	},
+	{
+		"name": "云南省",
+		"city": [{
+				"name": "昆明市",
+				"area": [
+					"盘龙区",
+					"五华区",
+					"官渡区",
+					"西山区",
+					"东川区",
+					"安宁市",
+					"呈贡县",
+					"晋宁县",
+					"富民县",
+					"宜良县",
+					"嵩明县",
+					"石林彝族自治县",
+					"禄劝彝族苗族自治县",
+					"寻甸回族彝族自治县"
+				]
+			},
+			{
+				"name": "曲靖市",
+				"area": [
+					"麒麟区",
+					"宣威市",
+					"马龙县",
+					"沾益县",
+					"富源县",
+					"罗平县",
+					"师宗县",
+					"陆良县",
+					"会泽县"
+				]
+			},
+			{
+				"name": "玉溪市",
+				"area": [
+					"红塔区",
+					"江川县",
+					"澄江县",
+					"通海县",
+					"华宁县",
+					"易门县",
+					"峨山彝族自治县",
+					"新平彝族傣族自治县",
+					"元江哈尼族彝族傣族自治县"
+				]
+			},
+			{
+				"name": "保山市",
+				"area": [
+					"隆阳区",
+					"施甸县",
+					"腾冲县",
+					"龙陵县",
+					"昌宁县"
+				]
+			},
+			{
+				"name": "昭通市",
+				"area": [
+					"昭阳区",
+					"鲁甸县",
+					"巧家县",
+					"盐津县",
+					"大关县",
+					"永善县",
+					"绥江县",
+					"镇雄县",
+					"彝良县",
+					"威信县",
+					"水富县"
+				]
+			},
+			{
+				"name": "丽江市",
+				"area": [
+					"古城区",
+					"永胜县",
+					"华坪县",
+					"玉龙纳西族自治县",
+					"宁蒗彝族自治县"
+				]
+			},
+			{
+				"name": "普洱市",
+				"area": [
+					"思茅区",
+					"普洱哈尼族彝族自治县",
+					"墨江哈尼族自治县",
+					"景东彝族自治县",
+					"景谷傣族彝族自治县",
+					"镇沅彝族哈尼族拉祜族自治县",
+					"江城哈尼族彝族自治县",
+					"孟连傣族拉祜族佤族自治县",
+					"澜沧拉祜族自治县",
+					"西盟佤族自治县"
+				]
+			},
+			{
+				"name": "临沧市",
+				"area": [
+					"临翔区",
+					"凤庆县",
+					"云县",
+					"永德县",
+					"镇康县",
+					"双江拉祜族佤族布朗族傣族自治县",
+					"耿马傣族佤族自治县",
+					"沧源佤族自治县"
+				]
+			},
+			{
+				"name": "德宏傣族景颇族自治州",
+				"area": [
+					"潞西市",
+					"瑞丽市",
+					"梁河县",
+					"盈江县",
+					"陇川县"
+				]
+			},
+			{
+				"name": "怒江傈僳族自治州",
+				"area": [
+					"泸水县",
+					"福贡县",
+					"贡山独龙族怒族自治县",
+					"兰坪白族普米族自治县"
+				]
+			},
+			{
+				"name": "迪庆藏族自治州",
+				"area": [
+					"香格里拉县",
+					"德钦县",
+					"维西傈僳族自治县"
+				]
+			},
+			{
+				"name": "大理白族自治州",
+				"area": [
+					"大理市",
+					"祥云县",
+					"宾川县",
+					"弥渡县",
+					"永平县",
+					"云龙县",
+					"洱源县",
+					"剑川县",
+					"鹤庆县",
+					"漾濞彝族自治县",
+					"南涧彝族自治县",
+					"巍山彝族回族自治县"
+				]
+			},
+			{
+				"name": "楚雄彝族自治州",
+				"area": [
+					"楚雄市",
+					"双柏县",
+					"牟定县",
+					"南华县",
+					"姚安县",
+					"大姚县",
+					"永仁县",
+					"元谋县",
+					"武定县",
+					"禄丰县"
+				]
+			},
+			{
+				"name": "红河哈尼族彝族自治州",
+				"area": [
+					"蒙自县",
+					"个旧市",
+					"开远市",
+					"绿春县",
+					"建水县",
+					"石屏县",
+					"弥勒县",
+					"泸西县",
+					"元阳县",
+					"红河县",
+					"金平苗族瑶族傣族自治县",
+					"河口瑶族自治县",
+					"屏边苗族自治县"
+				]
+			},
+			{
+				"name": "文山壮族苗族自治州",
+				"area": [
+					"文山县",
+					"砚山县",
+					"西畴县",
+					"麻栗坡县",
+					"马关县",
+					"丘北县",
+					"广南县",
+					"富宁县"
+				]
+			},
+			{
+				"name": "西双版纳傣族自治州",
+				"area": [
+					"景洪市",
+					"勐海县",
+					"勐腊县"
+				]
+			}
+		]
+	},
+	{
+		"name": "西藏",
+		"city": [{
+				"name": "拉萨市",
+				"area": [
+					"城关区",
+					"林周县",
+					"当雄县",
+					"尼木县",
+					"曲水县",
+					"堆龙德庆县",
+					"达孜县",
+					"墨竹工卡县"
+				]
+			},
+			{
+				"name": "那曲地区",
+				"area": [
+					"那曲县",
+					"嘉黎县",
+					"比如县",
+					"聂荣县",
+					"安多县",
+					"申扎县",
+					"索县",
+					"班戈县",
+					"巴青县",
+					"尼玛县"
+				]
+			},
+			{
+				"name": "昌都地区",
+				"area": [
+					"昌都县",
+					"江达县",
+					"贡觉县",
+					"类乌齐县",
+					"丁青县",
+					"察雅县",
+					"八宿县",
+					"左贡县",
+					"芒康县",
+					"洛隆县",
+					"边坝县"
+				]
+			},
+			{
+				"name": "林芝地区",
+				"area": [
+					"林芝县",
+					"工布江达县",
+					"米林县",
+					"墨脱县",
+					"波密县",
+					"察隅县",
+					"朗县"
+				]
+			},
+			{
+				"name": "山南地区",
+				"area": [
+					"乃东县",
+					"扎囊县",
+					"贡嘎县",
+					"桑日县",
+					"琼结县",
+					"曲松县",
+					"措美县",
+					"洛扎县",
+					"加查县",
+					"隆子县",
+					"错那县",
+					"浪卡子县"
+				]
+			},
+			{
+				"name": "日喀则地区",
+				"area": [
+					"日喀则市",
+					"南木林县",
+					"江孜县",
+					"定日县",
+					"萨迦县",
+					"拉孜县",
+					"昂仁县",
+					"谢通门县",
+					"白朗县",
+					"仁布县",
+					"康马县",
+					"定结县",
+					"仲巴县",
+					"亚东县",
+					"吉隆县",
+					"聂拉木县",
+					"萨嘎县",
+					"岗巴县"
+				]
+			},
+			{
+				"name": "阿里地区",
+				"area": [
+					"噶尔县",
+					"普兰县",
+					"札达县",
+					"日土县",
+					"革吉县",
+					"改则县",
+					"措勤县"
+				]
+			}
+		]
+	},
+	{
+		"name": "陕西省",
+		"city": [{
+				"name": "西安市",
+				"area": [
+					"莲湖区",
+					"新城区",
+					"碑林区",
+					"雁塔区",
+					"灞桥区",
+					"未央区",
+					"阎良区",
+					"临潼区",
+					"长安区",
+					"高陵县",
+					"蓝田县",
+					"户县",
+					"周至县"
+				]
+			},
+			{
+				"name": "铜川市",
+				"area": [
+					"耀州区",
+					"王益区",
+					"印台区",
+					"宜君县"
+				]
+			},
+			{
+				"name": "宝鸡市",
+				"area": [
+					"渭滨区",
+					"金台区",
+					"陈仓区",
+					"岐山县",
+					"凤翔县",
+					"陇县",
+					"太白县",
+					"麟游县",
+					"扶风县",
+					"千阳县",
+					"眉县",
+					"凤县"
+				]
+			},
+			{
+				"name": "咸阳市",
+				"area": [
+					"秦都区",
+					"渭城区",
+					"杨陵区",
+					"兴平市",
+					"礼泉县",
+					"泾阳县",
+					"永寿县",
+					"三原县",
+					"彬县",
+					"旬邑县",
+					"长武县",
+					"乾县",
+					"武功县",
+					"淳化县"
+				]
+			},
+			{
+				"name": "渭南市",
+				"area": [
+					"临渭区",
+					"韩城市",
+					"华阴市",
+					"蒲城县",
+					"潼关县",
+					"白水县",
+					"澄城县",
+					"华县",
+					"合阳县",
+					"富平县",
+					"大荔县"
+				]
+			},
+			{
+				"name": "延安市",
+				"area": [
+					"宝塔区",
+					"安塞县",
+					"洛川县",
+					"子长县",
+					"黄陵县",
+					"延川县",
+					"富县",
+					"延长县",
+					"甘泉县",
+					"宜川县",
+					"志丹县",
+					"黄龙县",
+					"吴起县"
+				]
+			},
+			{
+				"name": "汉中市",
+				"area": [
+					"汉台区",
+					"留坝县",
+					"镇巴县",
+					"城固县",
+					"南郑县",
+					"洋县",
+					"宁强县",
+					"佛坪县",
+					"勉县",
+					"西乡县",
+					"略阳县"
+				]
+			},
+			{
+				"name": "榆林市",
+				"area": [
+					"榆阳区",
+					"清涧县",
+					"绥德县",
+					"神木县",
+					"佳县",
+					"府谷县",
+					"子洲县",
+					"靖边县",
+					"横山县",
+					"米脂县",
+					"吴堡县",
+					"定边县"
+				]
+			},
+			{
+				"name": "安康市",
+				"area": [
+					"汉滨区",
+					"紫阳县",
+					"岚皋县",
+					"旬阳县",
+					"镇坪县",
+					"平利县",
+					"石泉县",
+					"宁陕县",
+					"白河县",
+					"汉阴县"
+				]
+			},
+			{
+				"name": "商洛市",
+				"area": [
+					"商州区",
+					"镇安县",
+					"山阳县",
+					"洛南县",
+					"商南县",
+					"丹凤县",
+					"柞水县"
+				]
+			}
+		]
+	},
+	{
+		"name": "甘肃省",
+		"city": [{
+				"name": "兰州市",
+				"area": [
+					"城关区",
+					"七里河区",
+					"西固区",
+					"安宁区",
+					"红古区",
+					"永登县",
+					"皋兰县",
+					"榆中县"
+				]
+			},
+			{
+				"name": "嘉峪关市",
+				"area": [
+					"嘉峪关市"
+				]
+			},
+			{
+				"name": "金昌市",
+				"area": [
+					"金川区",
+					"永昌县"
+				]
+			},
+			{
+				"name": "白银市",
+				"area": [
+					"白银区",
+					"平川区",
+					"靖远县",
+					"会宁县",
+					"景泰县"
+				]
+			},
+			{
+				"name": "天水市",
+				"area": [
+					"清水县",
+					"秦安县",
+					"甘谷县",
+					"武山县",
+					"张家川回族自治县",
+					"北道区",
+					"秦城区"
+				]
+			},
+			{
+				"name": "武威市",
+				"area": [
+					"凉州区",
+					"民勤县",
+					"古浪县",
+					"天祝藏族自治县"
+				]
+			},
+			{
+				"name": "酒泉市",
+				"area": [
+					"肃州区",
+					"玉门市",
+					"敦煌市",
+					"金塔县",
+					"肃北蒙古族自治县",
+					"阿克塞哈萨克族自治县",
+					"安西县"
+				]
+			},
+			{
+				"name": "张掖市",
+				"area": [
+					"甘州区",
+					"民乐县",
+					"临泽县",
+					"高台县",
+					"山丹县",
+					"肃南裕固族自治县"
+				]
+			},
+			{
+				"name": "庆阳市",
+				"area": [
+					"西峰区",
+					"庆城县",
+					"环县",
+					"华池县",
+					"合水县",
+					"正宁县",
+					"宁县",
+					"镇原县"
+				]
+			},
+			{
+				"name": "平凉市",
+				"area": [
+					"崆峒区",
+					"泾川县",
+					"灵台县",
+					"崇信县",
+					"华亭县",
+					"庄浪县",
+					"静宁县"
+				]
+			},
+			{
+				"name": "定西市",
+				"area": [
+					"安定区",
+					"通渭县",
+					"临洮县",
+					"漳县",
+					"岷县",
+					"渭源县",
+					"陇西县"
+				]
+			},
+			{
+				"name": "陇南市",
+				"area": [
+					"武都区",
+					"成县",
+					"宕昌县",
+					"康县",
+					"文县",
+					"西和县",
+					"礼县",
+					"两当县",
+					"徽县"
+				]
+			},
+			{
+				"name": "临夏回族自治州",
+				"area": [
+					"临夏市",
+					"临夏县",
+					"康乐县",
+					"永靖县",
+					"广河县",
+					"和政县",
+					"东乡族自治县",
+					"积石山保安族东乡族撒拉族自治县"
+				]
+			},
+			{
+				"name": "甘南藏族自治州",
+				"area": [
+					"合作市",
+					"临潭县",
+					"卓尼县",
+					"舟曲县",
+					"迭部县",
+					"玛曲县",
+					"碌曲县",
+					"夏河县"
+				]
+			}
+		]
+	},
+	{
+		"name": "青海省",
+		"city": [{
+				"name": "西宁市",
+				"area": [
+					"城中区",
+					"城东区",
+					"城西区",
+					"城北区",
+					"湟源县",
+					"湟中县",
+					"大通回族土族自治县"
+				]
+			},
+			{
+				"name": "海东地区",
+				"area": [
+					"平安县",
+					"乐都县",
+					"民和回族土族自治县",
+					"互助土族自治县",
+					"化隆回族自治县",
+					"循化撒拉族自治县"
+				]
+			},
+			{
+				"name": "海北藏族自治州",
+				"area": [
+					"海晏县",
+					"祁连县",
+					"刚察县",
+					"门源回族自治县"
+				]
+			},
+			{
+				"name": "海南藏族自治州",
+				"area": [
+					"共和县",
+					"同德县",
+					"贵德县",
+					"兴海县",
+					"贵南县"
+				]
+			},
+			{
+				"name": "黄南藏族自治州",
+				"area": [
+					"同仁县",
+					"尖扎县",
+					"泽库县",
+					"河南蒙古族自治县"
+				]
+			},
+			{
+				"name": "果洛藏族自治州",
+				"area": [
+					"玛沁县",
+					"班玛县",
+					"甘德县",
+					"达日县",
+					"久治县",
+					"玛多县"
+				]
+			},
+			{
+				"name": "玉树藏族自治州",
+				"area": [
+					"玉树县",
+					"杂多县",
+					"称多县",
+					"治多县",
+					"囊谦县",
+					"曲麻莱县"
+				]
+			},
+			{
+				"name": "海西蒙古族藏族自治州",
+				"area": [
+					"德令哈市",
+					"格尔木市",
+					"乌兰县",
+					"都兰县",
+					"天峻县"
+				]
+			}
+		]
+	},
+	{
+		"name": "宁夏",
+		"city": [{
+				"name": "银川市",
+				"area": [
+					"兴庆区",
+					"西夏区",
+					"金凤区",
+					"灵武市",
+					"永宁县",
+					"贺兰县"
+				]
+			},
+			{
+				"name": "石嘴山市",
+				"area": [
+					"大武口区",
+					"惠农区",
+					"平罗县"
+				]
+			},
+			{
+				"name": "吴忠市",
+				"area": [
+					"利通区",
+					"青铜峡市",
+					"盐池县",
+					"同心县"
+				]
+			},
+			{
+				"name": "固原市",
+				"area": [
+					"原州区",
+					"西吉县",
+					"隆德县",
+					"泾源县",
+					"彭阳县"
+				]
+			},
+			{
+				"name": "中卫市",
+				"area": [
+					"沙坡头区",
+					"中宁县",
+					"海原县"
+				]
+			}
+		]
+	},
+	{
+		"name": "新疆",
+		"city": [{
+				"name": "乌鲁木齐市",
+				"area": [
+					"天山区",
+					"沙依巴克区",
+					"新市区",
+					"水磨沟区",
+					"头屯河区",
+					"达坂城区",
+					"东山区",
+					"乌鲁木齐县"
+				]
+			},
+			{
+				"name": "克拉玛依市",
+				"area": [
+					"克拉玛依区",
+					"独山子区",
+					"白碱滩区",
+					"乌尔禾区"
+				]
+			},
+			{
+				"name": "吐鲁番地区",
+				"area": [
+					"吐鲁番市",
+					"托克逊县",
+					"鄯善县"
+				]
+			},
+			{
+				"name": "哈密地区",
+				"area": [
+					"哈密市",
+					"伊吾县",
+					"巴里坤哈萨克自治县"
+				]
+			},
+			{
+				"name": "和田地区",
+				"area": [
+					"和田市",
+					"和田县",
+					"洛浦县",
+					"民丰县",
+					"皮山县",
+					"策勒县",
+					"于田县",
+					"墨玉县"
+				]
+			},
+			{
+				"name": "阿克苏地区",
+				"area": [
+					"阿克苏市",
+					"温宿县",
+					"沙雅县",
+					"拜城县",
+					"阿瓦提县",
+					"库车县",
+					"柯坪县",
+					"新和县",
+					"乌什县"
+				]
+			},
+			{
+				"name": "喀什地区",
+				"area": [
+					"喀什市",
+					"巴楚县",
+					"泽普县",
+					"伽师县",
+					"叶城县",
+					"岳普湖县",
+					"疏勒县",
+					"麦盖提县",
+					"英吉沙县",
+					"莎车县",
+					"疏附县",
+					"塔什库尔干塔吉克自治县"
+				]
+			},
+			{
+				"name": "克孜勒苏柯尔克孜自治州",
+				"area": [
+					"阿图什市",
+					"阿合奇县",
+					"乌恰县",
+					"阿克陶县"
+				]
+			},
+			{
+				"name": "巴音郭楞蒙古自治州",
+				"area": [
+					"库尔勒市",
+					"和静县",
+					"尉犁县",
+					"和硕县",
+					"且末县",
+					"博湖县",
+					"轮台县",
+					"若羌县",
+					"焉耆回族自治县"
+				]
+			},
+			{
+				"name": "昌吉回族自治州",
+				"area": [
+					"昌吉市",
+					"阜康市",
+					"奇台县",
+					"玛纳斯县",
+					"吉木萨尔县",
+					"呼图壁县",
+					"木垒哈萨克自治县",
+					"米泉市"
+				]
+			},
+			{
+				"name": "博尔塔拉蒙古自治州",
+				"area": [
+					"博乐市",
+					"精河县",
+					"温泉县"
+				]
+			},
+			{
+				"name": "石河子",
+				"area": [
+					"石河子"
+				]
+			},
+			{
+				"name": "阿拉尔",
+				"area": [
+					"阿拉尔"
+				]
+			},
+			{
+				"name": "图木舒克",
+				"area": [
+					"图木舒克"
+				]
+			},
+			{
+				"name": "五家渠",
+				"area": [
+					"五家渠"
+				]
+			},
+			{
+				"name": "伊犁哈萨克自治州",
+				"area": [
+					"伊宁市",
+					"奎屯市",
+					"伊宁县",
+					"特克斯县",
+					"尼勒克县",
+					"昭苏县",
+					"新源县",
+					"霍城县",
+					"巩留县",
+					"察布查尔锡伯自治县",
+					"塔城地区",
+					"阿勒泰地区"
+				]
+			}
+		]
+	},
+	{
+		"name": "台湾省",
+		"city": [{
+				"name": "台北市",
+				"area": [
+					"内湖区",
+					"南港区",
+					"中正区",
+					"万华区",
+					"大同区",
+					"中山区",
+					"松山区",
+					"大安区",
+					"信义区",
+					"文山区",
+					"士林区",
+					"北投区"
+				]
+			},
+			{
+				"name": "新北市",
+				"area": [
+					"板桥区",
+					"汐止区",
+					"新店区"
+				]
+			},
+			{
+				"name": "桃园市",
+				"area": [
+					"其他"
+				]
+			},
+			{
+				"name": "台中市",
+				"area": [
+					"其他"
+				]
+			},
+			{
+				"name": "台南市",
+				"area": [
+					"其他"
+				]
+			},
+			{
+				"name": "高雄市",
+				"area": [
+					"其他"
+				]
+			}
+		]
+	},
+	{
+		"name": "澳门",
+		"city": [{
+			"name": "澳门",
+			"area": [
+				"花地玛堂区",
+				"圣安多尼堂区",
+				"大堂区",
+				"望德堂区",
+				"风顺堂区",
+				"嘉模堂区",
+				"圣方济各堂区",
+				"路凼"
+			]
+		}]
+	},
+	{
+		"name": "香港",
+		"city": [{
+			"name": "香港",
+			"area": [
+				"深水埗区",
+				"油尖旺区",
+				"九龙城区",
+				"黄大仙区",
+				"观塘区",
+				"北区",
+				"大埔区",
+				"沙田区",
+				"西贡区",
+				"元朗区",
+				"屯门区",
+				"荃湾区",
+				"葵青区",
+				"离岛区",
+				"中西区",
+				"湾仔区",
+				"东区",
+				"南区"
+			]
+		}]
+	}
+]

+ 103 - 0
my/components/wangding-pickerAddress/wangding-pickerAddress.vue

@@ -0,0 +1,103 @@
+<template>
+	<picker @change="bindPickerChange" @columnchange="columnchange" :range="array" range-key="name" :value="value" mode="multiSelector">
+		<slot></slot>
+	</picker>
+</template>
+
+<script>
+	import AllAddress from './data.js'
+	let selectVal = ['','','']
+	
+	export default {
+		data() {
+			return{
+				value: [0,0,0],
+				array: [],
+				index: 0
+			}
+		},
+		created() {
+			this.initSelect()
+		},
+		methods:{
+			// 初始化地址选项
+			initSelect() {
+				this.updateSourceDate() // 更新源数据
+				.updateAddressDate() // 更新结果数据
+				//.$forceUpdate()  // 触发双向绑定
+			},
+			// 地址控件改变控件
+			columnchange(d) {
+				this.updateSelectIndex(d.detail.column, d.detail.value) // 更新选择索引
+				.updateSourceDate() // 更新源数据
+				.updateAddressDate() // 更新结果数据
+				//.$forceUpdate()  // 触发双向绑定
+			},
+			
+			/**
+			 * 更新源数据
+			 * */
+			updateSourceDate() {
+				this.array = []
+				this.array[0] = AllAddress.map(obj => {
+					return {
+						name: obj.name
+					}
+				})
+				this.array[1] = AllAddress[this.value[0]].city.map(obj => {
+					return {
+						name: obj.name
+					}
+				})
+				this.array[2] = AllAddress[this.value[0]].city[this.value[1]].area.map(obj => { 
+					return {
+						name: obj
+					}
+				})
+				return this
+			},
+			
+			/**
+			 * 更新索引
+			 * */
+			updateSelectIndex(column, value){
+				let arr = JSON.parse(JSON.stringify(this.value)) 
+				arr[column] = value
+				if(column === 0 ) {
+					arr[1] = 0
+					arr[2] = 0
+				}
+				if(column === 1 ) {
+					arr[2] = 0
+				}
+				this.value = arr
+				return this
+			},
+			
+			/**
+			 * 更新结果数据 
+			 * */
+			updateAddressDate() {
+				selectVal[0] = this.array[0][this.value[0]].name
+				selectVal[1] = this.array[1][this.value[1]].name 
+				selectVal[2] = this.array[2][this.value[2]].name 
+				return this
+			},
+			
+			/**
+			 * 点击确定
+			 * */
+			bindPickerChange(e) {
+				this.$emit('change', {
+					index: this.value,
+					data: selectVal
+				})
+				return this
+			}
+			
+		}
+	}
+</script>
+
+<style>
+</style>

File diff suppressed because it is too large
+ 36 - 0
my/components/watch-login/css/icon.css


+ 108 - 0
my/components/watch-login/watch-button.vue

@@ -0,0 +1,108 @@
+<template>
+	<view>
+		<!-- 按钮 -->
+		<button :class="['buttonBorder',!_rotate?'dlbutton':'dlbutton_loading']" :style="{'background':bgColor, 'color': fontColor}">
+			<view :class="_rotate?'rotate_loop':''">
+				<text v-if="_rotate" class="cuIcon cuIcon-loading1 "></text>
+				<text v-if="!_rotate">{{ text }}</text>
+			</view>
+		</button>
+	</view>
+</template>
+
+<script>
+	export default{
+		props:{
+			text: String, //显示文本
+			rotate:{
+				//是否启动加载
+				type: [Boolean,String],
+				default: false,
+			}, 
+			bgColor:{
+				//按钮背景颜色
+				type: String,
+				// default: "linear-gradient(to right, rgba(0,0,0,0.7), rgba(0,0,0,0.6))",
+			},
+			fontColor:{
+				//按钮字体颜色
+				type: String,
+				default: "#FFFFFF",
+			},
+		},
+		computed:{
+			_rotate() {
+				//处理值
+				return String(this.rotate) !== 'false'
+			},
+		}
+	}
+</script>
+
+<style>
+	@import url("./css/icon.css");
+	
+	.dlbutton {
+		color: #FFFFFF;
+		font-size: 30upx;
+		width:601upx;
+		height:90upx;
+		background: #557EFD;
+		box-shadow:0upx 0upx 13upx 0upx rgba(164,217,228,0.4);
+		border-radius:2.5rem;
+		line-height: 90upx;
+		text-align: center;
+		margin-left: auto;
+		margin-right: auto;
+		margin-top: 96upx;
+	}
+	.dlbutton_loading {
+		color: #FFFFFF;
+		font-size: 30upx;
+		width:100upx;
+		height:100upx;
+		background: #557EFD;
+		box-shadow:0upx 0upx 13upx 0upx rgba(164,217,228,0.4);
+		border-radius:2.5rem;
+		line-height: 100upx;
+		text-align: center;
+		margin-left: auto;
+		margin-right: auto;
+		margin-top: 96upx;
+	}
+	.buttonBorder{
+	    border: none ;
+	    border-radius: 2.5rem ;
+	    -webkit-box-shadow: 0 0 60upx 0 rgba(0,0,0,.2) ;
+	    box-shadow: 0 0 60upx 0 rgba(0,0,0,.2) ;
+	    -webkit-transition: all 0.4s cubic-bezier(.57,.19,.51,.95);
+	    -moz-transition: all 0.4s cubic-bezier(.57,.19,.51,.95);
+	    -ms-transition: all 0.4s cubic-bezier(.57,.19,.51,.95);
+	    -o-transition: all 0.4s cubic-bezier(.57,.19,.51,.95);
+	    transition: all 0.4s cubic-bezier(.57,.19,.51,.95);
+	}
+	
+	/* 旋转动画 */
+	.rotate_loop{
+		-webkit-transition-property: -webkit-transform;
+	    -webkit-transition-duration: 1s;
+	    -moz-transition-property: -moz-transform;
+	    -moz-transition-duration: 1s;
+	    -webkit-animation: rotate 1s linear infinite;
+	    -moz-animation: rotate 1s linear infinite;
+	    -o-animation: rotate 1s linear infinite;
+	    animation: rotate 1s linear infinite;
+	}
+	@-webkit-keyframes rotate{from{-webkit-transform: rotate(0deg)}
+	    to{-webkit-transform: rotate(360deg)}
+	}
+	@-moz-keyframes rotate{from{-moz-transform: rotate(0deg)}
+	    to{-moz-transform: rotate(359deg)}
+	}
+	@-o-keyframes rotate{from{-o-transform: rotate(0deg)}
+	    to{-o-transform: rotate(359deg)}
+	}
+	@keyframes rotate{from{transform: rotate(0deg)}
+	    to{transform: rotate(359deg)}
+	}
+</style>

+ 223 - 0
my/components/watch-login/watch-input.vue

@@ -0,0 +1,223 @@
+<template>
+	<view class="main-list oBorder">
+		<!-- 文本框 -->
+		<input
+			class="main-input"
+			:value="value"
+			:type="_type"
+			:maxlength="maxlength"
+			:placeholder="placeholder"
+			:password="type==='password'&&!showPassword"
+			@input="onInput"
+		/>
+		<!-- 是否可见密码 -->
+		<image
+			v-if="_isShowPass&&type==='password'&&!_isShowCode"
+			class="img cuIcon"
+			:class="showPassword?'cuIcon-attention':'cuIcon-attentionforbid'"
+			@tap="showPass"
+		></image>
+		<!-- 倒计时 -->
+		<view
+				v-if="_isShowCode&&!_isShowPass"
+				:class="['vercode',{'vercode-run': second>0}]"
+				@click="setCode"
+		>{{ getVerCodeSecond }}</view>
+
+
+		<view
+				v-if="_isShowGet"
+				class="vercode"
+				@click="setNumberCode"
+		>官方邀请码</view>
+
+	</view>
+</template>
+
+<script>
+	var _this, countDown;
+	export default{
+		data(){
+			return{
+				showPassword: false, //是否显示明文
+				second: 0, //倒计时
+				isRunCode: false, //是否开始倒计时
+			}
+		},
+		props:{
+			type: String, //类型
+			value: String, //值
+			placeholder: String, //框内提示
+			maxlength: {
+				//最大长度
+				type: [Number,String],
+				default: 20,
+			},
+			isShowPass:{
+				//是否显示密码图标(二选一)
+				type: [Boolean,String],
+				default: false,
+			},
+			isShowCode:{
+				//是否显示获取验证码(二选一)
+				type: [Boolean,String],
+				default: false,
+			},
+			isShowGet:{
+				//是否显示获取验证码(二选一)
+				type: [Boolean,String],
+				default: false,
+			},
+			codeText:{
+				type: String,
+				default: "获取验证码",
+			},
+			setTime:{
+				//倒计时时间设置
+				type: [Number,String],
+				default: 60,
+			}
+		},
+		model: {
+			prop: 'value',
+			event: 'input'
+		},
+		mounted() {
+			_this=this
+			//准备触发
+			this.$on('runCode',(val)=>{
+                this.runCode(val);
+            });
+			clearInterval(countDown);//先清理一次循环,避免缓存
+		},
+		methods:{
+			showPass(){
+				//是否显示密码
+				this.showPassword = !this.showPassword
+			},
+			onInput(e) {
+				//传出值
+				this.$emit('input', e.target.value)
+			},
+			setCode(){
+				//设置获取验证码的事件
+				if(this.isRunCode){
+					//判断是否开始倒计时,避免重复点击
+					return false;
+				}
+				this.$emit('setCode')
+			},
+			setNumberCode(){
+				this.$emit('setNumberCode')
+			},
+			runCode(val){
+				//开始倒计时
+				if(String(val)=="0"){
+
+					//判断是否需要终止循环
+					this.second = 0; //初始倒计时
+					clearInterval(countDown);//清理循环
+					this.isRunCode= false; //关闭循环状态
+					return false;
+				}
+				if(this.isRunCode){
+					//判断是否开始倒计时,避免重复点击
+					return false;
+				}
+				this.isRunCode= true
+				this.second = this._setTime //倒数秒数
+
+				let _this=this;
+				countDown = setInterval(function(){
+					_this.second--
+					if(_this.second==0){
+						_this.isRunCode= false
+						clearInterval(countDown)
+					}
+				},1000)
+			}
+
+
+		},
+		computed:{
+			_type(){
+				//处理值
+				const type = this.type
+				return type == 'password' ? 'text' : type
+			},
+			_isShowPass() {
+				//处理值
+				return String(this.isShowPass) !== 'false'
+			},
+			_isShowCode() {
+				//处理值
+				return String(this.isShowCode) !== 'false'
+			},
+			_isShowGet() {
+				//处理值
+				return String(this.isShowGet) !== 'false'
+			},
+			_setTime() {
+				//处理值
+				const setTime = Number(this.setTime)
+				return setTime>0 ? setTime : 60
+			},
+			getVerCodeSecond(){
+				//验证码倒计时计算
+				if(this.second<=0){
+					return this.codeText;
+				}else{
+					if(this.second<10){
+						return '0'+this.second;
+					}else{
+						return this.second;
+					}
+				}
+
+			}
+		}
+	}
+</script>
+
+<style>
+	@import url("./css/icon.css");
+
+	.main-list{
+		display: flex;
+		flex-direction: row;
+		justify-content: space-between;
+		align-items: center;
+		height: 90upx;   /* Input 高度 */
+		color: #333333;
+		padding: 32upx;
+		margin-top:18upx;
+		margin-bottom: 18upx;
+	}
+	.img{
+		width: 32upx;
+		height: 32upx;
+		font-size: 32upx;
+	}
+	.main-input{
+		flex: 1;
+		text-align: left;
+		font-size: 28upx;
+		/* line-height: 100upx; */
+		padding-right: 10upx;
+		margin-left: 20upx;
+	}
+	.vercode {
+		color: rgba(0,0,0,0.7);
+		font-size: 24upx;
+		line-height: 100upx;
+	}
+	.vercode-run {
+		color: rgba(0,0,0,0.4) !important;
+	}
+	.oBorder{
+	    border: none;
+	    border-radius: 2.5rem ;
+	    -webkit-box-shadow: 0 0 60upx 0 rgba(43,86,112,.1) ;
+	    box-shadow: 0 0 60upx 0 rgba(43,86,112,.1) ;
+	}
+</style>

+ 394 - 0
my/enterpriseInfo/enterpriseInfo.vue

@@ -0,0 +1,394 @@
+<template>
+	<view class="content">
+		<!-- 企业信息 -->
+		<view class="info flex justify-center">
+			<view class="info-box flex align-center justify-between">
+				<image :src="info.companyLogo?info.companyLogo:'../../static/logo.png'"
+					style="width: 130rpx;height: 130rpx;border-radius: 24rpx;" mode="">
+				</image>
+				<view class="info-box-r">
+					<view class="info-box-r-title" v-show="info.companyName">
+						{{info.companyName}}
+					</view>
+					<view class="info-box-r-label flex align-center flex-wrap">
+						<view class="info-box-r-label-item" v-show="info.companyScope">
+							{{info.companyScope}}
+						</view>
+						<view class="info-box-r-label-item" v-if="info.companyPeople">
+							{{info.companyPeople}}
+						</view>
+					</view>
+				</view>
+			</view>
+		</view>
+		<!-- 企业介绍 -->
+		<view class="remarks flex justify-center">
+			<view class="remarks-box">
+				<view class="remarks-box-title">
+					企业介绍
+				</view>
+				<view class="remarks-box-con">
+					<u-read-more show-height="100" :shadow-style="shadowStyle" color="#FD6416" text-indent="0"
+						closeText="展开">
+						<rich-text :nodes="info.companyDetails"
+							style="color: #ffffff;font-size: 28rpx;font-weight: 500;">
+						</rich-text>
+					</u-read-more>
+				</view>
+			</view>
+		</view>
+		<!-- 公司地址 -->
+		<view class="address flex justify-center">
+			<view class="address-box flex justify-center">
+				<view class="address-box-c">
+					<view class="address-box-c-title">
+						公司地址
+					</view>
+					<view @click="openMap()" class="address-box-c-add flex justify-between align-center">
+						<view class="address-box-c-add-l flex align-center">
+							<u-icon name="map" style="margin-right: 20rpx;" color="#00B88F" size="30"></u-icon>
+							{{info.province?info.province:''}}{{info.city?info.city:''}}{{info.district?info.district:''}}
+							{{info.companyAddress?info.companyAddress:''}}
+						</view>
+						<view class="address-box-c-add-r">
+							<u-icon name="arrow-right" color="#FEFEFE" size="30"></u-icon>
+						</view>
+					</view>
+				</view>
+			</view>
+		</view>
+		<!-- 工商信息 -->
+		<view class="gsInfo flex justify-center">
+			<view class="gsInfo-box flex justify-center">
+				<view class="gsInfo-box-c">
+					<view class="gsInfo-box-c-title">
+						工商信息
+					</view>
+					<view class="gsInfo-box-c-item">
+						公司全称:{{info.companyName?info.companyName:''}}
+					</view>
+					<view class="gsInfo-box-c-item">
+						成立时间:{{info.companyCreateTime?info.companyCreateTime:''}}
+					</view>
+					<view class="gsInfo-box-c-item">
+						注册资本:{{info.companyRegisteredFund?info.companyRegisteredFund:''}}万人民币
+					</view>
+					<view class="gsInfo-box-c-item">
+						法人代表:{{info.companyLegalPerson?info.companyLegalPerson:'未知'}}
+					</view>
+				</view>
+			</view>
+		</view>
+
+		<!-- 全部岗位弹窗 -->
+		<btnPopous v-if="companyId" :companyId="companyId" :cittArr="cittArr" :classify="classify" :moneyArr="moneyArr"
+			:jyArr="jyArr" />
+	</view>
+</template>
+
+<script>
+	import btnPopous from '../../components/btnPopous/btnPopous.vue'
+	export default {
+		components: {
+			btnPopous
+		},
+		data() {
+			return {
+				backStyle: {
+					color: '#ffffff'
+				},
+				shadowStyle: {
+					backgroundImage: 'none',
+				},
+				companyId: '',
+				info: {},
+				cittArr: [], //企业发布岗位的城市数组
+				moneyArr: [], //薪资列表数组
+				jyArr: [], //工作经验列表数组
+				classify: [], //岗位名称数组
+			};
+		},
+		//分享
+		onShareAppMessage(res) {
+			return {
+				path: '/my/enterpriseInfo/enterpriseInfo?companyId=' + this.companyId + '&invitation=' + uni
+					.getStorageSync(
+						'invitationCode'),
+				title: this.info.companyName,
+			}
+		},
+		/*
+		 * uniapp微信小程序分享页面到微信朋友圈
+		 */
+		onShareTimeline(res) {
+			return {
+				path: '/my/enterpriseInfo/enterpriseInfo?companyId=' + this.companyId + '&invitation=' + uni
+					.getStorageSync(
+						'invitationCode'),
+				title: this.info.companyName,
+			}
+		},
+		// onPageScroll(e) {
+		// 	if (e.scrollTop > 100) {
+		// 		uni.setNavigationBarTitle({
+		// 			title: '西安省钱兄网络科技有限公司'
+		// 		})
+		// 	} else {
+		// 		uni.setNavigationBarTitle({
+		// 			title: '企业详情'
+		// 		})
+		// 	}
+		// },
+		onLoad(option) {
+			// 获取邀请码保存到本地
+			if (option.invitation) {
+				this.$queue.setData('inviterCode', option.invitation);
+			}
+			// #ifdef MP-WEIXIN
+			if (option.scene) {
+				const scene = decodeURIComponent(option.scene);
+				this.$queue.setData('inviterCode', scene.split(',')[0]);
+			}
+			// #endif
+			uni.showLoading({
+				title: '加载中'
+			})
+			this.companyId = option.companyId
+			this.getInfo();
+			this.getCityCompanyId();
+			this.getMoney();
+			this.getJy();
+			this.getClassify();
+		},
+		methods: {
+			openMap() {
+				let that = this
+				if (that.info.companyLat && that.info.companyLng) {
+					uni.openLocation({
+						latitude: that.info.companyLat,
+						longitude: that.info.companyLng,
+						address: that.info.province + '' + that.info.city + '' + that.info.district + '' + that
+							.info
+							.companyAddress,
+						name: that.info.companyAddress
+					})
+				} else {
+					uni.showToast({
+						title: '暂无位置信息',
+						icon: 'none'
+					})
+				}
+
+
+			},
+			/**
+			 * 岗位分类数组
+			 */
+			getClassify() {
+				this.$Request.getT("/app/postPush/getCompanyClassify", {
+					companyId: this.companyId
+				}).then(res => {
+					if (res.code == 0) {
+						this.classify = res.data //岗位分类列表数组
+					}
+				})
+			},
+			/**
+			 * 获取薪资 列表
+			 */
+			getMoney() {
+				this.$Request.get("/app/dict/list", {
+					type: '薪资 '
+				}).then(res => {
+					if (res.code == 0) {
+						this.moneyArr = res.data //薪资列表数组
+					}
+				})
+			},
+			/**
+			 * 获取工作经验列表
+			 */
+			getJy() {
+				this.$Request.get("/app/dict/list", {
+					type: '工作经验 '
+				}).then(res => {
+					if (res.code == 0) {
+						this.jyArr = res.data //工作经验列表数组
+					}
+				})
+			},
+			/**
+			 * 根据公司id查询已发布岗位的市
+			 */
+			getCityCompanyId() {
+				let data = {
+					companyId: this.companyId
+				}
+				this.$Request.getT("/app/postPush/getCityCompanyId", data).then(res => {
+					if (res.code == 0) {
+						this.cittArr = res.data
+					}
+				})
+			},
+			//获取企业详情
+			getInfo() {
+				let that = this
+				this.$Request.get("/app/company/selectCompanyByCompanyId", {
+					companyId: this.companyId
+				}).then(res => {
+					if (res.code == 0) {
+						this.info = res.data
+						if (this.info.companyName) {
+							uni.setNavigationBarTitle({
+								title: that.info.companyName
+							})
+						}
+						// let reg = /.+?(省|市|自治区|自治州|县|区)/g
+						// let cityarr = this.info.companyAddress.match(reg)
+						// if (cityarr && cityarr != null) {
+						// 	if (cityarr.length == 3) {
+						// 		this.info.companyAddress = cityarr[0] + '' + cityarr[1] + '' + cityarr[2]
+						// 	} else {
+						// 		this.info.companyAddress = cityarr[0] + '' + cityarr[1]
+						// 	}
+						// }
+					}
+					uni.hideLoading()
+				})
+			},
+		}
+	}
+</script>
+
+<style lang="scss">
+	page {
+		background-color: #4B4D5C;
+		// overflow: hidden;
+	}
+
+	.content {
+		padding-bottom: 320rpx;
+	}
+
+	.info {
+		width: 100%;
+		height: 120rpx;
+		margin-top: 50rpx;
+
+		.info-box {
+			width: 686rpx;
+			height: 100%;
+
+			.info-box-r {
+				width: calc(100% - 150rpx);
+				margin-left: 20rpx;
+			}
+
+			.info-box-r-title {
+				color: #FFFFFF;
+				font-size: 38rpx;
+				font-weight: 800;
+				margin-top: 10rpx;
+			}
+
+			.info-box-r-label {
+				color: #FFFFFF;
+				font-size: 28rpx;
+				font-weight: 500;
+				margin-top: 20rpx;
+
+				.info-box-r-label-item {
+					margin-right: 20rpx;
+					margin-bottom: 20rpx;
+				}
+			}
+		}
+	}
+
+	.remarks {
+		width: 100%;
+		margin-top: 60rpx;
+
+		.remarks-box {
+			width: 686rpx;
+
+			.remarks-box-title {
+				color: #FFFFFF;
+				font-size: 32rpx;
+				font-weight: 800;
+			}
+
+			.remarks-box-con {
+				margin-top: 30rpx;
+			}
+		}
+	}
+
+	.address {
+		width: 100%;
+		height: 190rpx;
+		margin-top: 50rpx;
+
+		.address-box {
+			width: 686rpx;
+			height: 100%;
+			background-color: #464855;
+			border-radius: 24rpx;
+
+			.address-box-c {
+				width: 626rpx;
+				height: 100%;
+			}
+
+			.address-box-c-title {
+				color: #FFFFFF;
+				font-size: 32rpx;
+				font-weight: 800;
+				margin-top: 40rpx;
+			}
+
+			.address-box-c-add {
+				width: 100%;
+				height: 40rpx;
+				margin-top: 30rpx;
+
+				.address-box-c-add-l {
+					color: #FEFEFE;
+				}
+			}
+		}
+	}
+
+	.gsInfo {
+		width: 100%;
+		margin-top: 20rpx;
+
+		.gsInfo-box {
+			width: 686rpx;
+			height: 100%;
+			background-color: #464855;
+			border-radius: 24rpx;
+
+			.gsInfo-box-c {
+				width: 626rpx;
+				height: 100%;
+				padding-top: 30rpx;
+				padding-bottom: 30rpx;
+			}
+
+			.gsInfo-box-c-title {
+				color: #FFFFFF;
+				font-size: 32rpx;
+				font-weight: 800;
+
+			}
+
+			.gsInfo-box-c-item {
+				margin-top: 30rpx;
+				color: #FEFEFE;
+				font-size: 28rpx;
+				font-weight: 500;
+			}
+		}
+	}
+</style>

+ 189 - 0
my/feedback/index.vue

@@ -0,0 +1,189 @@
+<template>
+	<view class="page" style="background-color: #ffffff;">
+		<view class="feedback-title">
+			<text>问题和意见</text>
+			<text @tap="chooseMsg">快速键入</text>
+		</view>
+		<view class="feedback-body"><textarea placeholder="请详细描述你的问题和意见..." v-model="sendDate.content"
+				class="feedback-textare" /></view>
+		<view class="feedback-title"><text>联系方式</text></view>
+		<view class="feedback-body"><input class="feedback-input" v-model="sendDate.contact" placeholder="方便我们联系你 " />
+		</view>
+
+		<button style="" class="feedback-submit" @tap="send">提交</button>
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				msgContents: ['界面显示错乱', '启动缓慢,卡出翔了', 'UI无法直视,丑哭了', '偶发性崩溃'],
+				stars: [1, 2, 3, 4, 5],
+				imageList: [],
+				sendDate: {
+					score: 5,
+					content: '',
+					contact: ''
+				}
+			};
+		},
+		onLoad() {
+			let deviceInfo = {
+				appid: plus.runtime.appid,
+				imei: plus.device.imei, //设备标识
+				p: plus.os.name === 'Android' ? 'a' : 'i', //平台类型,i表示iOS平台,a表示Android平台。
+				md: plus.device.model, //设备型号
+				app_version: plus.runtime.version,
+				plus_version: plus.runtime.innerVersion, //基座版本号
+				os: plus.os.version,
+				net: '' + plus.networkinfo.getCurrentType()
+			};
+			this.sendDate = Object.assign(deviceInfo, this.sendDate);
+		},
+		methods: {
+			close(e) {
+				this.imageList.splice(e, 1);
+			},
+			chooseMsg() {
+				//快速输入
+				uni.showActionSheet({
+					itemList: this.msgContents,
+					success: res => {
+						this.sendDate.content = this.msgContents[res.tapIndex];
+					}
+				});
+			},
+			chooseImg() {
+				//选择图片
+				uni.chooseImage({
+					sourceType: ['camera', 'album'],
+					sizeType: 'compressed',
+					count: 8 - this.imageList.length,
+					success: res => {
+						this.imageList = this.imageList.concat(res.tempFilePaths);
+					}
+				});
+			},
+			chooseStar(e) {
+				//点击评星
+				this.sendDate.score = e;
+			},
+			previewImage() {
+				//预览图片
+				uni.previewImage({
+					urls: this.imageList
+				});
+			},
+			send() {
+				//发送反馈
+				console.log(JSON.stringify(this.sendDate));
+
+				if (!this.sendDate.content) {
+					uni.showToast({
+						icon: 'none',
+						title: '请输入反馈内容'
+					});
+					return;
+				}
+				if (!this.sendDate.contact) {
+					uni.showToast({
+						icon: 'none',
+						title: '请填写QQ或邮箱'
+					});
+					return;
+				}
+				this.$queue.showLoading('加载中...');
+				this.$Request.postJson('/app/message/insertMessage', {
+					title: this.sendDate.contact,
+					content: JSON.stringify(this.sendDate),
+					state: 2
+				}).then(res => {
+					if (res.code === 0) {
+						uni.showToast({
+							title: '投诉成功'
+						});
+						setTimeout(function() {
+							uni.navigateBack();
+						}, 1000);
+					} else {
+						uni.hideLoading();
+						uni.showModal({
+							showCancel: false,
+							title: '投诉失败',
+							content: res.msg
+						});
+					}
+				});
+			}
+		}
+	};
+</script>
+
+<style>
+	@font-face {
+		font-family: uniicons;
+		font-weight: normal;
+		font-style: normal;
+		src: url('https://img-cdn-qiniu.dcloud.net.cn/fonts/uni.ttf') format('truetype');
+	}
+
+	page {
+		background-color: #F5F5F5 !important;
+	}
+
+	view {
+		font-size: 28upx;
+	}
+
+
+	/*问题反馈*/
+	.feedback-title {
+		display: flex;
+		flex-direction: row;
+		justify-content: space-between;
+		align-items: center;
+		padding: 20upx;
+		color: #8f8f94;
+		font-size: 28upx;
+	}
+
+	.feedback-star-view.feedback-title {
+		justify-content: flex-start;
+		margin: 0;
+	}
+
+	.feedback-body {
+		font-size: 32upx;
+		padding: 16upx;
+		margin: 16upx;
+		border-radius: 16upx;
+		background: #FFFFFF;
+		/* color: #FFF; */
+	}
+
+	.feedback-textare {
+		height: 200upx;
+		font-size: 34upx;
+		line-height: 50upx;
+		width: 100%;
+		box-sizing: border-box;
+		padding: 20upx 30upx 0;
+
+	}
+
+	.feedback-input {
+		font-size: 32upx;
+		height: 60upx;
+		padding: 15upx 20upx;
+		line-height: 60upx;
+	}
+
+
+	.feedback-submit {
+		background: #00B88F;
+		color: #ffffff;
+		margin: 20upx;
+		margin-top: 32upx;
+	}
+</style>

+ 280 - 0
my/feedback/jubao.vue

@@ -0,0 +1,280 @@
+<template>
+	<view v-if="XCXIsSelect == '是'" class="page" style="background-color: #ffffff;">
+		<view class="feedback-title">
+			<text>问题和意见</text>
+			<text @tap="chooseMsg">快速键入</text>
+		</view>
+		<view class="feedback-body"><textarea placeholder="请详细描述你的问题和意见..." v-model="sendDate.content"
+				class="feedback-textare" /></view>
+		<view class="feedback-title"><text>电话/微信</text></view>
+		<view class="feedback-body"><input class="feedback-input" v-model="sendDate.contact" placeholder="方便我们联系你 " />
+		</view>
+		<view class="feedback-title"><text>上传图片</text></view>
+		<view class="imgs flex align-center justify-between flex-wrap">
+			<!-- imgs -->
+			<view class="imgs-item" v-for="(item,index) in imgs" :key="index">
+				<image :src="item" mode="aspectFill"></image>
+				<view class="imgs-item-close" @click="close(index)">
+					<u-icon name="close-circle" color="red" size="38"></u-icon>
+				</view>
+			</view>
+			<view class="imgs-item" @click="uploadImg">
+				<u-icon name="photo" color="#00B88F" size="58"></u-icon>
+				<view class="imgs-item-txt">
+					上传图片
+				</view>
+			</view>
+			<view class="imgs-item" style="height: 0;">
+			</view>
+		</view>
+		<button style="" class="feedback-submit" @tap="send">提交</button>
+	</view>
+	<view class="" v-else>
+		帮助中心,联系我们,为你提供帮助
+	</view>
+</template>
+
+<script>
+	import config from '../../common/config';
+	export default {
+		data() {
+			return {
+				msgContents: ['岗位不符', '虚假岗位', '内容违规', '虚假招聘'],
+				stars: [1, 2, 3, 4, 5],
+				imageList: [],
+				sendDate: {
+					score: 5,
+					content: '',
+					contact: ''
+				},
+				postPushId: '',
+				XCXIsSelect: '是',
+				imgs: [],
+				resumesId: '',
+			};
+		},
+		onLoad(e) {
+			this.XCXIsSelect = uni.getStorageSync('XCXIsSelect')
+			if (e.resumesId) {
+				this.resumesId = e.resumesId
+			}
+			if (e.postPushId) {
+				this.postPushId = e.postPushId
+			}
+			// #ifdef APP
+			let deviceInfo = {
+				appid: plus.runtime.appid,
+				imei: plus.device.imei, //设备标识
+				p: plus.os.name === 'Android' ? 'a' : 'i', //平台类型,i表示iOS平台,a表示Android平台。
+				md: plus.device.model, //设备型号
+				app_version: plus.runtime.version,
+				plus_version: plus.runtime.innerVersion, //基座版本号
+				os: plus.os.version,
+				net: '' + plus.networkinfo.getCurrentType()
+			};
+			this.sendDate = Object.assign(deviceInfo, this.sendDate);
+			// #endif
+
+		},
+		methods: {
+			//上传图片
+			uploadImg() {
+				let that = this
+				uni.chooseImage({
+					count: 1, //默认9
+					sizeType: ['original', 'compressed'], //可以指定是原图还是压缩图,默认二者都有
+					sourceType: ['album'], //从相册选择
+					success: function(res) {
+						uni.showLoading({
+							title: '上传中...'
+						});
+						uni.uploadFile({
+							url: 'https://zp.xianmaxiong.com/sqx_fast/alioss/upload',
+							// url: config.APIHOST + '/alioss/upload',
+							filePath: res.tempFilePaths[0],
+							name: 'file',
+							success: uploadFileRes => {
+								uni.hideLoading();
+								let url = JSON.parse(uploadFileRes.data);
+								console.log(url)
+								that.imgs.push(url.data)
+
+							},
+							fail() {
+								uni.hideLoading()
+								uni.showToast({
+									title: '上传失败',
+									icon: 'none'
+								})
+							}
+						});
+					}
+				});
+			},
+			close(e) {
+				this.imgs.splice(e, 1);
+			},
+			chooseMsg() {
+				//快速输入
+				uni.showActionSheet({
+					itemList: this.msgContents,
+					success: res => {
+						this.sendDate.content = this.msgContents[res.tapIndex];
+					}
+				});
+			},
+			chooseStar(e) {
+				//点击评星
+				this.sendDate.score = e;
+			},
+			previewImage() {
+				//预览图片
+				uni.previewImage({
+					urls: this.imageList
+				});
+			},
+			send() {
+				//发送反馈
+				console.log(JSON.stringify(this.sendDate));
+
+				if (!this.sendDate.content) {
+					uni.showToast({
+						icon: 'none',
+						title: '请输入反馈内容'
+					});
+					return;
+				}
+				if (!this.sendDate.contact) {
+					uni.showToast({
+						icon: 'none',
+						title: '请填写电话或微信'
+					});
+					return;
+				}
+				this.$queue.showLoading('加载中...');
+				if (this.imgs.length > 0) {
+					this.sendDate.imgs = this.imgs.join(',')
+				}
+				this.$Request.postJson('/app/message/insertMessage', {
+					title: this.sendDate.contact,
+					content: JSON.stringify(this.sendDate),
+					state: this.postPushId ? 3 : 6, //3:举报岗位 6:举报简历
+					byUserId: this.postPushId ? this.postPushId : this.resumesId
+				}).then(res => {
+					if (res.code === 0) {
+						uni.showToast({
+							title: '提交成功'
+						});
+						setTimeout(function() {
+							uni.navigateBack();
+						}, 1000);
+					} else {
+						uni.hideLoading();
+						uni.showModal({
+							showCancel: false,
+							title: '投诉失败',
+							content: res.msg
+						});
+					}
+				});
+			}
+		}
+	};
+</script>
+
+<style>
+	@font-face {
+		font-family: uniicons;
+		font-weight: normal;
+		font-style: normal;
+		src: url('https://img-cdn-qiniu.dcloud.net.cn/fonts/uni.ttf') format('truetype');
+	}
+
+	page {
+		background-color: #F5F5F5 !important;
+	}
+
+	view {
+		font-size: 28upx;
+	}
+
+
+	/*问题反馈*/
+	.feedback-title {
+		display: flex;
+		flex-direction: row;
+		justify-content: space-between;
+		align-items: center;
+		padding: 20upx;
+		color: #8f8f94;
+		font-size: 28upx;
+	}
+
+	.feedback-star-view.feedback-title {
+		justify-content: flex-start;
+		margin: 0;
+	}
+
+	.feedback-body {
+		font-size: 32upx;
+		padding: 16upx;
+		margin: 16upx;
+		border-radius: 16upx;
+		background: #FFFFFF;
+		/* color: #FFF; */
+	}
+
+	.feedback-textare {
+		height: 200upx;
+		font-size: 34upx;
+		line-height: 50upx;
+		width: 100%;
+		box-sizing: border-box;
+		padding: 20upx 30upx 0;
+
+	}
+
+	.feedback-input {
+		font-size: 32upx;
+		height: 60upx;
+		padding: 15upx 20upx;
+		line-height: 60upx;
+	}
+
+	.imgs {
+		margin: 20rpx;
+
+	}
+
+	.imgs-item {
+		width: 220rpx;
+		height: 220rpx;
+		background-color: #f5f5f5;
+		margin-bottom: 20rpx;
+		border-radius: 10rpx;
+		display: flex;
+		flex-direction: column;
+		align-items: center;
+		justify-content: center;
+		position: relative;
+	}
+
+	.imgs-item-close {
+		position: absolute;
+		top: 0;
+		right: 0;
+	}
+
+	.imgs-item image {
+		width: 100%;
+		height: 100%;
+		border-radius: 10rpx;
+	}
+
+	.feedback-submit {
+		background: #00B88F;
+		color: #ffffff;
+		margin: 20upx;
+		margin-top: 32upx;
+	}
+</style>

+ 274 - 0
my/gird/browse.vue

@@ -0,0 +1,274 @@
+<template>
+	<view class="">
+		<view class="qyList flex justify-center">
+			<view class="qyList-box">
+				<view class="qyList-box-item flex justify-center" v-for="(item,index) in dataList" :key="index"
+					@click="goNav('/pages/index/game/orderDet?resumesId='+item.resumesId)">
+					<view class="qyList-box-item-box">
+						<view class="qyList-box-item-info flex justify-between align-center">
+							<view class="qyList-box-item-info-l">
+								<view class="" style="color: #212121;font-size: 38rpx;font-weight: 800;">
+									{{item.resumesName}}
+								</view>
+								<view class="flex align-center flex-wrap"
+									style="color: #999999;font-size: 26rpx;margin-top: 10rpx;">
+									<text>{{item.resumesAge}}岁</text>
+									<text style="margin-left: 20rpx;margin-right: 20rpx;">|</text>
+									<text>{{item.resumesWorkExperience}}</text>
+									<text style="margin-left: 20rpx;margin-right: 20rpx;">|</text>
+									<text>{{item.school}}</text>
+									<text style="margin-left: 20rpx;margin-right: 20rpx;">|</text>
+									<text>期望{{item.resumesCompensation}}</text>
+								</view>
+							</view>
+							<view class="qyList-box-item-info-r">
+								<image :src="item.avatar?item.avatar:'../../static/logo.png'"
+									style="width: 95rpx;height: 95rpx;border-radius: 50%;" mode=""></image>
+							</view>
+						</view>
+						<view class="qyList-box-item-job flex align-center">
+							<u-icon name="heart-fill" color="#00B88F" size="30" style="margin-right: 16rpx;">
+							</u-icon>
+							期望岗位:{{item.resumesPost}}
+						</view>
+						<view v-if="item.resumesCompanyList.length>0" class="qyList-box-item-job flex align-center">
+							<image src="../../static/images/qi.png"
+								style="width: 30rpx;height: 32rpx;margin-right: 16rpx;" mode=""></image>
+							{{item.resumesCompanyList[0].resumesTitle}} /
+							{{item.resumesCompanyList[0].resumesPost}}
+						</view>
+						<view class="qyList-box-item-rem" v-if="item.resumesDetails">
+							优势:{{item.resumesDetails}}
+						</view>
+					</view>
+				</view>
+
+			</view>
+		</view>
+		<empty v-if="dataList.length==0" />
+		<!-- 筛选悬浮 -->
+		<view class="filterSe" @click="goScreen()">
+			<image src="../../static/images/my/filterSe.png" mode=""></image>
+		</view>
+	</view>
+</template>
+
+<script>
+	import empty from '../../components/empty.vue'
+	export default {
+		components: {
+			empty
+		},
+		data() {
+			return {
+				dataList: [],
+				page: 1,
+				pages: 1,
+				limit: 10,
+				myId: '',
+				isVip: false,
+				filter: {
+					resumesCompensation: '', //薪资
+					resumesEducation: '', //学历
+					resumesWorkExperience: '', //经验
+					industryName: '', //行业
+				}
+			}
+		},
+		onLoad(e) {
+			this.$queue.showLoading("加载中...");
+			this.myId = uni.getStorageSync('userId')
+			this.getBrowseList()
+		},
+		watch: {
+			filter: {
+				handler() {
+					this.page = 1
+					this.getBrowseList()
+				},
+				deep: true,
+				immediate: true
+			}
+		},
+		onUnload() {
+			uni.removeStorageSync('isCompyBrowse')
+		},
+		onShow() {
+			if (uni.getStorageSync('isCompyBrowse') && (uni.getStorageSync('isCompyBrowse')).length > 0) {
+				let isCompyBrowse = uni.getStorageSync('isCompyBrowse')
+				isCompyBrowse.map(item => {
+					let arr = []
+					item.list.map(ite => {
+						if (ite.value != '不限') {
+							arr.push(ite.value)
+						}
+					})
+					switch (item.name) {
+						case '学历':
+							this.filter.resumesEducation = arr.join(',')
+							break;
+						case '薪资':
+							this.filter.resumesCompensation = arr.join(',')
+							break;
+						case '经验':
+							this.filter.resumesWorkExperience = arr.join(',')
+							break;
+						case '行业':
+							this.filter.industryName = arr.join(',')
+							break;
+					}
+
+				})
+			} else {
+				this.filter.resumesEducation = '' //学历
+				this.filter.resumesWorkExperience = '' //经验
+				this.filter.industryName = '' //行业
+				this.filter.resumesCompensation = '' //薪资
+			}
+		},
+		methods: {
+			// 去筛选
+			goScreen() {
+				uni.navigateTo({
+					url: '/package/screen/screen?isCompyBrowse=1'
+				})
+			},
+			goNav(url) {
+				uni.navigateTo({
+					url: url
+				})
+			},
+			// 足迹
+			getBrowseList() {
+				let data = {
+					page: this.page,
+					limit: this.limit,
+					resumesCompensation: this.filter.resumesCompensation, //薪资
+					resumesEducation: this.filter.resumesEducation, //学历
+					resumesWorkExperience: this.filter.resumesWorkExperience, //经验
+					industryName: this.filter.industryName, //行业
+				}
+				this.$Request.get("/app/userBrowse/selectCompayBrowse", data).then(res => {
+					uni.hideLoading();
+					uni.stopPullDownRefresh();
+					if (res.code == 0) {
+						this.pages = res.data.pages
+						if (this.page == 1) {
+							this.dataList = res.data.records
+						} else {
+							this.dataList = [...this.dataList, ...res.data.records]
+						}
+						console.log(this.dataList)
+					} else {
+						console.log(res.msg)
+					}
+
+				})
+			},
+			// 跳转订单
+			goDetail(e) {
+				uni.navigateTo({
+					url: '/pages/index/game/orderDet?resumesId=' + e.resumes.resumesId
+				});
+			},
+			// 删除
+			delData(e) {
+				let that = this
+				uni.showModal({
+					title: '提示',
+					content: '确定删除吗?',
+					confirmColor: '#00B88F',
+					success: function(res) {
+						if (res.confirm) {
+							console.log('用户点击确定');
+							let data = {
+								id: e.id
+							}
+							that.$Request.post("/app/userBrowse/deleteMyBrowse", data).then(res => {
+								if (res.code == 0) {
+									uni.showToast({
+										title: '删除成功!',
+										icon: 'none'
+									})
+									that.getBrowseList()
+								}
+							})
+						} else if (res.cancel) {
+							console.log('用户点击取消');
+						}
+					}
+				})
+			}
+		},
+		onReachBottom: function() {
+			if (this.page < this.pages) {
+				this.page += 1
+				this.getBrowseList()
+			}
+		},
+		onPullDownRefresh: function() {
+			this.page = 1;
+			this.getBrowseList()
+		},
+	}
+</script>
+
+<style lang="scss">
+	page {
+		background-color: #F2F2F7;
+	}
+
+	.filterSe {
+		position: fixed;
+		bottom: 10vh;
+		right: 30rpx;
+
+		image {
+			width: 100rpx;
+			height: 100rpx;
+			// border-radius: 50%;
+		}
+	}
+
+	.qyList {
+		width: 100%;
+		height: auto;
+		margin-top: 20rpx;
+
+		.qyList-box {
+			width: 686rpx;
+			height: 100%;
+
+			.qyList-box-item {
+				width: 100%;
+				// height: 400rpx;
+				padding-bottom: 40rpx;
+				background-color: #ffffff;
+				border-radius: 24rpx;
+				margin-bottom: 20rpx;
+
+				.qyList-box-item-box {
+					width: 626rpx;
+					height: 100%;
+				}
+
+				.qyList-box-item-info {
+					margin-top: 40rpx;
+				}
+
+				.qyList-box-item-job {
+					color: #121212;
+					font-size: 28rpx;
+					font-weight: 500;
+					margin-top: 20rpx;
+				}
+
+				.qyList-box-item-rem {
+					color: #999999;
+					font-size: 26rpx;
+					margin-top: 20rpx;
+				}
+			}
+		}
+	}
+</style>

+ 148 - 0
my/gird/guanzhu.vue

@@ -0,0 +1,148 @@
+<template>
+	<view class="content">
+		<view v-if="dataList.length != 0" class="bg u-flex u-p-l-30 u-p-t-30 u-p-b-10 u-p-r-30" v-for="(item,index) in dataList" :key='index'>
+			<view class="u-m-r-10">
+				<u-avatar :src="item.avatar?item.avatar: '../../static/logo.png'" size="100"></u-avatar>
+			</view>
+			<view class="u-flex-1 text-white margin-left-xs">
+				<view class="u-font-16  text-bold">{{item.userName}}</view>
+				<view class="u-font-14 margin-top-sm u-tips-color" @click="goNav('/pages/me/vip/index')">{{item.updateTime?item.updateTime:''}}</view>
+			</view>
+			<view>
+				<view v-if="item.status == 1" @click="insert(item)" class="round"
+					style="color: white;background: #557EFD;padding: 10upx 24upx;width: 150upx;text-align: center;font-size: 22upx;">
+					互相关注</view>
+				<view v-if="item.status == 2 && type == 1" @click="insert(item)" class="round"
+					style="color: white;background: #557EFD;padding: 10upx 24upx;width: 150upx;text-align: center;font-size: 22upx;">
+					回关</view>
+				<view v-if="item.status == 2 && type == 2" @click="insert(item)" class="round"
+					style="color: white;background: #557EFD;padding: 10upx 24upx;width: 150upx;text-align: center;font-size: 22upx;">
+					已关注</view>
+			</view>
+		</view>
+		
+		<empty v-if="dataList.length == 0" ></empty>
+	</view>
+</template>
+
+<script>
+	import empty from '../../components/empty.vue'
+	export default {
+		components: {
+			empty
+		},
+		data() {
+			return {
+				dataList: [],
+				type: 1,
+				page: 1,
+				limit: 10
+			}
+		},
+		onLoad(e) {
+			console.log(e)
+			this.$queue.showLoading("加载中...");
+			uni.setNavigationBarTitle({
+				title: e.name
+			})
+			this.type = e.type
+			if (this.type == 1) {
+				this.getFansList()
+			} else {
+				this.getFollowList()
+			}
+		},
+		methods: {
+			// 获取粉丝数量
+			getFansList() {
+				let data = {
+					page: this.page,
+					limit: this.limit
+				}
+				this.$Request.get("/app/userFollow/selectFans", data).then(res => {
+					uni.hideLoading();
+					if (res.code == 0) {
+						if(this.page == 1) {
+							this.dataList = res.data.list
+						} else {
+							this.dataList = [...this.dataList, ...res.data.list]
+						}
+					} else {
+						console.log(res.msg)
+					}
+					uni.stopPullDownRefresh();
+				});
+			},
+			// 获取关注数量
+			getFollowList() {
+				let data = {
+					page: this.page,
+					limit: this.limit
+				}
+				this.$Request.get("/app/userFollow/selectMyFollow", data).then(res => {
+					if (res.code == 0) {
+						if(this.page == 1) {
+							this.dataList = res.data.list
+						} else {
+							this.dataList = [...this.dataList, ...res.data.list]
+						}
+					} else {
+						console.log(res.msg)
+					}
+					uni.hideLoading();
+					uni.stopPullDownRefresh();
+				});
+			},
+			insert(e) {
+				let that = this
+				let data = {
+					followUserId: e.userId
+				}
+				that.$Request.get("/app/userFollow/insert", data).then(res => {
+					console.log(res)
+					if (res.code == 0) {
+						uni.showToast({
+							title: res.msg,
+							icon: 'none'
+						})
+						setTimeout(function() {
+							if (that.type == 1) {
+								that.getFansList()
+							} else {
+								that.getFollowList()
+							}
+						}, 500)
+					}
+				});
+			}
+		},
+		onReachBottom: function() {
+			this.page = this.page + 1;
+			if (e.type == 1) {
+				this.getFansList()
+			} else {
+				this.getFollowList()
+			}
+		},
+		onPullDownRefresh: function() {
+			this.page = 1;
+			// this.dataList = []
+			if (this.type == 1) {
+				this.getFansList()
+			} else {
+				this.getFollowList()
+			}
+		},
+	}
+</script>
+
+<style>
+page {
+		background-color: #F7F7F7;
+	}
+
+	.bg {
+		background-color: #FFFFFF;
+	}
+
+</style>

+ 109 - 0
my/gird/visitor.vue

@@ -0,0 +1,109 @@
+<template>
+	<view class="content">
+		<view v-if="dataList.length != 0" class="bg u-flex u-p-l-30 u-p-t-30 u-p-b-10 u-p-r-30"
+			v-for="(item,index) in dataList" :key='index' @longpress="delData(item)">
+			<view class="u-m-r-10">
+				<u-avatar :src="item.avatar?item.avatar: '../../static/logo.png'" size="100"></u-avatar>
+			</view>
+			<view class="u-flex-1 text-white margin-left-xs">
+				<view class="u-font-16  text-bold">{{item.userName}}</view>
+				<view class="u-font-14 margin-top-sm u-tips-color" @click="goNav('/pages/me/vip/index')">
+					{{item.updateTime}}访问了你
+				</view>
+			</view>
+		</view>
+		<empty v-if="dataList.length == 0"></empty>
+	</view>
+</template>
+
+<script>
+	import empty from '../../components/empty.vue'
+	export default {
+		components: {
+			empty
+		},
+		data() {
+			return {
+				dataList: [],
+				page: 1,
+				limit: 10
+			}
+		},
+		onLoad(e) {
+			// uni.setNavigationBarTitle({
+			// 	title: e.name
+			// })
+			this.$queue.showLoading("加载中...");
+			this.getVisitorList()
+
+		},
+		methods: {
+			// 访客
+			getVisitorList() {
+				let data = {
+					page: this.page,
+					limit: this.limit
+				}
+				this.$Request.get("/app/userBrowse/myVisitor", data).then(res => {
+					uni.hideLoading();
+					if (res.code == 0) {
+						if (this.page == 1) {
+							this.dataList = res.data.list
+						} else {
+							this.dataList = [...this.dataList, ...res.data.list]
+						}
+					} else {
+						console.log(res.msg)
+					}
+					uni.stopPullDownRefresh();
+				})
+			},
+			// 删除
+			delData(e) {
+				let that = this
+				uni.showModal({
+					title: '提示',
+					content: '确定删除吗?',
+					confirmColor:'#00B88F',
+					success: function(res) {
+						if (res.confirm) {
+							console.log('用户点击确定');
+							let data = {
+								id: e.id
+							}
+							that.$Request.post("/app/userBrowse/deleteMyVisitor", data).then(res => {
+								if (res.code == 0) {
+									uni.showToast({
+										title: '删除成功!',
+										icon: 'none'
+									})
+									that.getVisitorList()
+								}
+							})
+						} else if (res.cancel) {
+							console.log('用户点击取消');
+						}
+					}
+				})
+			}
+		},
+		onReachBottom: function() {
+			this.page = this.page + 1;
+			this.getVisitorList()
+		},
+		onPullDownRefresh: function() {
+			this.page = 1;
+			this.getVisitorList()
+		},
+	}
+</script>
+
+<style>
+	page {
+		background-color: #F7F7F7;
+	}
+
+	.bg {
+		background-color: #FFFFFF;
+	}
+</style>

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