hwq vor 3 Jahren
Ursprung
Commit
c7ef3fdfe5
100 geänderte Dateien mit 26194 neuen und 0 gelöschten Zeilen
  1. 20 0
      .hbuilderx/launch.json
  2. 269 0
      App.vue
  3. 37 0
      api/index.js
  4. 35 0
      api/login.js
  5. 73 0
      api/money.js
  6. 37 0
      api/wx.js
  7. 57 0
      components/Loading/index.vue
  8. 120 0
      components/countDown/index.vue
  9. 18 0
      components/empty.vue
  10. 36 0
      components/emptyPage.vue
  11. 118 0
      components/home/index.vue
  12. 33 0
      components/js_sdk/xb-copy/uni-copy.js
  13. 630 0
      components/jyf-parser/jyf-parser.vue
  14. 97 0
      components/jyf-parser/libs/CssHandler.js
  15. 535 0
      components/jyf-parser/libs/MpHtmlParser.js
  16. 80 0
      components/jyf-parser/libs/config.js
  17. 22 0
      components/jyf-parser/libs/handler.wxs
  18. 501 0
      components/jyf-parser/libs/trees.vue
  19. 421 0
      components/newlist/nowList.vue
  20. 68 0
      components/returnButton.vue
  21. 196 0
      components/share.vue
  22. 218 0
      components/ss-calendar/ss-calendar.vue
  23. 1201 0
      components/tki-qrcode/qrcode.js
  24. 210 0
      components/tki-qrcode/tki-qrcode.vue
  25. 122 0
      components/uni-badge/uni-badge.vue
  26. 181 0
      components/uni-countdown/uni-countdown.vue
  27. 188 0
      components/uni-countdown/uni-countdowns.vue
  28. 124 0
      components/uni-fav/uni-fav.vue
  29. 96 0
      components/uni-icons/icons.js
  30. 10 0
      components/uni-icons/uni-icons.vue
  31. 230 0
      components/uni-list-item/uni-list-item.vue
  32. 68 0
      components/uni-list/uni-list.vue
  33. 65 0
      components/uni-list/uni-refresh.vue
  34. 87 0
      components/uni-list/uni-refresh.wxs
  35. 205 0
      components/uni-load-more/uni-load-more.vue
  36. 396 0
      components/uni-notice-bar/uni-notice-bar.vue
  37. 198 0
      components/uni-number-box.vue
  38. 243 0
      components/uni-popup/uni-popup-dialog.vue
  39. 116 0
      components/uni-popup/uni-popup-message.vue
  40. 263 0
      components/uni-popup/uni-popup-ori.vue
  41. 282 0
      components/uni-popup/uni-popup-share.vue
  42. 263 0
      components/uni-popup/uni-popup.vue
  43. 141 0
      components/uni-rate/uni-rate.vue
  44. 244 0
      components/uni-steps/uni-steps.vue
  45. 279 0
      components/uni-transition/uni-transition.vue
  46. 226 0
      components/upload-images.vue
  47. 4914 0
      components/wangding-pickerAddress/data.js
  48. 103 0
      components/wangding-pickerAddress/wangding-pickerAddress.vue
  49. 25 0
      components/wyb-drop-down/iconfont.css
  50. 421 0
      components/wyb-drop-down/wyb-drop-down.vue
  51. 18 0
      config/app.js
  52. 32 0
      config/cache.js
  53. 28 0
      index.html
  54. 31 0
      lang/en.js
  55. 19 0
      lang/i18n.js
  56. 2210 0
      lang/i18nJs.js
  57. 31 0
      lang/zh_cn.js
  58. 39 0
      libs/log.js
  59. 84 0
      libs/login.js
  60. 253 0
      libs/wechat.js
  61. 49 0
      main.js
  62. 129 0
      manifest.json
  63. 20 0
      node_modules/.package-lock.json
  64. 31 0
      node_modules/@metamask/onboarding/CHANGELOG.md
  65. 21 0
      node_modules/@metamask/onboarding/LICENSE
  66. 92 0
      node_modules/@metamask/onboarding/README.md
  67. 38 0
      node_modules/@metamask/onboarding/dist/index.d.ts
  68. 2462 0
      node_modules/@metamask/onboarding/dist/metamask-onboarding.bundle.js
  69. 240 0
      node_modules/@metamask/onboarding/dist/metamask-onboarding.cjs.js
  70. 236 0
      node_modules/@metamask/onboarding/dist/metamask-onboarding.es.js
  71. 61 0
      node_modules/@metamask/onboarding/package.json
  72. 205 0
      node_modules/@metamask/onboarding/src/index.ts
  73. 218 0
      node_modules/bowser/CHANGELOG.md
  74. 39 0
      node_modules/bowser/LICENSE
  75. 179 0
      node_modules/bowser/README.md
  76. 0 0
      node_modules/bowser/bundled.js
  77. 0 0
      node_modules/bowser/es5.js
  78. 250 0
      node_modules/bowser/index.d.ts
  79. 83 0
      node_modules/bowser/package.json
  80. 77 0
      node_modules/bowser/src/bowser.js
  81. 116 0
      node_modules/bowser/src/constants.js
  82. 700 0
      node_modules/bowser/src/parser-browsers.js
  83. 120 0
      node_modules/bowser/src/parser-engines.js
  84. 199 0
      node_modules/bowser/src/parser-os.js
  85. 266 0
      node_modules/bowser/src/parser-platforms.js
  86. 496 0
      node_modules/bowser/src/parser.js
  87. 309 0
      node_modules/bowser/src/utils.js
  88. 40 0
      package-lock.json
  89. 5 0
      package.json
  90. 47 0
      pages.json
  91. 180 0
      pages/index/bonus.vue
  92. 66 0
      pages/index/explain.vue
  93. 1157 0
      pages/index/index.vue
  94. 98 0
      pages/index/network.vue
  95. 147 0
      plugin/image-tools/index.js
  96. 0 0
      plugin/jweixin-module/index.js
  97. 551 0
      static/css/cmy.css
  98. BIN
      static/img/add.png
  99. BIN
      static/img/all-back.png
  100. BIN
      static/img/all-bg.png

+ 20 - 0
.hbuilderx/launch.json

@@ -0,0 +1,20 @@
+{ // launch.json 配置了启动调试时相关设置,configurations下节点名称可为 app-plus/h5/mp-weixin/mp-baidu/mp-alipay/mp-qq/mp-toutiao/mp-360/
+  // launchtype项可配置值为local或remote, local代表前端连本地云函数,remote代表前端连云端云函数
+    "version": "0.0",
+    "configurations": [{
+     	"app-plus" : 
+     	{
+     		"launchtype" : "remote"
+     	},
+     	"default" : 
+     	{
+     		"launchtype" : "remote"
+     	},
+     	"h5" : 
+     	{
+     		"launchtype" : "remote"
+     	},
+     	"type" : "uniCloud"
+     }
+    ]
+}

+ 269 - 0
App.vue

@@ -0,0 +1,269 @@
+<script>
+/**
+ * vuex管理登陆状态,具体可以参考官方登陆模板示例
+ */
+import { mapMutations } from 'vuex';
+// #ifdef H5
+import { weixindata, setRouter } from './utils/wxAuthorized';
+// #endif
+// #ifdef APP-PLUS
+import { getUpApp } from './utils/upApp.js';
+// #endif
+export default {
+	data() {
+		return {
+			/* 保存微信信息 */
+			appData: {}
+		};
+	},
+	methods: {
+		...mapMutations('user', ['setUserInfo', 'login', 'hasLogin', 'setAddress'])
+	},
+	onLaunch: function(urlObj) {
+		let obj = this;
+		let systemInfo = uni.getSystemInfoSync();
+		let lang = systemInfo.language || 'zh-CN'
+		uni.setStorageSync('lang',lang)
+		// 加载缓存中的用户信息
+		let userInfo = uni.getStorageSync('userInfo') || '';
+		let address = uni.getStorageSync('address')
+		// 判断是否拥有用户信息
+		if (userInfo.id) {
+			//更新登陆状态
+			uni.getStorage({
+				key: 'userInfo',
+				success: res => {
+					obj.setAddress(address)
+					obj.setUserInfo(res.data);
+					obj.login(res.data);
+				}
+			});
+		}
+		// #ifdef H5
+		// 保存路由对象
+		setRouter(this.$router);
+		//判断是否已经缓存浏览器
+		let bool = uni.getStorageSync('weichatBrowser') || '';
+		if (bool === '') {
+			//判断是否为微信浏览
+			bool = navigator.userAgent.toLowerCase().match(/MicroMessenger/i) == 'micromessenger';
+			// 保存当前是否为微信内核浏览器
+			uni.setStorageSync('weichatBrowser', bool);
+		}
+		if (bool) {
+			// 加载微信信息
+			weixindata();
+		}
+		// #endif
+		// #ifdef APP-PLUS
+		// 判断是否升级
+		getUpApp();
+		// 获取当前运行系统
+		let system = uni.getStorageSync('platform') || '';
+		if (!system) {
+			uni.setStorage({
+				key: 'platform',
+				data: uni.getSystemInfoSync().platform
+			});
+		}
+		// #endif
+	},
+	onShow: function() {
+		// 加载拦截
+		// console.log('App Show');
+	},
+	onHide: function() {
+		// console.log('App Hide');
+	}
+};
+</script>
+
+<style lang="scss">
+/*全局公共样式和字体图标*/
+@import '/static/css/cmy.css';
+@import "uview-ui/index.scss";
+view,
+scroll-view,
+swiper,
+swiper-item,
+cover-view,
+cover-image,
+icon,
+text,
+rich-text,
+progress,
+button,
+checkbox,
+form,
+input,
+label,
+radio,
+slider,
+switch,
+textarea,
+navigator,
+audio,
+camera,
+image,
+video {
+	box-sizing: border-box;
+}
+/* 骨架屏替代方案 */
+.Skeleton {
+	background: #f3f3f3;
+	padding: 20rpx 0;
+	border-radius: 8rpx;
+}
+
+/* 图片载入替代方案 */
+.image-wrapper {
+	font-size: 0;
+	background: #f3f3f3;
+	border-radius: 4px;
+	image {
+		width: 100%;
+		height: 100%;
+		transition: 0.6s;
+		opacity: 0;
+		&.loaded {
+			opacity: 1;
+		}
+	}
+}
+
+// 设置富文本中图片最大宽度
+uni-rich-text img {
+	max-width: 100% !important;
+}
+/*边框*/
+.b-b:after,
+.b-t:after {
+	position: absolute;
+	z-index: 3;
+	left: 0;
+	right: 0;
+	height: 0;
+	content: '';
+	transform: scaleY(0.5);
+	border-bottom: 1px solid $border-color-base;
+}
+
+.b-b:after {
+	bottom: 0;
+}
+
+.b-t:after {
+	top: 0;
+}
+
+/* button样式改写 */
+uni-button,
+button {
+	height: 80rpx;
+	line-height: 80rpx;
+	font-size: $font-lg + 2rpx;
+	font-weight: normal;
+
+	&.no-border:before,
+	&.no-border:after {
+		border: 0;
+	}
+}
+
+uni-button[type='default'],
+button[type='default'] {
+	color: $font-color-dark;
+}
+
+/* input 样式 */
+.input-placeholder {
+	color: #999999;
+}
+
+.placeholder {
+	color: #999999;
+}
+// 边距样式
+@for $i from 1 to 4 {
+	.margin-l-#{$i * 10} {
+		margin-left: $i * 10rpx !important;
+	}
+	.margin-r-#{$i * 10} {
+		margin-right: $i * 10rpx !important;
+	}
+	.margin-t-#{$i * 10} {
+		margin-top: $i * 10rpx !important;
+	}
+	.margin-b-#{$i * 10} {
+		margin-bottom: $i * 10rpx !important;
+	}
+	.margin-#{$i * 10} {
+		margin: $i * 10rpx !important;
+	}
+	.margin-v-#{$i * 10} {
+		margin-top: $i * 10rpx !important;
+		margin-bottom: $i * 10rpx !important;
+	}
+	.margin-c-#{$i * 10} {
+		margin-left: $i * 10rpx !important;
+		margin-right: $i * 10rpx !important;
+	}
+	.padding-l-#{$i * 10} {
+		padding-left: $i * 10rpx !important;
+	}
+	.padding-r-#{$i * 10} {
+		padding-right: $i * 10rpx !important;
+	}
+	.padding-t-#{$i * 10} {
+		padding-top: $i * 10rpx !important;
+	}
+	.padding-b-#{$i * 10} {
+		padding-bottom: $i * 10rpx !important;
+	}
+	.padding-#{$i * 10} {
+		padding: $i * 10rpx !important;
+	}
+	.padding-v-#{$i * 10} {
+		padding-top: $i * 10rpx !important;
+		padding-bottom: $i * 10rpx !important;
+	}
+	.padding-c-#{$i * 10} {
+		padding-left: $i * 10rpx !important;
+		padding-right: $i * 10rpx !important;
+	}
+}
+// 字体大小
+.font-size-sm {
+	font-size: $font-sm;
+}
+.font-size-base {
+	font-size: $font-base;
+}
+.font-size-lg {
+	font-size: $font-lg;
+}
+// 字体颜色
+.font-color-yellow {
+	color: $color-yellow;
+}
+.font-color-gray {
+	color: $color-gray;
+}
+.font-color-red {
+	color: $color-red;
+}
+// 边框颜色
+.border-color-yellow {
+	border: 1rpx solid $color-yellow;
+}
+
+// 修改默认背景颜色
+uni-page-wrapper {
+	background-color: $page-color-base;
+}
+page {
+	background-color: $page-color-base;
+	// 设置默认字体
+	font-family: PingFang SC, STHeitiSC-Light, Helvetica-Light, arial, sans-serif, Droid Sans Fallback;
+}
+</style>

+ 37 - 0
api/index.js

@@ -0,0 +1,37 @@
+import request from '@/utils/request'
+
+//绑定推荐人
+export function addSpread(data) {
+	return request({
+		url: '/api/user/addSpread',
+		method: 'post',
+		data
+	});
+}
+
+//首页
+export function index(data) {
+	return request({
+		url: '/api/index/index',
+		method: 'get',
+		data
+	});
+}
+
+//奖池详情
+export function lake(data) {
+	return request({
+		url:'/api/index/lake',
+		method:'get',
+		data
+	})
+}
+
+//网络详情
+export function group(data) {
+	return request({
+		url:'/api/user/group',
+		method:'get',
+		data
+	})
+}

+ 35 - 0
api/login.js

@@ -0,0 +1,35 @@
+import request from '@/utils/request'
+
+// 登录
+export function logins(data) {
+	return request({
+		url: '/api/user/login',
+		method: 'post',
+		data
+	});
+}
+
+export function userinfo(data) {
+	return request({
+		url: '/api/user/index',
+		method: 'get',
+		data
+	});
+}
+
+export function index(data) {
+	return request({
+		url:'/api/index/index',
+		method: 'get',
+		data
+	})
+}
+
+//退出登录
+export function logout(data) {
+	return request({
+		url: '/api/user/logout',
+		method: 'post',
+		data
+	});
+}

+ 73 - 0
api/money.js

@@ -0,0 +1,73 @@
+import request from '@/utils/request'
+
+// 授权加密requset
+export function approveDate(data) {
+	return request({
+		url: '/api/user/approveDate',
+		method: 'post',
+		data
+	});
+}
+
+// 激活加密requset
+export function transferDate(data) {
+	return request({
+		url: '/api/user/transferDate',
+		method: 'post',
+		data
+	});
+}
+
+// 提币计算
+export function extractCaculator(data) {
+	return request({
+		url: '/api/user/extractCaculator',
+		method: 'post',
+		data
+	});
+}
+
+// 取回NLP
+export function getNLP(data) {
+	return request({
+		url: '/api/user/getNLP',
+		method: 'post',
+		data
+	});
+}
+
+// 授权
+export function approveUser(data) {
+	return request({
+		url: '/api/user/approveUser',
+		method: 'post',
+		data
+	});
+}
+
+// 激活
+export function activateUser(data) {
+	return request({
+		url: '/api/user/activateUser',
+		method: 'post',
+		data
+	});
+}
+
+// 升级
+export function levelUp(data) {
+	return request({
+		url: '/api/user/levelUp',
+		method: 'post',
+		data
+	});
+}
+
+// 提币
+export function extractUSDT(data) {
+	return request({
+		url: '/api/user/extractUSDT',
+		method: 'post',
+		data
+	});
+}

+ 37 - 0
api/wx.js

@@ -0,0 +1,37 @@
+import request from '@/utils/request'
+// #ifdef H5
+// 微信分享信息
+export function share(data) {
+	return request({
+		url: '/api/share',
+		method: 'get',
+		data
+	});
+}
+//微信配置
+export function wechatConfig(data) {
+	return request({
+		url: '/api/wechat/config',
+		method: 'get',
+		data
+	});
+}
+// 微信code地址
+export function wechatAuth(data) {
+	return request({
+		url: '/api/wechat/auth',
+		method: 'get',
+		data
+	});
+}
+// #endif
+// #ifdef MP-WEIXIN
+// 微信code地址
+export function wechatMpAuth(data) {
+	return request({
+		url: '/api/wechat/mp_auth',
+		method: 'post',
+		data
+	});
+}
+// #endif

+ 57 - 0
components/Loading/index.vue

@@ -0,0 +1,57 @@
+<template>
+	<view>
+		<view class="Loads acea-row row-center-wrapper" v-if="loading && !loaded" style="margin-top: .2rem;">
+			<view v-if="loading">
+				<view class="iconfont icon-jiazai loading acea-row row-center-wrapper"></view>
+				正在加载中
+			</view>
+			<view v-else>
+				上拉加载更多
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		name: "Loading",
+		props: {
+			loaded: {
+				type: Boolean,
+				default: false
+			},
+			loading: {
+				type: Boolean,
+				default: false
+			}
+		}
+	};
+</script>
+<style>
+	.Loads {
+	  height: 80upx;
+	  font-size: 25upx;
+	  color: #000;
+	}
+	.Loads .iconfont {
+	  font-size: 30upx;
+	  margin-right: 10upx;
+	  height: 32upx;
+	  line-height: 32upx;
+	}
+	/*加载动画*/
+	@keyframes load {
+	  from {
+	    transform: rotate(0deg);
+	  }
+	  to {
+	    transform: rotate(360deg);
+	  }
+	}
+	.loadingpic {
+	  animation: load 3s linear 1s infinite;
+	}
+	.loading {
+	  animation: load linear 1s infinite;
+	}
+</style>

+ 120 - 0
components/countDown/index.vue

@@ -0,0 +1,120 @@
+<template>
+	<view class="time" :style="justifyLeft">
+		<text class="red" v-if="tipText">{{ tipText }}</text>
+		<text class="styleAll" v-if="isDay === true">{{ day }}</text>
+		<text class="timeTxt red" v-if="dayText">{{ dayText }}</text>
+		<text class="styleAll">{{ hour }}</text>
+		<text class="timeTxt red" v-if="hourText">{{ hourText }}</text>
+		<text class="styleAll">{{ minute }}</text>
+		<text class="timeTxt red" v-if="minuteText">{{ minuteText }}</text>
+		<text class="styleAll">{{ second }}</text>
+		<text class="timeTxt red" v-if="secondText">{{ secondText }}</text>
+	</view>
+</template>
+
+<script>
+	export default {
+		name: "countDown",
+		props: {
+			justifyLeft: {
+				type: String,
+				default: ""
+			},
+			//距离开始提示文字
+			tipText: {
+				type: String,
+				default: "倒计时"
+			},
+			dayText: {
+				type: String,
+				default: "天"
+			},
+			hourText: {
+				type: String,
+				default: "时"
+			},
+			minuteText: {
+				type: String,
+				default: "分"
+			},
+			secondText: {
+				type: String,
+				default: "秒"
+			},
+			datatime: {
+				type: Number,
+				default: 0
+			},
+			isDay: {
+				type: Boolean,
+				default: true
+			}
+		},
+		data: function() {
+			return {
+				day: "00",
+				hour: "00",
+				minute: "00",
+				second: "00"
+			};
+		},
+		created: function() {
+			this.show_time();
+		},
+		mounted: function() {},
+		methods: {
+			show_time: function() {
+				let that = this;
+
+				function runTime() {
+					//时间函数
+					let intDiff = that.datatime - Date.parse(new Date()) / 1000; //获取数据中的时间戳的时间差;
+					let day = 0,
+						hour = 0,
+						minute = 0,
+						second = 0;
+					if (intDiff > 0) {
+						//转换时间
+						if (that.isDay === true) {
+							day = Math.floor(intDiff / (60 * 60 * 24));
+						} else {
+							day = 0;
+						}
+						hour = Math.floor(intDiff / (60 * 60)) - day * 24;
+						minute = Math.floor(intDiff / 60) - day * 24 * 60 - hour * 60;
+						second =
+							Math.floor(intDiff) -
+							day * 24 * 60 * 60 -
+							hour * 60 * 60 -
+							minute * 60;
+						if (hour <= 9) hour = "0" + hour;
+						if (minute <= 9) minute = "0" + minute;
+						if (second <= 9) second = "0" + second;
+						that.day = day;
+						that.hour = hour;
+						that.minute = minute;
+						that.second = second;
+					} else {
+						that.day = "00";
+						that.hour = "00";
+						that.minute = "00";
+						that.second = "00";
+					}
+				}
+				runTime();
+				setInterval(runTime, 1000);
+			}
+		}
+	};
+</script>
+
+<style>
+	.time{
+		display: flex;
+		justify-content: center;
+	} 
+	.red{
+		color: #fc4141;
+		margin: 0 4rpx;
+	}
+</style>

Datei-Diff unterdrückt, da er zu groß ist
+ 18 - 0
components/empty.vue


+ 36 - 0
components/emptyPage.vue

@@ -0,0 +1,36 @@
+<template>
+	<view class="empty-box">
+		<image src="/static/images/empty-box.png"></image>
+		<view class="txt">{{title}}</view>
+	</view>
+</template>
+
+<script>
+	export default{
+		props: {
+			title: {
+				type: String,
+				default: '暂无记录',
+			},
+		},
+	}
+	
+</script>
+
+<style lang="scss">
+	.empty-box{
+		display: flex;
+		flex-direction: column;
+		justify-content: center;
+		align-items: center;
+		margin-top: 200rpx;
+		image{
+			width: 414rpx;
+			height: 240rpx;
+		}
+		.txt{
+			font-size: 26rpx;
+			color: #999;
+		}
+	}
+</style>

+ 118 - 0
components/home/index.vue

@@ -0,0 +1,118 @@
+<template>
+	<view style="touch-action: none;">
+		<view class="home" style="position:fixed;" :style="{ top: top + 'px', bottom: bottom }" id="right-nav" @touchmove.stop.prevent="setTouchMove">
+			<view class="homeCon bg-color-red" :class="homeActive === true ? 'on' : ''" v-if="homeActive">
+				<navigator hover-class='none' url='/pages/index/index' open-type='switchTab' class='iconfont icon-shouye-xianxing'></navigator>
+				<navigator hover-class='none' url='/pages/order_addcart/order_addcart' open-type='switchTab' class='iconfont icon-caigou-xianxing'></navigator>
+				<navigator hover-class='none' url='/pages/user/index' open-type='switchTab' class='iconfont icon-yonghu1'></navigator>
+			</view>
+			<view @click="open" class="pictrueBox">
+				<view class="pictrue">
+					<image :src="
+              homeActive === true
+                ? '/static/images/close.gif'
+                : '/static/images/open.gif'
+            "
+					 class="image" />
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+<script>
+	import {
+		mapGetters
+	} from "vuex";
+	export default {
+		name: "Home",
+		props: {},
+		data: function() {
+			return {
+				top: "",
+				bottom: ""
+			};
+		},
+		computed: mapGetters(["homeActive"]),
+		methods: {
+			setTouchMove(e) {
+				var that = this;
+				if (e.touches[0].clientY < 545 && e.touches[0].clientY > 66) {
+					that.top = e.touches[0].clientY
+					// that.setData({
+					// 	top: e.touches[0].clientY
+					// })
+				}
+			},
+			open: function() {
+				this.homeActive ?
+					this.$store.commit("CLOSE_HOME") :
+					this.$store.commit("OPEN_HOME");
+			}
+		},
+		created() {
+			this.bottom = "50px";
+		}
+	};
+</script>
+
+<style scoped>
+	.pictrueBox {
+		width: 130rpx;
+		height: 120rpx;
+	}
+
+	/*返回主页按钮*/
+	.home {
+		position: fixed;
+		color: white;
+		text-align: center;
+		z-index: 9999;
+		right: 15rpx;
+		display: flex;
+	}
+
+	.home .homeCon {
+		border-radius: 50rpx;
+		opacity: 0;
+		height: 0;
+		color: #e93323;
+		width: 0;
+	}
+
+	.home .homeCon.on {
+		opacity: 1;
+		animation: bounceInRight 0.5s cubic-bezier(0.215, 0.610, 0.355, 1.000);
+		width: 300rpx;
+		height: 86rpx;
+		margin-bottom: 20rpx;
+		display: flex;
+		justify-content: center;
+		align-items: center;
+		background: #f44939 !important;
+	}
+
+	.home .homeCon .iconfont {
+		font-size: 48rpx;
+		color: #fff;
+		display: inline-block;
+		margin: 0 auto;
+	}
+
+	.home .pictrue {
+		width: 86rpx;
+		height: 86rpx;
+		border-radius: 50%;
+		margin: 0 auto;
+	}
+
+	.home .pictrue .image {
+		width: 100%;
+		height: 100%;
+		border-radius: 50%;
+		transform: rotate(90deg);
+		ms-transform: rotate(90deg);
+		moz-transform: rotate(90deg);
+		webkit-transform: rotate(90deg);
+		o-transform: rotate(90deg);
+	}
+</style>

+ 33 - 0
components/js_sdk/xb-copy/uni-copy.js

@@ -0,0 +1,33 @@
+export default function uniCopy(content) {
+	/**
+	 * 小程序端 和 app端的复制逻辑
+	 */
+	//#ifndef H5
+	uni.setClipboardData({
+		data: content,
+		success: function() {
+			console.log('success');
+			return true
+		}
+	});
+	//#endif
+	
+	/**
+	 * H5端的复制逻辑
+	 */
+	// #ifdef H5
+	if (!document.queryCommandSupported('copy')) { //为了兼容有些浏览器 queryCommandSupported 的判断
+		// 不支持
+		return false
+	}
+	let textarea = document.createElement("textarea")
+	textarea.value = content
+	textarea.readOnly = "readOnly"
+	document.body.appendChild(textarea)
+	textarea.select() // 选择对象
+	textarea.setSelectionRange(0, content.length) //核心
+	let result = document.execCommand("copy") // 执行浏览器复制命令
+	textarea.remove()
+	return result
+	// #endif
+}

+ 630 - 0
components/jyf-parser/jyf-parser.vue

@@ -0,0 +1,630 @@
+<template>
+	<view>
+		<slot v-if="!nodes.length" />
+		<!--#ifdef APP-PLUS-NVUE-->
+		<web-view id="_top" ref="web" :style="'margin-top:-2px;height:'+height+'px'" @onPostMessage="_message" />
+		<!--#endif-->
+		<!--#ifndef APP-PLUS-NVUE-->
+		<view id="_top" :style="showAm+(selectable?';user-select:text;-webkit-user-select:text':'')">
+			<!--#ifdef H5 || MP-360-->
+			<div :id="'rtf'+uid"></div>
+			<!--#endif-->
+			<!--#ifndef H5 || MP-360-->
+			<trees :nodes="nodes" :lazyLoad="lazyLoad" :loading="loadingImg" />
+			<!--#endif-->
+		</view>
+		<!--#endif-->
+	</view>
+</template>
+
+<script>
+	// #ifndef H5 || APP-PLUS-NVUE || MP-360
+	import trees from './libs/trees';
+	var cache = {},
+		// #ifdef MP-WEIXIN || MP-TOUTIAO
+		fs = uni.getFileSystemManager ? uni.getFileSystemManager() : null,
+		// #endif
+		Parser = require('./libs/MpHtmlParser.js');
+	var dom;
+	// 计算 cache 的 key
+	function hash(str) {
+		for (var i = str.length, val = 5381; i--;)
+			val += (val << 5) + str.charCodeAt(i);
+		return val;
+	}
+	// #endif
+	// #ifdef H5 || APP-PLUS-NVUE || MP-360
+	var windowWidth = uni.getSystemInfoSync().windowWidth,
+		cfg = require('./libs/config.js');
+	// #endif
+	// #ifdef APP-PLUS-NVUE
+	var weexDom = weex.requireModule('dom');
+	// #endif
+	/**
+	 * Parser 富文本组件
+	 * @tutorial https://github.com/jin-yufeng/Parser
+	 * @property {String} html 富文本数据
+	 * @property {Boolean} autopause 是否在播放一个视频时自动暂停其他视频
+	 * @property {Boolean} autoscroll 是否自动给所有表格添加一个滚动层
+	 * @property {Boolean} autosetTitle 是否自动将 title 标签中的内容设置到页面标题
+	 * @property {Number} compress 压缩等级
+	 * @property {String} domain 图片、视频等链接的主域名
+	 * @property {Boolean} lazyLoad 是否开启图片懒加载
+	 * @property {String} loadingImg 图片加载完成前的占位图
+	 * @property {Boolean} selectable 是否开启长按复制
+	 * @property {Object} tagStyle 标签的默认样式
+	 * @property {Boolean} showWithAnimation 是否使用渐显动画
+	 * @property {Boolean} useAnchor 是否使用锚点
+	 * @property {Boolean} useCache 是否缓存解析结果
+	 * @event {Function} parse 解析完成事件
+	 * @event {Function} load dom 加载完成事件
+	 * @event {Function} ready 所有图片加载完毕事件
+	 * @event {Function} error 错误事件
+	 * @event {Function} imgtap 图片点击事件
+	 * @event {Function} linkpress 链接点击事件
+	 * @author JinYufeng
+	 * @version 20200728
+	 * @listens MIT
+	 */
+	export default {
+		name: 'parser',
+		data() {
+			return {
+				// #ifdef H5 || MP-360
+				uid: this._uid,
+				// #endif
+				// #ifdef APP-PLUS-NVUE
+				height: 1,
+				// #endif
+				// #ifndef APP-PLUS-NVUE
+				showAm: '',
+				// #endif
+				nodes: []
+			}
+		},
+		// #ifndef H5 || APP-PLUS-NVUE || MP-360
+		components: {
+			trees
+		},
+		// #endif
+		props: {
+			html: String,
+			autopause: {
+				type: Boolean,
+				default: true
+			},
+			autoscroll: Boolean,
+			autosetTitle: {
+				type: Boolean,
+				default: true
+			},
+			// #ifndef H5 || APP-PLUS-NVUE || MP-360
+			compress: Number,
+			loadingImg: String,
+			useCache: Boolean,
+			// #endif
+			domain: String,
+			lazyLoad: Boolean,
+			selectable: Boolean,
+			tagStyle: Object,
+			showWithAnimation: Boolean,
+			useAnchor: Boolean
+		},
+		watch: {
+			html(html) {
+				this.setContent(html);
+			}
+		},
+		created() {
+			// 图片数组
+			this.imgList = [];
+			this.imgList.each = function(f) {
+				for (var i = 0, len = this.length; i < len; i++)
+					this.setItem(i, f(this[i], i, this));
+			}
+			this.imgList.setItem = function(i, src) {
+				if (i == void 0 || !src) return;
+				// #ifndef MP-ALIPAY || APP-PLUS
+				// 去重
+				if (src.indexOf('http') == 0 && this.includes(src)) {
+					var newSrc = src.split('://')[0];
+					for (var j = newSrc.length, c; c = src[j]; j++) {
+						if (c == '/' && src[j - 1] != '/' && src[j + 1] != '/') break;
+						newSrc += Math.random() > 0.5 ? c.toUpperCase() : c;
+					}
+					newSrc += src.substr(j);
+					return this[i] = newSrc;
+				}
+				// #endif
+				this[i] = src;
+				// 暂存 data src
+				if (src.includes('data:image')) {
+					var filePath, info = src.match(/data:image\/(\S+?);(\S+?),(.+)/);
+					if (!info) return;
+					// #ifdef MP-WEIXIN || MP-TOUTIAO
+					filePath = `${wx.env.USER_DATA_PATH}/${Date.now()}.${info[1]}`;
+					fs && fs.writeFile({
+						filePath,
+						data: info[3],
+						encoding: info[2],
+						success: () => this[i] = filePath
+					})
+					// #endif
+					// #ifdef APP-PLUS
+					filePath = `_doc/parser_tmp/${Date.now()}.${info[1]}`;
+					var bitmap = new plus.nativeObj.Bitmap();
+					bitmap.loadBase64Data(src, () => {
+						bitmap.save(filePath, {}, () => {
+							bitmap.clear()
+							this[i] = filePath;
+						})
+					})
+					// #endif
+				}
+			}
+		},
+		mounted() {
+			// #ifdef H5 || MP-360
+			this.document = document.getElementById('rtf' + this._uid);
+			// #endif
+			// #ifndef H5 || APP-PLUS-NVUE || MP-360
+			if (dom) this.document = new dom(this);
+			// #endif
+			// #ifdef APP-PLUS-NVUE
+			this.document = this.$refs.web;
+			setTimeout(() => {
+				// #endif
+				if (this.html) this.setContent(this.html);
+				// #ifdef APP-PLUS-NVUE
+			}, 30)
+			// #endif
+		},
+		beforeDestroy() {
+			// #ifdef H5 || MP-360
+			if (this._observer) this._observer.disconnect();
+			// #endif
+			this.imgList.each(src => {
+				// #ifdef APP-PLUS
+				if (src && src.includes('_doc')) {
+					plus.io.resolveLocalFileSystemURL(src, entry => {
+						entry.remove();
+					});
+				}
+				// #endif
+				// #ifdef MP-WEIXIN || MP-TOUTIAO
+				if (src && src.includes(uni.env.USER_DATA_PATH))
+					fs && fs.unlink({
+						filePath: src
+					})
+				// #endif
+			})
+			clearInterval(this._timer);
+		},
+		methods: {
+			// 设置富文本内容
+			setContent(html, append) {
+				// #ifdef APP-PLUS-NVUE
+				if (!html)
+					return this.height = 1;
+				if (append)
+					this.$refs.web.evalJs("var b=document.createElement('div');b.innerHTML='" + html.replace(/'/g, "\\'") +
+						"';document.getElementById('parser').appendChild(b)");
+				else {
+					html =
+						'<meta charset="utf-8" /><meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no"><style>html,body{width:100%;height:100%;overflow:hidden}body{margin:0}</style><base href="' +
+						this.domain + '"><div id="parser"' + (this.selectable ? '>' : ' style="user-select:none">') + this._handleHtml(html).replace(/\n/g, '\\n') +
+						'</div><script>"use strict";function e(e){if(window.__dcloud_weex_postMessage||window.__dcloud_weex_){var t={data:[e]};window.__dcloud_weex_postMessage?window.__dcloud_weex_postMessage(t):window.__dcloud_weex_.postMessage(JSON.stringify(t))}}document.body.onclick=function(){e({action:"click"})},' +
+						(this.showWithAnimation ? 'document.body.style.animation="_show .5s",' : '') +
+						'setTimeout(function(){e({action:"load",text:document.body.innerText,height:document.getElementById("parser").scrollHeight})},50);\x3c/script>';
+					this.$refs.web.evalJs("document.write('" + html.replace(/'/g, "\\'") + "');document.close()");
+				}
+				this.$refs.web.evalJs(
+					'var t=document.getElementsByTagName("title");t.length&&e({action:"getTitle",title:t[0].innerText});for(var o,n=document.getElementsByTagName("style"),r=1;o=n[r++];)o.innerHTML=o.innerHTML.replace(/body/g,"#parser");for(var a,c=document.getElementsByTagName("img"),s=[],i=0==c.length,d=0,l=0,g=0;a=c[l];l++)parseInt(a.style.width||a.getAttribute("width"))>' +
+					windowWidth + '&&(a.style.height="auto"),a.onload=function(){++d==c.length&&(i=!0)},a.onerror=function(){++d==c.length&&(i=!0),' + (cfg.errorImg ? 'this.src="' + cfg.errorImg + '",' : '') +
+					'e({action:"error",source:"img",target:this})},a.hasAttribute("ignore")||"A"==a.parentElement.nodeName||(a.i=g++,s.push(a.src),a.onclick=function(){e({action:"preview",img:{i:this.i,src:this.src}})});e({action:"getImgList",imgList:s});for(var u,m=document.getElementsByTagName("a"),f=0;u=m[f];f++)u.onclick=function(){var t,o=this.getAttribute("href");if("#"==o[0]){var n=document.getElementById(o.substr(1));n&&(t=n.offsetTop)}return e({action:"linkpress",href:o,offset:t}),!1};for(var h,y=document.getElementsByTagName("video"),v=0;h=y[v];v++)h.style.maxWidth="100%",h.onerror=function(){e({action:"error",source:"video",target:this})}' +
+					(this.autopause ? ',h.onplay=function(){for(var e,t=0;e=y[t];t++)e!=this&&e.pause()}' : '') +
+					';for(var _,p=document.getElementsByTagName("audio"),w=0;_=p[w];w++)_.onerror=function(){e({action:"error",source:"audio",target:this})};' +
+					(this.autoscroll ? 'for(var T,E=document.getElementsByTagName("table"),B=0;T=E[B];B++){var N=document.createElement("div");N.style.overflow="scroll",T.parentNode.replaceChild(N,T),N.appendChild(T)}' : '') +
+					'var x=document.getElementById("parser");clearInterval(window.timer),window.timer=setInterval(function(){i&&clearInterval(window.timer),e({action:"ready",ready:i,height:x.scrollHeight})},350)'
+				)
+				this.nodes = [1];
+				// #endif
+				// #ifdef H5 || MP-360
+				if (!html) {
+					if (this.rtf && !append) this.rtf.parentNode.removeChild(this.rtf);
+					return;
+				}
+				var div = document.createElement('div');
+				if (!append) {
+					if (this.rtf) this.rtf.parentNode.removeChild(this.rtf);
+					this.rtf = div;
+				} else {
+					if (!this.rtf) this.rtf = div;
+					else this.rtf.appendChild(div);
+				}
+				div.innerHTML = this._handleHtml(html, append);
+				for (var styles = this.rtf.getElementsByTagName('style'), i = 0, style; style = styles[i++];) {
+					style.innerHTML = style.innerHTML.replace(/body/g, '#rtf' + this._uid);
+					style.setAttribute('scoped', 'true');
+				}
+				// 懒加载
+				if (!this._observer && this.lazyLoad && IntersectionObserver) {
+					this._observer = new IntersectionObserver(changes => {
+						for (let item, i = 0; item = changes[i++];) {
+							if (item.isIntersecting) {
+								item.target.src = item.target.getAttribute('data-src');
+								item.target.removeAttribute('data-src');
+								this._observer.unobserve(item.target);
+							}
+						}
+					}, {
+						rootMargin: '500px 0px 500px 0px'
+					})
+				}
+				var _ts = this;
+				// 获取标题
+				var title = this.rtf.getElementsByTagName('title');
+				if (title.length && this.autosetTitle)
+					uni.setNavigationBarTitle({
+						title: title[0].innerText
+					})
+				// 图片处理
+				this.imgList.length = 0;
+				var imgs = this.rtf.getElementsByTagName('img');
+				for (let i = 0, j = 0, img; img = imgs[i]; i++) {
+					if (parseInt(img.style.width || img.getAttribute('width')) > windowWidth)
+						img.style.height = 'auto';
+					var src = img.getAttribute('src');
+					if (this.domain && src) {
+						if (src[0] == '/') {
+							if (src[1] == '/')
+								img.src = (this.domain.includes('://') ? this.domain.split('://')[0] : '') + ':' + src;
+							else img.src = this.domain + src;
+						} else if (!src.includes('://')) img.src = this.domain + '/' + src;
+					}
+					if (!img.hasAttribute('ignore') && img.parentElement.nodeName != 'A') {
+						img.i = j++;
+						_ts.imgList.push(img.src || img.getAttribute('data-src'));
+						img.onclick = function() {
+							var preview = true;
+							this.ignore = () => preview = false;
+							_ts.$emit('imgtap', this);
+							if (preview) {
+								uni.previewImage({
+									current: this.i,
+									urls: _ts.imgList
+								});
+							}
+						}
+					}
+					img.onerror = function() {
+						if (cfg.errorImg)
+							_ts.imgList[this.i] = this.src = cfg.errorImg;
+						_ts.$emit('error', {
+							source: 'img',
+							target: this
+						});
+					}
+					if (_ts.lazyLoad && this._observer && img.src && img.i != 0) {
+						img.setAttribute('data-src', img.src);
+						img.removeAttribute('src');
+						this._observer.observe(img);
+					}
+				}
+				// 链接处理
+				var links = this.rtf.getElementsByTagName('a');
+				for (var link of links) {
+					link.onclick = function() {
+						var jump = true,
+							href = this.getAttribute('href');
+						_ts.$emit('linkpress', {
+							href,
+							ignore: () => jump = false
+						});
+						if (jump && href) {
+							if (href[0] == '#') {
+								if (_ts.useAnchor) {
+									_ts.navigateTo({
+										id: href.substr(1)
+									})
+								}
+							} else if (href.indexOf('http') == 0 || href.indexOf('//') == 0)
+								return true;
+							else
+								uni.navigateTo({
+									url: href
+								})
+						}
+						return false;
+					}
+				}
+				// 视频处理
+				var videos = this.rtf.getElementsByTagName('video');
+				_ts.videoContexts = videos;
+				for (let video, i = 0; video = videos[i++];) {
+					video.style.maxWidth = '100%';
+					video.onerror = function() {
+						_ts.$emit('error', {
+							source: 'video',
+							target: this
+						});
+					}
+					video.onplay = function() {
+						if (_ts.autopause)
+							for (let item, i = 0; item = _ts.videoContexts[i++];)
+								if (item != this) item.pause();
+					}
+				}
+				// 音频处理
+				var audios = this.rtf.getElementsByTagName('audio');
+				for (var audio of audios)
+					audio.onerror = function() {
+						_ts.$emit('error', {
+							source: 'audio',
+							target: this
+						});
+					}
+				// 表格处理
+				if (this.autoscroll) {
+					var tables = this.rtf.getElementsByTagName('table');
+					for (var table of tables) {
+						let div = document.createElement('div');
+						div.style.overflow = 'scroll';
+						table.parentNode.replaceChild(div, table);
+						div.appendChild(table);
+					}
+				}
+				if (!append) this.document.appendChild(this.rtf);
+				this.$nextTick(() => {
+					this.nodes = [1];
+					this.$emit('load');
+				});
+				setTimeout(() => this.showAm = '', 500);
+				// #endif
+				// #ifndef APP-PLUS-NVUE
+				// #ifndef H5 || MP-360
+				var nodes;
+				if (!html) return this.nodes = [];
+				var parser = new Parser(html, this);
+				// 缓存读取
+				if (this.useCache) {
+					var hashVal = hash(html);
+					if (cache[hashVal])
+						nodes = cache[hashVal];
+					else {
+						nodes = parser.parse();
+						cache[hashVal] = nodes;
+					}
+				} else nodes = parser.parse();
+				this.$emit('parse', nodes);
+				if (append) this.nodes = this.nodes.concat(nodes);
+				else this.nodes = nodes;
+				if (nodes.length && nodes.title && this.autosetTitle)
+					uni.setNavigationBarTitle({
+						title: nodes.title
+					})
+				if (this.imgList) this.imgList.length = 0;
+				this.videoContexts = [];
+				this.$nextTick(() => {
+					(function f(cs) {
+						for (var i = cs.length; i--;) {
+							if (cs[i].top) {
+								cs[i].controls = [];
+								cs[i].init();
+								f(cs[i].$children);
+							}
+						}
+					})(this.$children)
+					this.$emit('load');
+				})
+				// #endif
+				var height;
+				clearInterval(this._timer);
+				this._timer = setInterval(() => {
+					// #ifdef H5 || MP-360
+					this.rect = this.rtf.getBoundingClientRect();
+					// #endif
+					// #ifndef H5 || MP-360
+					uni.createSelectorQuery().in(this)
+						.select('#_top').boundingClientRect().exec(res => {
+							if (!res) return;
+							this.rect = res[0];
+							// #endif
+							if (this.rect.height == height) {
+								this.$emit('ready', this.rect)
+								clearInterval(this._timer);
+							}
+							height = this.rect.height;
+							// #ifndef H5 || MP-360
+						});
+					// #endif
+				}, 350);
+				if (this.showWithAnimation && !append) this.showAm = 'animation:_show .5s';
+				// #endif
+			},
+			// 获取文本内容
+			getText(ns = this.nodes) {
+				var txt = '';
+				// #ifdef APP-PLUS-NVUE
+				txt = this._text;
+				// #endif
+				// #ifdef H5 || MP-360
+				txt = this.rtf.innerText;
+				// #endif
+				// #ifndef H5 || APP-PLUS-NVUE || MP-360
+				for (var i = 0, n; n = ns[i++];) {
+					if (n.type == 'text') txt += n.text.replace(/&nbsp;/g, '\u00A0').replace(/&lt;/g, '<').replace(/&gt;/g, '>')
+						.replace(/&amp;/g, '&');
+					else if (n.type == 'br') txt += '\n';
+					else {
+						// 块级标签前后加换行
+						var block = n.name == 'p' || n.name == 'div' || n.name == 'tr' || n.name == 'li' || (n.name[0] == 'h' && n.name[1] >
+							'0' && n.name[1] < '7');
+						if (block && txt && txt[txt.length - 1] != '\n') txt += '\n';
+						if (n.children) txt += this.getText(n.children);
+						if (block && txt[txt.length - 1] != '\n') txt += '\n';
+						else if (n.name == 'td' || n.name == 'th') txt += '\t';
+					}
+				}
+				// #endif
+				return txt;
+			},
+			// 锚点跳转
+			in (obj) {
+				if (obj.page && obj.selector && obj.scrollTop) this._in = obj;
+			},
+			navigateTo(obj) {
+				if (!this.useAnchor) return obj.fail && obj.fail('Anchor is disabled');
+				// #ifdef APP-PLUS-NVUE
+				if (!obj.id)
+					weexDom.scrollToElement(this.$refs.web);
+				else
+					this.$refs.web.evalJs('var pos=document.getElementById("' + obj.id +
+						'");if(pos)post({action:"linkpress",href:"#",offset:pos.offsetTop+' + (obj.offset || 0) + '})');
+				obj.success && obj.success();
+				// #endif
+				// #ifndef APP-PLUS-NVUE
+				var d = ' ';
+				// #ifdef MP-WEIXIN || MP-QQ || MP-TOUTIAO
+				d = '>>>';
+				// #endif
+				var selector = uni.createSelectorQuery().in(this._in ? this._in.page : this).select((this._in ? this._in.selector :
+					'#_top') + (obj.id ? `${d}#${obj.id},${this._in?this._in.selector:'#_top'}${d}.${obj.id}` : '')).boundingClientRect();
+				if (this._in) selector.select(this._in.selector).scrollOffset().select(this._in.selector).boundingClientRect();
+				else selector.selectViewport().scrollOffset();
+				selector.exec(res => {
+					if (!res[0]) return obj.fail && obj.fail('Label not found')
+					var scrollTop = res[1].scrollTop + res[0].top - (res[2] ? res[2].top : 0) + (obj.offset || 0);
+					if (this._in) this._in.page[this._in.scrollTop] = scrollTop;
+					else uni.pageScrollTo({
+						scrollTop,
+						duration: 300
+					})
+					obj.success && obj.success();
+				})
+				// #endif
+			},
+			// 获取视频对象
+			getVideoContext(id) {
+				// #ifndef APP-PLUS-NVUE
+				if (!id) return this.videoContexts;
+				else
+					for (var i = this.videoContexts.length; i--;)
+						if (this.videoContexts[i].id == id) return this.videoContexts[i];
+				// #endif
+			},
+			// #ifdef H5 || APP-PLUS-NVUE || MP-360
+			_handleHtml(html, append) {
+				if (!append) {
+					// 处理 tag-style 和 userAgentStyles
+					var style = '<style>@keyframes _show{0%{opacity:0}100%{opacity:1}}img{max-width:100%}';
+					for (var item in cfg.userAgentStyles)
+						style += `${item}{${cfg.userAgentStyles[item]}}`;
+					for (item in this.tagStyle)
+						style += `${item}{${this.tagStyle[item]}}`;
+					style += '</style>';
+					html = style + html;
+				}
+				// 处理 rpx
+				if (html.includes('rpx'))
+					html = html.replace(/[0-9.]+\s*rpx/g, $ => (parseFloat($) * windowWidth / 750) + 'px');
+				return html;
+			},
+			// #endif
+			// #ifdef APP-PLUS-NVUE
+			_message(e) {
+				// 接收 web-view 消息
+				var d = e.detail.data[0];
+				switch (d.action) {
+					case 'load':
+						this.$emit('load');
+						this.height = d.height;
+						this._text = d.text;
+						break;
+					case 'getTitle':
+						if (this.autosetTitle)
+							uni.setNavigationBarTitle({
+								title: d.title
+							})
+						break;
+					case 'getImgList':
+						this.imgList.length = 0;
+						for (var i = d.imgList.length; i--;)
+							this.imgList.setItem(i, d.imgList[i]);
+						break;
+					case 'preview':
+						var preview = true;
+						d.img.ignore = () => preview = false;
+						this.$emit('imgtap', d.img);
+						if (preview)
+							uni.previewImage({
+								current: d.img.i,
+								urls: this.imgList
+							})
+						break;
+					case 'linkpress':
+						var jump = true,
+							href = d.href;
+						this.$emit('linkpress', {
+							href,
+							ignore: () => jump = false
+						})
+						if (jump && href) {
+							if (href[0] == '#') {
+								if (this.useAnchor)
+									weexDom.scrollToElement(this.$refs.web, {
+										offset: d.offset
+									})
+							} else if (href.includes('://'))
+								plus.runtime.openWeb(href);
+							else
+								uni.navigateTo({
+									url: href
+								})
+						}
+						break;
+					case 'error':
+						if (d.source == 'img' && cfg.errorImg)
+							this.imgList.setItem(d.target.i, cfg.errorImg);
+						this.$emit('error', {
+							source: d.source,
+							target: d.target
+						})
+						break;
+					case 'ready':
+						this.height = d.height;
+						if (d.ready) uni.createSelectorQuery().in(this).select('#_top').boundingClientRect().exec(res => {
+							this.rect = res[0];
+							this.$emit('ready', res[0]);
+						})
+						break;
+					case 'click':
+						this.$emit('click');
+						this.$emit('tap');
+				}
+			},
+			// #endif
+		}
+	}
+</script>
+
+<style>
+	@keyframes _show {
+		0% {
+			opacity: 0;
+		}
+
+		100% {
+			opacity: 1;
+		}
+	}
+
+	/* #ifdef MP-WEIXIN */
+	:host {
+		display: block;
+		overflow: scroll;
+		-webkit-overflow-scrolling: touch;
+	}
+
+	/* #endif */
+</style>

+ 97 - 0
components/jyf-parser/libs/CssHandler.js

@@ -0,0 +1,97 @@
+const cfg = require('./config.js'),
+	isLetter = c => (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
+
+function CssHandler(tagStyle) {
+	var styles = Object.assign(Object.create(null), cfg.userAgentStyles);
+	for (var item in tagStyle)
+		styles[item] = (styles[item] ? styles[item] + ';' : '') + tagStyle[item];
+	this.styles = styles;
+}
+CssHandler.prototype.getStyle = function(data) {
+	this.styles = new parser(data, this.styles).parse();
+}
+CssHandler.prototype.match = function(name, attrs) {
+	var tmp, matched = (tmp = this.styles[name]) ? tmp + ';' : '';
+	if (attrs.class) {
+		var items = attrs.class.split(' ');
+		for (var i = 0, item; item = items[i]; i++)
+			if (tmp = this.styles['.' + item])
+				matched += tmp + ';';
+	}
+	if (tmp = this.styles['#' + attrs.id])
+		matched += tmp + ';';
+	return matched;
+}
+module.exports = CssHandler;
+
+function parser(data, init) {
+	this.data = data;
+	this.floor = 0;
+	this.i = 0;
+	this.list = [];
+	this.res = init;
+	this.state = this.Space;
+}
+parser.prototype.parse = function() {
+	for (var c; c = this.data[this.i]; this.i++)
+		this.state(c);
+	return this.res;
+}
+parser.prototype.section = function() {
+	return this.data.substring(this.start, this.i);
+}
+// 状态机
+parser.prototype.Space = function(c) {
+	if (c == '.' || c == '#' || isLetter(c)) {
+		this.start = this.i;
+		this.state = this.Name;
+	} else if (c == '/' && this.data[this.i + 1] == '*')
+		this.Comment();
+	else if (!cfg.blankChar[c] && c != ';')
+		this.state = this.Ignore;
+}
+parser.prototype.Comment = function() {
+	this.i = this.data.indexOf('*/', this.i) + 1;
+	if (!this.i) this.i = this.data.length;
+	this.state = this.Space;
+}
+parser.prototype.Ignore = function(c) {
+	if (c == '{') this.floor++;
+	else if (c == '}' && !--this.floor) this.state = this.Space;
+}
+parser.prototype.Name = function(c) {
+	if (cfg.blankChar[c]) {
+		this.list.push(this.section());
+		this.state = this.NameSpace;
+	} else if (c == '{') {
+		this.list.push(this.section());
+		this.Content();
+	} else if (c == ',') {
+		this.list.push(this.section());
+		this.Comma();
+	} else if (!isLetter(c) && (c < '0' || c > '9') && c != '-' && c != '_')
+		this.state = this.Ignore;
+}
+parser.prototype.NameSpace = function(c) {
+	if (c == '{') this.Content();
+	else if (c == ',') this.Comma();
+	else if (!cfg.blankChar[c]) this.state = this.Ignore;
+}
+parser.prototype.Comma = function() {
+	while (cfg.blankChar[this.data[++this.i]]);
+	if (this.data[this.i] == '{') this.Content();
+	else {
+		this.start = this.i--;
+		this.state = this.Name;
+	}
+}
+parser.prototype.Content = function() {
+	this.start = ++this.i;
+	if ((this.i = this.data.indexOf('}', this.i)) == -1) this.i = this.data.length;
+	var content = this.section();
+	for (var i = 0, item; item = this.list[i++];)
+		if (this.res[item]) this.res[item] += ';' + content;
+		else this.res[item] = content;
+	this.list = [];
+	this.state = this.Space;
+}

+ 535 - 0
components/jyf-parser/libs/MpHtmlParser.js

@@ -0,0 +1,535 @@
+/**
+ * html 解析器
+ * @tutorial https://github.com/jin-yufeng/Parser
+ * @version 20200728
+ * @author JinYufeng
+ * @listens MIT
+ */
+const cfg = require('./config.js'),
+	blankChar = cfg.blankChar,
+	CssHandler = require('./CssHandler.js'),
+	windowWidth = uni.getSystemInfoSync().windowWidth;
+var emoji;
+
+function MpHtmlParser(data, options = {}) {
+	this.attrs = {};
+	this.CssHandler = new CssHandler(options.tagStyle, windowWidth);
+	this.data = data;
+	this.domain = options.domain;
+	this.DOM = [];
+	this.i = this.start = this.audioNum = this.imgNum = this.videoNum = 0;
+	options.prot = (this.domain || '').includes('://') ? this.domain.split('://')[0] : 'http';
+	this.options = options;
+	this.state = this.Text;
+	this.STACK = [];
+	// 工具函数
+	this.bubble = () => {
+		for (var i = this.STACK.length, item; item = this.STACK[--i];) {
+			if (cfg.richOnlyTags[item.name]) {
+				if (item.name == 'table' && !Object.hasOwnProperty.call(item, 'c')) item.c = 1;
+				return false;
+			}
+			item.c = 1;
+		}
+		return true;
+	}
+	this.decode = (val, amp) => {
+		var i = -1,
+			j, en;
+		while (1) {
+			if ((i = val.indexOf('&', i + 1)) == -1) break;
+			if ((j = val.indexOf(';', i + 2)) == -1) break;
+			if (val[i + 1] == '#') {
+				en = parseInt((val[i + 2] == 'x' ? '0' : '') + val.substring(i + 2, j));
+				if (!isNaN(en)) val = val.substr(0, i) + String.fromCharCode(en) + val.substr(j + 1);
+			} else {
+				en = val.substring(i + 1, j);
+				if (cfg.entities[en] || en == amp)
+					val = val.substr(0, i) + (cfg.entities[en] || '&') + val.substr(j + 1);
+			}
+		}
+		return val;
+	}
+	this.getUrl = url => {
+		if (url[0] == '/') {
+			if (url[1] == '/') url = this.options.prot + ':' + url;
+			else if (this.domain) url = this.domain + url;
+		} else if (this.domain && url.indexOf('data:') != 0 && !url.includes('://'))
+			url = this.domain + '/' + url;
+		return url;
+	}
+	this.isClose = () => this.data[this.i] == '>' || (this.data[this.i] == '/' && this.data[this.i + 1] == '>');
+	this.section = () => this.data.substring(this.start, this.i);
+	this.parent = () => this.STACK[this.STACK.length - 1];
+	this.siblings = () => this.STACK.length ? this.parent().children : this.DOM;
+}
+MpHtmlParser.prototype.parse = function() {
+	if (emoji) this.data = emoji.parseEmoji(this.data);
+	for (var c; c = this.data[this.i]; this.i++)
+		this.state(c);
+	if (this.state == this.Text) this.setText();
+	while (this.STACK.length) this.popNode(this.STACK.pop());
+	return this.DOM;
+}
+// 设置属性
+MpHtmlParser.prototype.setAttr = function() {
+	var name = this.attrName.toLowerCase(),
+		val = this.attrVal;
+	if (cfg.boolAttrs[name]) this.attrs[name] = 'T';
+	else if (val) {
+		if (name == 'src' || (name == 'data-src' && !this.attrs.src)) this.attrs.src = this.getUrl(this.decode(val, 'amp'));
+		else if (name == 'href' || name == 'style') this.attrs[name] = this.decode(val, 'amp');
+		else if (name.substr(0, 5) != 'data-') this.attrs[name] = val;
+	}
+	this.attrVal = '';
+	while (blankChar[this.data[this.i]]) this.i++;
+	if (this.isClose()) this.setNode();
+	else {
+		this.start = this.i;
+		this.state = this.AttrName;
+	}
+}
+// 设置文本节点
+MpHtmlParser.prototype.setText = function() {
+	var back, text = this.section();
+	if (!text) return;
+	text = (cfg.onText && cfg.onText(text, () => back = true)) || text;
+	if (back) {
+		this.data = this.data.substr(0, this.start) + text + this.data.substr(this.i);
+		let j = this.start + text.length;
+		for (this.i = this.start; this.i < j; this.i++) this.state(this.data[this.i]);
+		return;
+	}
+	if (!this.pre) {
+		// 合并空白符
+		var flag, tmp = [];
+		for (let i = text.length, c; c = text[--i];)
+			if (!blankChar[c]) {
+				tmp.unshift(c);
+				if (!flag) flag = 1;
+			} else {
+				if (tmp[0] != ' ') tmp.unshift(' ');
+				if (c == '\n' && flag == void 0) flag = 0;
+			}
+		if (flag == 0) return;
+		text = tmp.join('');
+	}
+	this.siblings().push({
+		type: 'text',
+		text: this.decode(text)
+	});
+}
+// 设置元素节点
+MpHtmlParser.prototype.setNode = function() {
+	var node = {
+			name: this.tagName.toLowerCase(),
+			attrs: this.attrs
+		},
+		close = cfg.selfClosingTags[node.name];
+	if (this.options.nodes.length) node.type = 'node';
+	this.attrs = {};
+	if (!cfg.ignoreTags[node.name]) {
+		// 处理属性
+		var attrs = node.attrs,
+			style = this.CssHandler.match(node.name, attrs, node) + (attrs.style || ''),
+			styleObj = {};
+		if (attrs.id) {
+			if (this.options.compress & 1) attrs.id = void 0;
+			else if (this.options.useAnchor) this.bubble();
+		}
+		if ((this.options.compress & 2) && attrs.class) attrs.class = void 0;
+		switch (node.name) {
+			case 'a':
+			case 'ad': // #ifdef APP-PLUS
+			case 'iframe':
+				// #endif
+				this.bubble();
+				break;
+			case 'font':
+				if (attrs.color) {
+					styleObj['color'] = attrs.color;
+					attrs.color = void 0;
+				}
+				if (attrs.face) {
+					styleObj['font-family'] = attrs.face;
+					attrs.face = void 0;
+				}
+				if (attrs.size) {
+					var size = parseInt(attrs.size);
+					if (size < 1) size = 1;
+					else if (size > 7) size = 7;
+					var map = ['xx-small', 'x-small', 'small', 'medium', 'large', 'x-large', 'xx-large'];
+					styleObj['font-size'] = map[size - 1];
+					attrs.size = void 0;
+				}
+				break;
+			case 'embed':
+				// #ifndef APP-PLUS
+				var src = node.attrs.src || '',
+					type = node.attrs.type || '';
+				if (type.includes('video') || src.includes('.mp4') || src.includes('.3gp') || src.includes('.m3u8'))
+					node.name = 'video';
+				else if (type.includes('audio') || src.includes('.m4a') || src.includes('.wav') || src.includes('.mp3') || src.includes(
+						'.aac'))
+					node.name = 'audio';
+				else break;
+				if (node.attrs.autostart)
+					node.attrs.autoplay = 'T';
+				node.attrs.controls = 'T';
+				// #endif
+				// #ifdef APP-PLUS
+				this.bubble();
+				break;
+				// #endif
+			case 'video':
+			case 'audio':
+				if (!attrs.id) attrs.id = node.name + (++this[`${node.name}Num`]);
+				else this[`${node.name}Num`]++;
+				if (node.name == 'video') {
+					if (this.videoNum > 3)
+						node.lazyLoad = 1;
+					if (attrs.width) {
+						styleObj.width = parseFloat(attrs.width) + (attrs.width.includes('%') ? '%' : 'px');
+						attrs.width = void 0;
+					}
+					if (attrs.height) {
+						styleObj.height = parseFloat(attrs.height) + (attrs.height.includes('%') ? '%' : 'px');
+						attrs.height = void 0;
+					}
+				}
+				if (!attrs.controls && !attrs.autoplay) attrs.controls = 'T';
+				attrs.source = [];
+				if (attrs.src) {
+					attrs.source.push(attrs.src);
+					attrs.src = void 0;
+				}
+				this.bubble();
+				break;
+			case 'td':
+			case 'th':
+				if (attrs.colspan || attrs.rowspan)
+					for (var k = this.STACK.length, item; item = this.STACK[--k];)
+						if (item.name == 'table') {
+							item.c = void 0;
+							break;
+						}
+		}
+		if (attrs.align) {
+			styleObj['text-align'] = attrs.align;
+			attrs.align = void 0;
+		}
+		// 压缩 style
+		var styles = style.split(';');
+		style = '';
+		for (var i = 0, len = styles.length; i < len; i++) {
+			var info = styles[i].split(':');
+			if (info.length < 2) continue;
+			let key = info[0].trim().toLowerCase(),
+				value = info.slice(1).join(':').trim();
+			if (value[0] == '-' || value.includes('safe'))
+				style += `;${key}:${value}`;
+			else if (!styleObj[key] || value.includes('import') || !styleObj[key].includes('import'))
+				styleObj[key] = value;
+		}
+		if (node.name == 'img') {
+			if (attrs.src && !attrs.ignore) {
+				if (this.bubble())
+					attrs.i = (this.imgNum++).toString();
+				else attrs.ignore = 'T';
+			}
+			if (attrs.ignore) {
+				style += ';-webkit-touch-callout:none';
+				styleObj['max-width'] = '100%';
+			}
+			var width;
+			if (styleObj.width) width = styleObj.width;
+			else if (attrs.width) width = attrs.width.includes('%') ? attrs.width : attrs.width + 'px';
+			if (width) {
+				styleObj.width = width;
+				attrs.width = '100%';
+				if (parseInt(width) > windowWidth) {
+					styleObj.height = '';
+					if (attrs.height) attrs.height = void 0;
+				}
+			}
+			if (styleObj.height) {
+				attrs.height = styleObj.height;
+				styleObj.height = '';
+			} else if (attrs.height && !attrs.height.includes('%'))
+				attrs.height += 'px';
+		}
+		for (var key in styleObj) {
+			var value = styleObj[key];
+			if (!value) continue;
+			if (key.includes('flex') || key == 'order' || key == 'self-align') node.c = 1;
+			// 填充链接
+			if (value.includes('url')) {
+				var j = value.indexOf('(');
+				if (j++ != -1) {
+					while (value[j] == '"' || value[j] == "'" || blankChar[value[j]]) j++;
+					value = value.substr(0, j) + this.getUrl(value.substr(j));
+				}
+			}
+			// 转换 rpx
+			else if (value.includes('rpx'))
+				value = value.replace(/[0-9.]+\s*rpx/g, $ => parseFloat($) * windowWidth / 750 + 'px');
+			else if (key == 'white-space' && value.includes('pre') && !close)
+				this.pre = node.pre = true;
+			style += `;${key}:${value}`;
+		}
+		style = style.substr(1);
+		if (style) attrs.style = style;
+		if (!close) {
+			node.children = [];
+			if (node.name == 'pre' && cfg.highlight) {
+				this.remove(node);
+				this.pre = node.pre = true;
+			}
+			this.siblings().push(node);
+			this.STACK.push(node);
+		} else if (!cfg.filter || cfg.filter(node, this) != false)
+			this.siblings().push(node);
+	} else {
+		if (!close) this.remove(node);
+		else if (node.name == 'source') {
+			var parent = this.parent();
+			if (parent && (parent.name == 'video' || parent.name == 'audio') && node.attrs.src)
+				parent.attrs.source.push(node.attrs.src);
+		} else if (node.name == 'base' && !this.domain) this.domain = node.attrs.href;
+	}
+	if (this.data[this.i] == '/') this.i++;
+	this.start = this.i + 1;
+	this.state = this.Text;
+}
+// 移除标签
+MpHtmlParser.prototype.remove = function(node) {
+	var name = node.name,
+		j = this.i;
+	// 处理 svg
+	var handleSvg = () => {
+		var src = this.data.substring(j, this.i + 1);
+		if (!node.attrs.xmlns) src = ' xmlns="http://www.w3.org/2000/svg"' + src;
+		var i = j;
+		while (this.data[j] != '<') j--;
+		src = this.data.substring(j, i).replace("viewbox", "viewBox") + src;
+		var parent = this.parent();
+		if (node.attrs.width == '100%' && parent && (parent.attrs.style || '').includes('inline'))
+			parent.attrs.style = 'width:300px;max-width:100%;' + parent.attrs.style;
+		this.siblings().push({
+			name: 'img',
+			attrs: {
+				src: 'data:image/svg+xml;utf8,' + src.replace(/#/g, '%23'),
+				style: (/vertical[^;]+/.exec(node.attrs.style) || []).shift(),
+				ignore: 'T'
+			}
+		})
+	}
+	if (node.name == 'svg' && this.data[j] == '/') return handleSvg(this.i++);
+	while (1) {
+		if ((this.i = this.data.indexOf('</', this.i + 1)) == -1) {
+			if (name == 'pre' || name == 'svg') this.i = j;
+			else this.i = this.data.length;
+			return;
+		}
+		this.start = (this.i += 2);
+		while (!blankChar[this.data[this.i]] && !this.isClose()) this.i++;
+		if (this.section().toLowerCase() == name) {
+			// 代码块高亮
+			if (name == 'pre') {
+				this.data = this.data.substr(0, j + 1) + cfg.highlight(this.data.substring(j + 1, this.i - 5), node.attrs) + this.data
+					.substr(this.i - 5);
+				return this.i = j;
+			} else if (name == 'style')
+				this.CssHandler.getStyle(this.data.substring(j + 1, this.i - 7));
+			else if (name == 'title')
+				this.DOM.title = this.data.substring(j + 1, this.i - 7);
+			if ((this.i = this.data.indexOf('>', this.i)) == -1) this.i = this.data.length;
+			if (name == 'svg') handleSvg();
+			return;
+		}
+	}
+}
+// 节点出栈处理
+MpHtmlParser.prototype.popNode = function(node) {
+	// 空白符处理
+	if (node.pre) {
+		node.pre = this.pre = void 0;
+		for (let i = this.STACK.length; i--;)
+			if (this.STACK[i].pre)
+				this.pre = true;
+	}
+	var siblings = this.siblings(),
+		len = siblings.length,
+		childs = node.children;
+	if (node.name == 'head' || (cfg.filter && cfg.filter(node, this) == false))
+		return siblings.pop();
+	var attrs = node.attrs;
+	// 替换一些标签名
+	if (cfg.blockTags[node.name]) node.name = 'div';
+	else if (!cfg.trustTags[node.name]) node.name = 'span';
+	// 处理列表
+	if (node.c && (node.name == 'ul' || node.name == 'ol')) {
+		if ((node.attrs.style || '').includes('list-style:none')) {
+			for (let i = 0, child; child = childs[i++];)
+				if (child.name == 'li')
+					child.name = 'div';
+		} else if (node.name == 'ul') {
+			var floor = 1;
+			for (let i = this.STACK.length; i--;)
+				if (this.STACK[i].name == 'ul') floor++;
+			if (floor != 1)
+				for (let i = childs.length; i--;)
+					childs[i].floor = floor;
+		} else {
+			for (let i = 0, num = 1, child; child = childs[i++];)
+				if (child.name == 'li') {
+					child.type = 'ol';
+					child.num = ((num, type) => {
+						if (type == 'a') return String.fromCharCode(97 + (num - 1) % 26);
+						if (type == 'A') return String.fromCharCode(65 + (num - 1) % 26);
+						if (type == 'i' || type == 'I') {
+							num = (num - 1) % 99 + 1;
+							var one = ['I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX'],
+								ten = ['X', 'XX', 'XXX', 'XL', 'L', 'LX', 'LXX', 'LXXX', 'XC'],
+								res = (ten[Math.floor(num / 10) - 1] || '') + (one[num % 10 - 1] || '');
+							if (type == 'i') return res.toLowerCase();
+							return res;
+						}
+						return num;
+					})(num++, attrs.type) + '.';
+				}
+		}
+	}
+	// 处理表格的边框
+	if (node.name == 'table') {
+		var padding = attrs.cellpadding,
+			spacing = attrs.cellspacing,
+			border = attrs.border;
+		if (node.c) {
+			this.bubble();
+			attrs.style = (attrs.style || '') + ';display:table';
+			if (!padding) padding = 2;
+			if (!spacing) spacing = 2;
+		}
+		if (border) attrs.style = `border:${border}px solid gray;${attrs.style || ''}`;
+		if (spacing) attrs.style = `border-spacing:${spacing}px;${attrs.style || ''}`;
+		if (border || padding || node.c)
+			(function f(ns) {
+				for (var i = 0, n; n = ns[i]; i++) {
+					if (n.type == 'text') continue;
+					var style = n.attrs.style || '';
+					if (node.c && n.name[0] == 't') {
+						n.c = 1;
+						style += ';display:table-' + (n.name == 'th' || n.name == 'td' ? 'cell' : (n.name == 'tr' ? 'row' : 'row-group'));
+					}
+					if (n.name == 'th' || n.name == 'td') {
+						if (border) style = `border:${border}px solid gray;${style}`;
+						if (padding) style = `padding:${padding}px;${style}`;
+					} else f(n.children || []);
+					if (style) n.attrs.style = style;
+				}
+			})(childs)
+		if (this.options.autoscroll) {
+			var table = Object.assign({}, node);
+			node.name = 'div';
+			node.attrs = {
+				style: 'overflow:scroll'
+			}
+			node.children = [table];
+		}
+	}
+	this.CssHandler.pop && this.CssHandler.pop(node);
+	// 自动压缩
+	if (node.name == 'div' && !Object.keys(attrs).length && childs.length == 1 && childs[0].name == 'div')
+		siblings[len - 1] = childs[0];
+}
+// 状态机
+MpHtmlParser.prototype.Text = function(c) {
+	if (c == '<') {
+		var next = this.data[this.i + 1],
+			isLetter = c => (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
+		if (isLetter(next)) {
+			this.setText();
+			this.start = this.i + 1;
+			this.state = this.TagName;
+		} else if (next == '/') {
+			this.setText();
+			if (isLetter(this.data[++this.i + 1])) {
+				this.start = this.i + 1;
+				this.state = this.EndTag;
+			} else this.Comment();
+		} else if (next == '!' || next == '?') {
+			this.setText();
+			this.Comment();
+		}
+	}
+}
+MpHtmlParser.prototype.Comment = function() {
+	var key;
+	if (this.data.substring(this.i + 2, this.i + 4) == '--') key = '-->';
+	else if (this.data.substring(this.i + 2, this.i + 9) == '[CDATA[') key = ']]>';
+	else key = '>';
+	if ((this.i = this.data.indexOf(key, this.i + 2)) == -1) this.i = this.data.length;
+	else this.i += key.length - 1;
+	this.start = this.i + 1;
+	this.state = this.Text;
+}
+MpHtmlParser.prototype.TagName = function(c) {
+	if (blankChar[c]) {
+		this.tagName = this.section();
+		while (blankChar[this.data[this.i]]) this.i++;
+		if (this.isClose()) this.setNode();
+		else {
+			this.start = this.i;
+			this.state = this.AttrName;
+		}
+	} else if (this.isClose()) {
+		this.tagName = this.section();
+		this.setNode();
+	}
+}
+MpHtmlParser.prototype.AttrName = function(c) {
+	if (c == '=' || blankChar[c] || this.isClose()) {
+		this.attrName = this.section();
+		if (blankChar[c])
+			while (blankChar[this.data[++this.i]]);
+		if (this.data[this.i] == '=') {
+			while (blankChar[this.data[++this.i]]);
+			this.start = this.i--;
+			this.state = this.AttrValue;
+		} else this.setAttr();
+	}
+}
+MpHtmlParser.prototype.AttrValue = function(c) {
+	if (c == '"' || c == "'") {
+		this.start++;
+		if ((this.i = this.data.indexOf(c, this.i + 1)) == -1) return this.i = this.data.length;
+		this.attrVal = this.section();
+		this.i++;
+	} else {
+		for (; !blankChar[this.data[this.i]] && !this.isClose(); this.i++);
+		this.attrVal = this.section();
+	}
+	this.setAttr();
+}
+MpHtmlParser.prototype.EndTag = function(c) {
+	if (blankChar[c] || c == '>' || c == '/') {
+		var name = this.section().toLowerCase();
+		for (var i = this.STACK.length; i--;)
+			if (this.STACK[i].name == name) break;
+		if (i != -1) {
+			var node;
+			while ((node = this.STACK.pop()).name != name) this.popNode(node);
+			this.popNode(node);
+		} else if (name == 'p' || name == 'br')
+			this.siblings().push({
+				name,
+				attrs: {}
+			});
+		this.i = this.data.indexOf('>', this.i);
+		this.start = this.i + 1;
+		if (this.i == -1) this.i = this.data.length;
+		else this.state = this.Text;
+	}
+}
+module.exports = MpHtmlParser;

+ 80 - 0
components/jyf-parser/libs/config.js

@@ -0,0 +1,80 @@
+/* 配置文件 */
+var cfg = {
+	// 出错占位图
+	errorImg: null,
+	// 过滤器函数
+	filter: null,
+	// 代码高亮函数
+	highlight: null,
+	// 文本处理函数
+	onText: null,
+	// 实体编码列表
+	entities: {
+		quot: '"',
+		apos: "'",
+		semi: ';',
+		nbsp: '\xA0',
+		ensp: '\u2002',
+		emsp: '\u2003',
+		ndash: '–',
+		mdash: '—',
+		middot: '·',
+		lsquo: '‘',
+		rsquo: '’',
+		ldquo: '“',
+		rdquo: '”',
+		bull: '•',
+		hellip: '…'
+	},
+	blankChar: makeMap(' ,\xA0,\t,\r,\n,\f'),
+	boolAttrs: makeMap('allowfullscreen,autoplay,autostart,controls,ignore,loop,muted'),
+	// 块级标签,将被转为 div
+	blockTags: makeMap('address,article,aside,body,caption,center,cite,footer,header,html,nav,pre,section'),
+	// 将被移除的标签
+	ignoreTags: makeMap('area,base,canvas,frame,iframe,input,link,map,meta,param,script,source,style,svg,textarea,title,track,wbr'),
+	// 只能被 rich-text 显示的标签
+	richOnlyTags: makeMap('a,colgroup,fieldset,legend,table'),
+	// 自闭合的标签
+	selfClosingTags: makeMap('area,base,br,col,circle,ellipse,embed,frame,hr,img,input,line,link,meta,param,path,polygon,rect,source,track,use,wbr'),
+	// 信任的标签
+	trustTags: makeMap('a,abbr,ad,audio,b,blockquote,br,code,col,colgroup,dd,del,dl,dt,div,em,fieldset,h1,h2,h3,h4,h5,h6,hr,i,img,ins,label,legend,li,ol,p,q,source,span,strong,sub,sup,table,tbody,td,tfoot,th,thead,tr,title,ul,video'),
+	// 默认的标签样式
+	userAgentStyles: {
+		address: 'font-style:italic',
+		big: 'display:inline;font-size:1.2em',
+		blockquote: 'background-color:#f6f6f6;border-left:3px solid #dbdbdb;color:#6c6c6c;padding:5px 0 5px 10px',
+		caption: 'display:table-caption;text-align:center',
+		center: 'text-align:center',
+		cite: 'font-style:italic',
+		dd: 'margin-left:40px',
+		mark: 'background-color:yellow',
+		pre: 'font-family:monospace;white-space:pre;overflow:scroll',
+		s: 'text-decoration:line-through',
+		small: 'display:inline;font-size:0.8em',
+		u: 'text-decoration:underline'
+	}
+}
+
+function makeMap(str) {
+	var map = Object.create(null),
+		list = str.split(',');
+	for (var i = list.length; i--;)
+		map[list[i]] = true;
+	return map;
+}
+
+// #ifdef MP-WEIXIN
+if (wx.canIUse('editor')) {
+	cfg.blockTags.pre = void 0;
+	cfg.ignoreTags.rp = true;
+	Object.assign(cfg.richOnlyTags, makeMap('bdi,bdo,caption,rt,ruby'));
+	Object.assign(cfg.trustTags, makeMap('bdi,bdo,caption,pre,rt,ruby'));
+}
+// #endif
+
+// #ifdef APP-PLUS
+cfg.ignoreTags.iframe = void 0;
+Object.assign(cfg.trustTags, makeMap('embed,iframe'));
+// #endif
+
+module.exports = cfg;

+ 22 - 0
components/jyf-parser/libs/handler.wxs

@@ -0,0 +1,22 @@
+var inline = {
+	abbr: 1,
+	b: 1,
+	big: 1,
+	code: 1,
+	del: 1,
+	em: 1,
+	i: 1,
+	ins: 1,
+	label: 1,
+	q: 1,
+	small: 1,
+	span: 1,
+	strong: 1,
+	sub: 1,
+	sup: 1
+}
+module.exports = {
+	use: function(item) {
+		return !item.c && !inline[item.name] && (item.attrs.style || '').indexOf('display:inline') == -1
+	}
+}

+ 501 - 0
components/jyf-parser/libs/trees.vue

@@ -0,0 +1,501 @@
+<template>
+	<view :class="'interlayer '+(c||'')" :style="s">
+		<block v-for="(n, i) in nodes" v-bind:key="i">
+			<!--图片-->
+			<view v-if="n.name=='img'" :class="'_img '+n.attrs.class" :style="n.attrs.style" :data-attrs="n.attrs" @tap="imgtap">
+				<rich-text v-if="ctrl[i]!=0" :nodes="[{attrs:{src:loading&&(ctrl[i]||0)<2?loading:(lazyLoad&&!ctrl[i]?placeholder:(ctrl[i]==3?errorImg:n.attrs.src||'')),alt:n.attrs.alt||'',width:n.attrs.width||'',style:'-webkit-touch-callout:none;max-width:100%;display:block'+(n.attrs.height?';height:'+n.attrs.height:'')},name:'img'}]" />
+				<image class="_image" :src="lazyLoad&&!ctrl[i]?placeholder:n.attrs.src" :lazy-load="lazyLoad"
+				 :show-menu-by-longpress="!n.attrs.ignore" :data-i="i" :data-index="n.attrs.i" data-source="img" @load="loadImg"
+				 @error="error" />
+			</view>
+			<!--文本-->
+			<text v-else-if="n.type=='text'" decode>{{n.text}}</text>
+			<!--#ifndef MP-BAIDU-->
+			<text v-else-if="n.name=='br'">\n</text>
+			<!--#endif-->
+			<!--视频-->
+			<view v-else-if="((n.lazyLoad&&!n.attrs.autoplay)||(n.name=='video'&&!loadVideo))&&ctrl[i]==undefined" :id="n.attrs.id" :class="'_video '+(n.attrs.class||'')"
+			 :style="n.attrs.style" :data-i="i" @tap="_loadVideo" />
+			<video v-else-if="n.name=='video'" :id="n.attrs.id" :class="n.attrs.class" :style="n.attrs.style" :autoplay="n.attrs.autoplay||ctrl[i]==0"
+			 :controls="n.attrs.controls" :loop="n.attrs.loop" :muted="n.attrs.muted" :poster="n.attrs.poster" :src="n.attrs.source[ctrl[i]||0]"
+			 :unit-id="n.attrs['unit-id']" :data-id="n.attrs.id" :data-i="i" data-source="video" @error="error" @play="play" />
+			<!--音频-->
+			<audio v-else-if="n.name=='audio'" :ref="n.attrs.id" :class="n.attrs.class" :style="n.attrs.style" :author="n.attrs.author"
+			 :autoplay="n.attrs.autoplay" :controls="n.attrs.controls" :loop="n.attrs.loop" :name="n.attrs.name" :poster="n.attrs.poster"
+			 :src="n.attrs.source[ctrl[i]||0]" :data-i="i" :data-id="n.attrs.id" data-source="audio"
+			 @error.native="error" @play.native="play" />
+			<!--链接-->
+			<view v-else-if="n.name=='a'" :id="n.attrs.id" :class="'_a '+(n.attrs.class||'')" hover-class="_hover" :style="n.attrs.style"
+			 :data-attrs="n.attrs" @tap="linkpress">
+				<trees class="_span" c="_span" :nodes="n.children" />
+			</view>
+			<!--广告-->
+			<!--<ad v-else-if="n.name=='ad'" :class="n.attrs.class" :style="n.attrs.style" :unit-id="n.attrs['unit-id']" :appid="n.attrs.appid" :apid="n.attrs.apid" :type="n.attrs.type" :adpid="n.attrs.adpid" data-source="ad" @error="error" />-->
+			<!--列表-->
+			<view v-else-if="n.name=='li'" :id="n.attrs.id" :class="n.attrs.class" :style="(n.attrs.style||'')+';display:flex;flex-direction:row'">
+				<view v-if="n.type=='ol'" class="_ol-bef">{{n.num}}</view>
+				<view v-else class="_ul-bef">
+					<view v-if="n.floor%3==0" class="_ul-p1">█</view>
+					<view v-else-if="n.floor%3==2" class="_ul-p2" />
+					<view v-else class="_ul-p1" style="border-radius:50%">█</view>
+				</view>
+				<trees class="_li" c="_li" :nodes="n.children" :lazyLoad="lazyLoad" :loading="loading" />
+			</view>
+			<!--表格-->
+			<view v-else-if="n.name=='table'&&n.c" :id="n.attrs.id" :class="n.attrs.class" :style="(n.attrs.style||'')+';display:table'">
+				<view v-for="(tbody, o) in n.children" v-bind:key="o" :class="tbody.attrs.class" :style="(tbody.attrs.style||'')+(tbody.name[0]=='t'?';display:table-'+(tbody.name=='tr'?'row':'row-group'):'')">
+					<view v-for="(tr, p) in tbody.children" v-bind:key="p" :class="tr.attrs.class" :style="(tr.attrs.style||'')+(tr.name[0]=='t'?';display:table-'+(tr.name=='tr'?'row':'cell'):'')">
+						<trees v-if="tr.name=='td'" :nodes="tr.children" />
+						<trees v-else v-for="(td, q) in tr.children" v-bind:key="q" :class="td.attrs.class" :c="td.attrs.class" :style="(td.attrs.style||'')+(td.name[0]=='t'?';display:table-'+(td.name=='tr'?'row':'cell'):'')"
+						 :s="(td.attrs.style||'')+(td.name[0]=='t'?';display:table-'+(td.name=='tr'?'row':'cell'):'')" :nodes="td.children" />
+					</view>
+				</view>
+			</view>
+			<!--#ifdef APP-PLUS-->
+			<iframe v-else-if="n.name=='iframe'" :style="n.attrs.style" :allowfullscreen="n.attrs.allowfullscreen" :frameborder="n.attrs.frameborder"
+			 :width="n.attrs.width" :height="n.attrs.height" :src="n.attrs.src" />
+			<embed v-else-if="n.name=='embed'" :style="n.attrs.style" :width="n.attrs.width" :height="n.attrs.height" :src="n.attrs.src" />
+			<!--#endif-->
+			<!--富文本-->
+			<!--#ifdef MP-WEIXIN || MP-QQ || APP-PLUS-->
+			<rich-text v-else-if="handler.use(n)" :id="n.attrs.id" :class="'_p __'+n.name" :nodes="[n]" />
+			<!--#endif-->
+			<!--#ifndef MP-WEIXIN || MP-QQ || APP-PLUS-->
+			<rich-text v-else-if="!n.c" :id="n.attrs.id" :nodes="[n]" style="display:inline" />
+			<!--#endif-->
+			<trees v-else :class="(n.attrs.id||'')+' _'+n.name+' '+(n.attrs.class||'')" :c="(n.attrs.id||'')+' _'+n.name+' '+(n.attrs.class||'')"
+			 :style="n.attrs.style" :s="n.attrs.style" :nodes="n.children" :lazyLoad="lazyLoad" :loading="loading" />
+		</block>
+	</view>
+</template>
+<script module="handler" lang="wxs" src="./handler.wxs"></script>
+<script>
+	global.Parser = {};
+	import trees from './trees'
+	const errorImg = require('../libs/config.js').errorImg;
+	export default {
+		components: {
+			trees
+		},
+		name: 'trees',
+		data() {
+			return {
+				ctrl: [],
+				placeholder: 'data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="300" height="225"/>',
+				errorImg,
+				loadVideo: typeof plus == 'undefined',
+				// #ifndef MP-ALIPAY
+				c: '',
+				s: ''
+				// #endif
+			}
+		},
+		props: {
+			nodes: Array,
+			lazyLoad: Boolean,
+			loading: String,
+			// #ifdef MP-ALIPAY
+			c: String,
+			s: String
+			// #endif
+		},
+		mounted() {
+			for (this.top = this.$parent; this.top.$options.name != 'parser'; this.top = this.top.$parent);
+			this.init();
+		},
+		// #ifdef APP-PLUS
+		beforeDestroy() {
+			this.observer && this.observer.disconnect();
+		},
+		// #endif
+		methods: {
+			init() {
+				for (var i = this.nodes.length, n; n = this.nodes[--i];) {
+					if (n.name == 'img') {
+						this.top.imgList.setItem(n.attrs.i, n.attrs.src);
+						// #ifdef APP-PLUS
+						if (this.lazyLoad && !this.observer) {
+							this.observer = uni.createIntersectionObserver(this).relativeToViewport({
+								top: 500,
+								bottom: 500
+							});
+							setTimeout(() => {
+								this.observer.observe('._img', res => {
+									if (res.intersectionRatio) {
+										for (var j = this.nodes.length; j--;)
+											if (this.nodes[j].name == 'img')
+												this.$set(this.ctrl, j, 1);
+										this.observer.disconnect();
+									}
+								})
+							}, 0)
+						}
+						// #endif
+					} else if (n.name == 'video' || n.name == 'audio') {
+						var ctx;
+						if (n.name == 'video') {
+							ctx = uni.createVideoContext(n.attrs.id
+								// #ifndef MP-BAIDU
+								, this
+								// #endif
+							);
+						} else if (this.$refs[n.attrs.id])
+							ctx = this.$refs[n.attrs.id][0];
+						if (ctx) {
+							ctx.id = n.attrs.id;
+							this.top.videoContexts.push(ctx);
+						}
+					}
+				}
+				// #ifdef APP-PLUS
+				// APP 上避免 video 错位需要延时渲染
+				setTimeout(() => {
+					this.loadVideo = true;
+				}, 1000)
+				// #endif
+			},
+			play(e) {
+				var contexts = this.top.videoContexts;
+				if (contexts.length > 1 && this.top.autopause)
+					for (var i = contexts.length; i--;)
+						if (contexts[i].id != e.currentTarget.dataset.id)
+							contexts[i].pause();
+			},
+			imgtap(e) {
+				var attrs = e.currentTarget.dataset.attrs;
+				if (!attrs.ignore) {
+					var preview = true,
+						data = {
+							id: e.target.id,
+							src: attrs.src,
+							ignore: () => preview = false
+						};
+					global.Parser.onImgtap && global.Parser.onImgtap(data);
+					this.top.$emit('imgtap', data);
+					if (preview) {
+						var urls = this.top.imgList,
+							current = urls[attrs.i] ? parseInt(attrs.i) : (urls = [attrs.src], 0);
+						uni.previewImage({
+							current,
+							urls
+						})
+					}
+				}
+			},
+			loadImg(e) {
+				var i = e.currentTarget.dataset.i;
+				if (this.lazyLoad && !this.ctrl[i]) {
+					// #ifdef QUICKAPP-WEBVIEW
+					this.$set(this.ctrl, i, 0);
+					this.$nextTick(function() {
+						// #endif
+						// #ifndef APP-PLUS
+						this.$set(this.ctrl, i, 1);
+						// #endif
+						// #ifdef QUICKAPP-WEBVIEW
+					})
+					// #endif
+				} else if (this.loading && this.ctrl[i] != 2) {
+					// #ifdef QUICKAPP-WEBVIEW
+					this.$set(this.ctrl, i, 0);
+					this.$nextTick(function() {
+						// #endif
+						this.$set(this.ctrl, i, 2);
+						// #ifdef QUICKAPP-WEBVIEW
+					})
+					// #endif
+				}
+			},
+			linkpress(e) {
+				var jump = true,
+					attrs = e.currentTarget.dataset.attrs;
+				attrs.ignore = () => jump = false;
+				global.Parser.onLinkpress && global.Parser.onLinkpress(attrs);
+				this.top.$emit('linkpress', attrs);
+				if (jump) {
+					// #ifdef MP
+					if (attrs['app-id']) {
+						return uni.navigateToMiniProgram({
+							appId: attrs['app-id'],
+							path: attrs.path
+						})
+					}
+					// #endif
+					if (attrs.href) {
+						if (attrs.href[0] == '#') {
+							if (this.top.useAnchor)
+								this.top.navigateTo({
+									id: attrs.href.substring(1)
+								})
+						} else if (attrs.href.indexOf('http') == 0 || attrs.href.indexOf('//') == 0) {
+							// #ifdef APP-PLUS
+							plus.runtime.openWeb(attrs.href);
+							// #endif
+							// #ifndef APP-PLUS
+							uni.setClipboardData({
+								data: attrs.href,
+								success: () =>
+									uni.showToast({
+										title: '链接已复制'
+									})
+							})
+							// #endif
+						} else
+							uni.navigateTo({
+								url: attrs.href,
+								fail() {
+									uni.switchTab({
+										url: attrs.href,
+									})
+								}
+							})
+					}
+				}
+			},
+			error(e) {
+				var target = e.currentTarget,
+					source = target.dataset.source,
+					i = target.dataset.i;
+				if (source == 'video' || source == 'audio') {
+					// 加载其他 source
+					var index = this.ctrl[i] ? this.ctrl[i].i + 1 : 1;
+					if (index < this.nodes[i].attrs.source.length)
+						this.$set(this.ctrl, i, index);
+					if (e.detail.__args__)
+						e.detail = e.detail.__args__[0];
+				} else if (errorImg && source == 'img') {
+					this.top.imgList.setItem(target.dataset.index, errorImg);
+					this.$set(this.ctrl, i, 3);
+				}
+				this.top && this.top.$emit('error', {
+					source,
+					target,
+					errMsg: e.detail.errMsg
+				});
+			},
+			_loadVideo(e) {
+				this.$set(this.ctrl, e.target.dataset.i, 0);
+			}
+		}
+	}
+</script>
+
+<style>
+	/* 在这里引入自定义样式 */
+
+	/* 链接和图片效果 */
+	._a {
+		display: inline;
+		padding: 1.5px 0 1.5px 0;
+		color: #366092;
+		word-break: break-all;
+	}
+
+	._hover {
+		text-decoration: underline;
+		opacity: 0.7;
+	}
+
+	._img {
+		display: inline-block;
+		max-width: 100%;
+		overflow: hidden;
+	}
+
+	/* #ifdef MP-WEIXIN */
+	:host {
+		display: inline;
+	}
+
+	/* #endif */
+
+	/* #ifndef MP-ALIPAY || APP-PLUS */
+	.interlayer {
+		display: inherit;
+		flex-direction: inherit;
+		flex-wrap: inherit;
+		align-content: inherit;
+		align-items: inherit;
+		justify-content: inherit;
+		width: 100%;
+		white-space: inherit;
+	}
+
+	/* #endif */
+
+	._b,
+	._strong {
+		font-weight: bold;
+	}
+
+	/* #ifndef MP-ALIPAY */
+	._blockquote,
+	._div,
+	._p,
+	._ol,
+	._ul,
+	._li {
+		display: block;
+	}
+	
+	/* #endif */
+
+	._code {
+		font-family: monospace;
+	}
+
+	._del {
+		text-decoration: line-through;
+	}
+
+	._em,
+	._i {
+		font-style: italic;
+	}
+
+	._h1 {
+		font-size: 2em;
+	}
+
+	._h2 {
+		font-size: 1.5em;
+	}
+
+	._h3 {
+		font-size: 1.17em;
+	}
+
+	._h5 {
+		font-size: 0.83em;
+	}
+
+	._h6 {
+		font-size: 0.67em;
+	}
+
+	._h1,
+	._h2,
+	._h3,
+	._h4,
+	._h5,
+	._h6 {
+		display: block;
+		font-weight: bold;
+	}
+
+	._image {
+		display: block;
+		width: 100%;
+		height: 360px;
+		margin-top: -360px;
+		opacity: 0;
+	}
+
+	._ins {
+		text-decoration: underline;
+	}
+
+	._li {
+		flex: 1;
+		width: 0;
+	}
+
+	._ol-bef {
+		width: 36px;
+		margin-right: 5px;
+		text-align: right;
+	}
+
+	._ul-bef {
+		display: block;
+		margin: 0 12px 0 23px;
+		line-height: normal;
+	}
+
+	._ol-bef,
+	._ul-bef {
+		flex: none;
+		user-select: none;
+	}
+
+	._ul-p1 {
+		display: inline-block;
+		width: 0.3em;
+		height: 0.3em;
+		overflow: hidden;
+		line-height: 0.3em;
+	}
+
+	._ul-p2 {
+		display: inline-block;
+		width: 0.23em;
+		height: 0.23em;
+		border: 0.05em solid black;
+		border-radius: 50%;
+	}
+
+	._q::before {
+		content: '"';
+	}
+
+	._q::after {
+		content: '"';
+	}
+
+	._sub {
+		font-size: smaller;
+		vertical-align: sub;
+	}
+
+	._sup {
+		font-size: smaller;
+		vertical-align: super;
+	}
+
+	/* #ifdef MP-ALIPAY || APP-PLUS || QUICKAPP-WEBVIEW */
+	._abbr,
+	._b,
+	._code,
+	._del,
+	._em,
+	._i,
+	._ins,
+	._label,
+	._q,
+	._span,
+	._strong,
+	._sub,
+	._sup {
+		display: inline;
+	}
+
+	/* #endif */
+
+	/* #ifdef MP-WEIXIN || MP-QQ */
+	.__bdo,
+	.__bdi,
+	.__ruby,
+	.__rt {
+		display: inline-block;
+	}
+
+	/* #endif */
+	._video {
+		position: relative;
+		display: inline-block;
+		width: 300px;
+		height: 225px;
+		background-color: black;
+	}
+
+	._video::after {
+		position: absolute;
+		top: 50%;
+		left: 50%;
+		margin: -15px 0 0 -15px;
+		content: '';
+		border-color: transparent transparent transparent white;
+		border-style: solid;
+		border-width: 15px 0 15px 30px;
+	}
+</style>

+ 421 - 0
components/newlist/nowList.vue

@@ -0,0 +1,421 @@
+<template>
+	<view class="other">
+		<view class="other-1">大家还在拼</view>
+		
+		<view class="preferred_item" v-for="item in recommendedlist" @click.stop="ToKaiTuan(item)">
+			<view class="flex_item" style="overflow: hidden;">
+				<view class="tlist-img">
+					<view class="leftImgIcon">AA团</view>
+					<!-- <view class="leftImgIcon" v-if="sid == 129">达人团</view> -->
+					<view class="img"><image :src="item.image" mode="scaleToFill"></image></view>
+				</view>
+				<view class="tlist-img " v-for="imgItem in item.images">
+					<view class="img"><image :src="imgItem" mode="scaleToFill"></image></view>
+				</view>
+			</view>
+			<view class="goods_name">
+				<view class="goods_title flex_item">
+					<view class="text">{{ item.min_people }}人团</view>
+					<view class="title">{{ item.title }}</view>
+				</view>
+				<view class="goods-height">
+					<!-- <view class="goods_num clamp">{{ item.info }}</view> -->
+					<view class="flex goods-peplo">
+						<view class="goods-tip flex_item">
+							<view class="peplo">库存剩{{ item.percent | parseIntTo }}%</view>
+							<view class="make">{{ item.mark }}</view>
+						</view>
+						<view class="right flex_item">
+							<image src="/static/icon/hot.png" mode="aspectFill"></image>
+							<text>已拼{{ item.sales }}份</text>
+						</view>
+					</view>
+				</view>
+				<view class="price flex">
+					<view class="price_list">
+						<view class="price-red">
+							<text>单人仅付:</text>
+							<text class="moneyIcon">¥</text>
+							<text class="money">{{ item.price }}</text>
+							<text class="moneyType">/{{ item.unit_name }}</text>
+							<!-- <text class="outMoney">¥{{ item.product_price }}</text> -->
+						</view>
+					</view>
+					<view class="img position-relative" @click.stop="ToKaiTuan(item)">去开团</view>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+export default {
+	props: {
+		recommendedlist: {
+			type: Array,
+			default: function() {
+				return {
+				};
+			}
+		}
+	},
+	data() {
+		return {};
+	},
+	filters: {
+		parseIntTo(percent) {
+			percent = +percent * 100;
+			if (percent % 1 === 0) {
+				return percent;
+			} else {
+				percent = percent.toFixed(1);
+				return percent;
+			}
+		}
+	},
+	methods: {
+		// 去开团
+		ToKaiTuan(item) {
+			console.log(8754847)
+			let id = item.id;
+			uni.navigateTo({
+				url: '/pages/product/productGroup?id=' + id
+			});
+		},
+	}
+};
+</script>
+
+<style lang="scss">
+.other{
+		.other-1{
+			font-size:27rpx;
+			color:#333333;
+			line-height: 55rpx;
+		}
+		
+		.preferred_item {
+			width: 100%;
+			height: 100%;
+			padding: 25rpx 25rpx;
+			position: relative;
+			background-color: #FFFFFF;
+			border-radius: 15rpx;
+			margin-bottom: 15rpx;
+			.tlist-img {
+				width: 225rpx;
+				position: relative;
+				margin-right: 15rpx;
+				.leftImgIcon {
+					position: absolute;
+					top: 0;
+					left: 0;
+					font-size: 22rpx;
+					font-family: PingFangSC;
+					color: rgba(148, 71, 34, 1);
+					background: rgba(254, 242, 111, 1);
+					z-index: 99;
+					border-radius: 5rpx;
+					padding: 5rpx 10rpx;
+				}
+				.img {
+					width: 210rpx;
+					height: 210rpx;
+					image {
+						width: 100%;
+						height: 100%;
+						border-radius: 20rpx;
+					}
+				}
+				.stock {
+					margin-top: 13rpx;
+					font-size: 26rpx;
+					background: #fff1ee;
+					width: 100%;
+					color: #fb4912;
+					padding: 6rpx 0;
+					border-radius: 5rpx;
+					justify-content: center;
+					align-items: center;
+					position: absolute;
+					left: 0;
+					bottom: 0;
+					.img {
+						width: 20rpx;
+						height: 20rpx;
+						flex-shrink: 0;
+					}
+					.stock-num {
+						padding-left: 7rpx;
+						font-size: 22rpx;
+						border-radius: 5rpx;
+						height: 32rpx;
+						line-height: 32rpx;
+					}
+				}
+			}
+			.goods_name {
+				.goods_title {
+					padding-top: 15rpx;
+					color:rgba(0,0,0,1);
+					// white-space: nowrap;
+					// overflow: hidden;
+					// text-overflow: ellipsis;
+					font-size:32rpx;
+					color: $font-color-dark;
+					// height: 70rpx;
+					align-items: baseline;
+					.text{
+						border-radius: 8rpx;
+						border: 2rpx solid #FF1A27;
+						color: #FF1A27;
+						padding:0rpx 10rpx;
+						font-size: 26rpx !important;
+						margin-right: 15rpx;
+						
+					}
+					.title {
+						width: 80%;
+						overflow : hidden;
+						text-overflow: ellipsis;
+						display: -webkit-box;
+						-webkit-line-clamp: 2;
+						-webkit-box-orient: vertical;
+					}
+				}
+				.goods-height {
+					min-height: 60rpx;
+				}
+				.goods_num {
+					font-size: 26rpx;
+					color: #8f8f97;
+					padding-bottom: 15rpx;
+				}
+				.goods-peplo {
+					height: 45rpx;
+					margin-top: 15rpx;
+					.right {
+						color:#8e8e8e;
+						font-size: 24rpx;
+						width:195rpx;
+						image {
+							width: 30rpx;
+							height: 33rpx;
+							margin-right: 15rpx;
+						}
+					}
+					.goods-tip {
+						.peplo {
+							background:linear-gradient(14deg,rgba(255,116,37,1),rgba(255,30,41,1));
+							padding: 5rpx 10rpx;
+							color: #ffffff;
+							border-top-left-radius:8rpx;
+							border-bottom-left-radius: 8rpx;
+						}
+						.make {
+							background-color: #fef26f;
+							color: #944722;
+							border-top-right-radius: 8rpx;
+							border-bottom-right-radius: 8rpx;
+						}
+						.make,
+						.peplo {
+							font-size: $font-sm;
+							padding: 5rpx 10rpx;
+						}
+					}
+				}
+				.price {
+					font-size: 28rpx;
+					position: relative;
+					padding-top: 15rpx;
+					.price_list {
+						.price-red {
+							font-size: 30rpx !important;
+							font-family: Source Han Sans CN;
+							color: rgba(253, 27, 42, 1);
+							font-size: $font-base;
+							font-weight: bold;
+							.moneyIcon {
+								font-weight: normal !important; 
+							}
+							.money {
+								font-size: 58rpx;
+							}
+							.moneyType {
+								font-weight: 400;
+							}
+							.outMoney {
+								font-weight: 400;
+								text-decoration: line-through;
+								color: rgba(142, 142, 142, 1);
+							}
+						}
+						.price-green {
+							color: #2dbd59;
+							font-size: 26rpx !important;
+							font-weight: bold;
+							text {
+								background: linear-gradient(45deg, rgba(21, 197, 52, 1), rgba(21, 197, 52, 1));
+								color: #ffffff;
+								padding: 0rpx 10rpx;
+								border-radius: 7rpx;
+								font-size: 24rpx !important;
+								margin-left: 15rpx;
+							}
+						}
+					}
+					.img {
+						width: 265rpx;
+						height: 74rpx;
+						line-height: 74rpx;
+						// background:linear-gradient(14deg,rgba(255,116,37,1),rgba(255,30,41,1));
+						background: linear-gradient(270deg, rgba(181,116,242, 1) 0%, rgba(139,86,254, 1) 100%);
+						border-radius: 99rpx;
+						color: #ffffff;
+						font-size: $font-lg;
+						text-align: center;
+					}
+					.img1{
+						background-color: #D3D3D3;
+						width: 265rpx;
+						height: 74rpx;
+						line-height: 74rpx;
+						border-radius: 99rpx;
+						color: #ffffff;
+						font-size: $font-lg;
+						text-align: center;
+					}
+					.tomorrow {
+						background: #29a66e;
+						color: #ffffff;
+						border-radius: 25rpx;
+						padding: 10rpx 25rpx;
+					}
+				}
+			}
+		}
+		
+		
+		.other-2{
+			width: 100%;
+			background:#ffffff;
+			padding: 10rpx 15rpx ;
+			border-radius: 20rpx;
+			.content-row{
+				padding: 10rpx 0;
+				.row-1{
+					width: 210rpx;
+					position: relative;
+					margin-right: 20rpx;
+					text-align: center;
+					.row-1-1{
+						text-align: center;
+						height:40rpx;
+						background:#fff1ee;
+						border-radius:6rpx;
+						font-size:22rpx;
+						font-weight:500;
+						color:#fb4912;
+						line-height:40rpx;
+						image{
+							width: 23rpx;
+							height: 23rpx;
+							margin-right: 5rpx;
+						}
+					}
+					.img1{
+						width:170rpx;
+						height:170rpx;
+						border-radius:10rpx;
+					}
+					.img2{
+						position: absolute;
+						top: 0;
+						left: 16rpx;
+						width:80rpx;
+						height:32rpx;
+						border-radius:5px;
+					}
+				}
+				.row-2{
+					padding: 20rpx 0 20rpx 0;
+					width:calc(100% - 210rpx);
+					position: relative;
+					
+					border-bottom: 1px solid #EAEAEA;
+					.word-1{
+						font-size:32rpx;
+						font-weight:bold;
+						color:#141821;
+						margin-left: 10rpx;
+						overflow: hidden;
+						text-overflow: ellipsis;
+						white-space: nowrap;
+					}
+					.word-2{
+						margin-top: 10rpx;
+						font-size:24rpx;
+						color:#979797;
+					}
+					.word-3{
+						margin-top: 10rpx;
+						position: relative;
+						font-size:20rpx;
+						color:#ffffff;
+						image{
+							width: 235rpx;
+							height: 50rpx;
+						}
+						.word-3-1{
+							position: absolute;
+							top:8px;
+							left:2px;
+							width: 230rpx;
+							text-align: center;
+							.word-3-1-1{
+								display: inline-block;
+								color:#FD1B2A;
+								width: 50%;
+							}
+							
+						}
+					}
+					.word-4{
+						margin-top: 15rpx;
+						font-size:23rpx;
+						color:#fd1b2a;
+						margin-left: 10rpx;
+						text{
+							font-size:23rpx;
+							font-weight:bold;
+						}
+						.word-4-1{
+							font-size:36rpx;
+						}
+					}
+					.word-5{
+						margin-top: 15rpx;
+						font-size:21rpx;
+						color:#868686;
+					}
+					.button{
+						width:145rpx;
+						height:60rpx;
+						background:linear-gradient(14deg,#ff7425,#ff1e29);
+						border-radius:30rpx;
+						font-size:27rpx;
+						font-weight:bold;
+						color:#ffffff;
+						line-height:60rpx;
+						text-align: center;
+						
+						position: absolute;
+						bottom: 20rpx;
+						right: 0;
+					}
+				}
+			}
+		}
+	}
+</style>

+ 68 - 0
components/returnButton.vue

@@ -0,0 +1,68 @@
+<template>
+	<view class="content">
+		<view class="retun-Box" @click="navTo('/pages/order/order?state=0')">
+			<image class="return-img" src=".././static/tabBar/dingdan.png"></image>
+			<view class="return-text">订单</view>
+		</view>
+		<view class="retun-Box" @click="GoHome">
+			<image class="return-img" src=".././static/tabBar/home.png" ></image>
+			<view class="return-text">首页</view>
+		</view>
+	</view>
+</template>
+
+<script>
+export default {
+	props: {
+		
+	},
+	data() {
+		return {
+			
+		}
+	},
+	methods: {
+		navTo(url) {
+			uni.navigateTo({
+				url: url
+			})
+		},
+		GoHome() {
+			// uni.navigateTo({
+			// 	url: '/pages/groupBooking/index'
+			// })
+			uni.switchTab({
+				url: '/pages/index/index'
+			})
+		}
+	}
+}
+</script>
+
+<style lang="scss">
+.content {
+	position: fixed;
+	right: 0;
+	bottom: 330rpx;
+	.retun-Box {
+		border-radius: 50%;
+		background: #FFFFFF;
+		width: 100rpx;
+		height: 100rpx;
+		margin-top: 30rpx;
+		display: flex;
+		flex-direction: column;
+		align-items: center;
+		justify-content: center;
+		box-shadow: 0 6rpx 20rpx #888888;
+		.return-img {
+			width: 40rpx;
+			height: 40rpx;
+		}
+		.return-text {
+			font-size: $font-sm;
+			margin-top: 8rpx;
+		}
+	}
+}
+</style>

+ 196 - 0
components/share.vue

@@ -0,0 +1,196 @@
+<template>
+	<view v-if="show" class="mask" @click="toggleMask" @touchmove.stop.prevent="stopPrevent" :style="{ backgroundColor: backgroundColor }">
+		<view
+			class="mask-content"
+			@click.stop.prevent="stopPrevent"
+			:style="[
+				{
+					height: config.height,
+					transform: transform
+				}
+			]"
+		>
+			<scroll-view class="view-content" scroll-y>
+				<view class="share-header">分享到</view>
+				<view class="share-list">
+					<view v-for="(item, index) in shareList" :key="index" class="share-item" @click="shareToFriend(item.text)">
+						<image class="itemImage" :src="item.icon" mode=""></image>
+						<text class="itemText">{{ item.text }}</text>
+					</view>
+				</view>
+			</scroll-view>
+			<view class="bottomButtom b-t" @click="toggleMask">取消</view>
+		</view>
+	</view>
+</template>
+
+<script>
+export default {
+	data() {
+		return {
+			transform: 'translateY(50vh)',
+			timer: 0,
+			backgroundColor: 'rgba(0,0,0,0)',
+			show: false,
+			config: {}
+		};
+	},
+	props: {
+		contentHeight: {
+			type: Number,
+			default: 0
+		},
+		//是否是tabbar页面
+		hasTabbar: {
+			type: Boolean,
+			default: false
+		},
+		shareList: {
+			type: Array,
+			default: function() {
+				return [];
+			}
+		}
+	},
+	created() {
+		const height = uni.upx2px(this.contentHeight) + 'px';
+		this.config = {
+			height: height,
+			transform: `translateY(${height})`,
+			backgroundColor: 'rgba(0,0,0,.4)'
+		};
+		this.transform = this.config.transform;
+	},
+	methods: {
+		toggleMask() {
+			//防止高频点击
+			if (this.timer == 1) {
+				return;
+			}
+			this.timer = 1;
+			setTimeout(() => {
+				this.timer = 0;
+			}, 500);
+
+			if (this.show) {
+				this.transform = this.config.transform;
+				this.backgroundColor = 'rgba(0,0,0,0)';
+				setTimeout(() => {
+					this.show = false;
+					this.hasTabbar && uni.showTabBar();
+				}, 200);
+				return;
+			}
+
+			this.show = true;
+			//等待mask重绘完成执行
+			if (this.hasTabbar) {
+				uni.hideTabBar({
+					success: () => {
+						setTimeout(() => {
+							this.backgroundColor = this.config.backgroundColor;
+							this.transform = 'translateY(0px)';
+						}, 10);
+					}
+				});
+			} else {
+				setTimeout(() => {
+					this.backgroundColor = this.config.backgroundColor;
+					this.transform = 'translateY(0px)';
+				}, 10);
+			}
+		},
+		//防止冒泡和滚动穿透
+		stopPrevent() {},
+		//分享操作
+		shareToFriend(type) {
+			this.$api.msg(`分享给${type}`);
+			this.toggleMask();
+		}
+	}
+};
+</script>
+
+<style lang="scss">
+.mask {
+	position: fixed;
+	left: 0;
+	top: 0;
+	right: 0;
+	bottom: 0;
+	display: flex;
+	justify-content: center;
+	align-items: flex-end;
+	z-index: 998;
+	transition: 0.3s;
+	.bottomButtom {
+		position: absolute;
+		left: 0;
+		bottom: 0;
+		display: flex;
+		justify-content: center;
+		align-items: center;
+		width: 100%;
+		height: 90rpx;
+		background: #fff;
+		z-index: 9;
+		font-size: $font-base + 2rpx;
+		color: $font-color-dark;
+	}
+}
+.mask-content {
+	width: 100%;
+	height: 580rpx;
+	transition: 0.3s;
+	background: #fff;
+	&.has-bottom {
+		padding-bottom: 90rpx;
+	}
+	.view-content {
+		height: 100%;
+	}
+}
+.share-header {
+	height: 110rpx;
+	font-size: $font-base + 2rpx;
+	color: font-color-dark;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	padding-top: 10rpx;
+	&:before,
+	&:after {
+		content: '';
+		width: 240rpx;
+		heighg: 0;
+		border-top: 1px solid $border-color-base;
+		transform: scaleY(0.5);
+		margin-right: 30rpx;
+	}
+	&:after {
+		margin-left: 30rpx;
+		margin-right: 0;
+	}
+}
+.share-list {
+	display: flex;
+	flex-wrap: wrap;
+}
+.share-item {
+	min-width: 33.33%;
+	display: flex;
+	flex-direction: column;
+	justify-content: center;
+	align-items: center;
+	height: 180rpx;
+	.itemImage {
+		width: 80rpx;
+		height: 80rpx;
+		margin-bottom: 16rpx;
+	}
+	.itemText {
+		font-size: $font-base;
+		color: $font-color-base;
+	}
+}
+</style>

+ 218 - 0
components/ss-calendar/ss-calendar.vue

@@ -0,0 +1,218 @@
+<template>
+	<view class="calendar__wrap">
+		<view class="header">
+			<view class="current-date">{{ currentDate }}</view>
+		</view>
+		<view class="body">
+			<view class="weeks">
+				<view class="week__item" v-for="week in weeks" :key="week">{{ week }}</view>
+			</view>
+			<view class="day__list">
+				<view class="day__item" v-for="(item, index) in dateData" :key="index">
+					<view class="checked-box" :class="[checksClass]" v-if="item === 'checked'">
+						<text class="checked iconfont iconfavor" v-if="!checksIcon"></text>
+						<image v-else :src="checksIcon" mode="aspectFit"></image>
+						<view class="check_text" v-if="checkTextShow">{{ dayLoad(index) }}</view>
+					</view>
+					<text class="current-box" :class="[item === day ? (actionClass ? actionClass : 'current') : '']" v-else>{{ item }}</text>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+export default {
+	props: {
+		//选中的日期数据
+		checks: {
+			type: Array,
+			default() {
+				// 例子[1,2,3,4],表示本月1,2,3,4号4天被选中
+				return [];
+			}
+		},
+		// 选中物品的样式类
+		checksClass: {
+			type: String,
+			default: ''
+		},
+		// 选中时图标
+		checksIcon: {
+			type: String
+		},
+		// 选中时是否显示文字
+		checkTextShow: {
+			type: Boolean, 
+			default: false
+		},
+		// 表示当前日期的样式
+		actionClass: {
+			type: String,
+			default: ''
+		}
+	},
+	data() {
+		const { year, month, day } = this.getDate();
+		const dateData = this.getDateData(year, month);
+		const noDay = ( new Date(year,month-1,1)).getDay();
+		return {
+			year,
+			month,
+			day,
+			dateData,
+			noDay,//本月1号是星期几
+			weeks: ['日', '一', '二', '三', '四', '五', '六'],
+		};
+	},
+	computed: {
+		// 获取当前日期
+		currentDate() {
+			return `${this.year}-${this.format(this.month)}`;
+		}
+	},
+	watch: {
+		checks(val) {
+			const { year, month } = this.getDate();
+			const dateData = this.getDateData(year, month);
+			// 保存当前月份信息
+			this.dateData = dateData;
+		},
+	},
+	methods: {
+		// 重新计算时间
+		dayLoad: function(value) {
+			return value + 1 - this.noDay;
+		},
+		// 获取当前日期
+		getDate(current) {
+			const date = current ? new Date(current) : new Date();
+			const year = date.getFullYear();
+			// 月份值默认从0开始
+			const month = date.getMonth() + 1;
+			const day = date.getDate();
+			return {
+				year,
+				month,
+				day
+			};
+		},
+		// 日期处理
+		getDateData(year, month) {
+			// 新增月份时需要减少1个月
+			const date = new Date(year,month-1,1);
+			const firstDayWeek = date.getDay();
+			const data = [...this.getEmptys(firstDayWeek), ...this.getDays(firstDayWeek)];
+			return data;
+		},
+		// 查询日期列表有几个空格
+		getEmptys(count) {
+			let arr = [];
+			if (count) {
+				for (let i = 0; i < count; i++) {
+					arr.push('');
+				}
+			}
+			return arr;
+		},
+		getLastDay() {
+			let { year, month } = this.getDate();
+			month += 1;
+			if (month > 11) {
+				year += 1;
+				month = 1;
+			}
+			let firstDayTimeStamp = new Date(`${year}/${month}/1`).getTime();
+			let oneDayTimeStamp = 24 * 60 * 60 * 1000;
+			let lastDay = new Date(firstDayTimeStamp - oneDayTimeStamp).getDate();
+			return lastDay;
+		},
+		getDays() {
+			const lastDay = this.getLastDay();
+			const days = [];
+			for (let i = 1; i <= lastDay; i++) {
+				days.push(this.checks.includes(i) ? 'checked' : i);
+			}
+			return days;
+		},
+		format(num) {
+			return num < 10 ? `0${num}` : num;
+		}
+	}
+};
+</script>
+
+<style lang="scss" scoped>
+.calendar__wrap {
+	background-color: #fff;
+	color: $uni-text-color;
+	.header {
+		padding: 0 24rpx;
+		.current-date {
+			text-align: center;
+			font-size: $font-lg + 2rpx;
+			// border-bottom: 2rpx solid #eee;
+			padding: 32rpx 0;
+		}
+	}
+	.body {
+		.weeks {
+			display: flex;
+			font-size: $font-lg;
+			padding: 10rpx 0;
+			background-color: #f4f7ff;
+			.week__item {
+				flex: 1;
+				text-align: center;
+			}
+		}
+		.day__list {
+			display: flex;
+			flex-wrap: wrap;
+			.day__item {
+				display: flex;
+				justify-content: center;
+				width: 14.285%;
+				text-align: center;
+				padding: 30rpx 0;
+				font-size: 34rpx;
+				color: $font-color-light;
+				.checked-box,
+				.current-box {
+					display: flex;
+					align-items: center;
+					justify-content: center;
+					border-radius: 100%;
+					box-sizing: border-box;
+					position: relative;
+				}
+				image,
+				.checked-box,
+				.current-box,
+				.check_text {
+					width: 56rpx;
+					height: 56rpx;
+					line-height: 56rpx;
+				}
+				image,
+				.check_text {
+					position: absolute;
+					top: 0;
+					left: 0;
+				}
+				.checked {
+					font-size: 40rpx;
+					background-color: #3f9dff;
+					color: #fff;
+				}
+				.current {
+					padding: 12rpx;
+					background-color: $background-color;
+					color: #fff;
+					font-size: 28rpx;
+				}
+			}
+		}
+	}
+}
+</style>

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

@@ -0,0 +1,1201 @@
+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) {
+                                    options.cbResult(res.tempFilePath)
+                                }
+                            },
+                            fail: function (res) {
+                                if (options.cbResult) {
+                                    options.cbResult(res)
+                                }
+                            },
+                            complete: function () {
+                                if (options.showLoading){
+                                    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

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

@@ -0,0 +1,210 @@
+<template xlang="wxml" minapp="mpvue">
+	<view class="tki-qrcode">
+		<!-- #ifndef MP-ALIPAY -->
+		<canvas class="tki-qrcode-canvas" :canvas-id="cid" :style="{width:cpSize+'px',height:cpSize+'px'}" />
+		<!-- #endif -->
+		<!-- #ifdef MP-ALIPAY -->
+		<canvas :id="cid" :width="cpSize" :height="cpSize" class="tki-qrcode-canvas" />
+		<!-- #endif -->
+		<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, // 是否是自定义组件
+					showLoading: that.showLoading, // 是否显示loading
+					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>

+ 122 - 0
components/uni-badge/uni-badge.vue

@@ -0,0 +1,122 @@
+<template>
+	<text v-if="text" :class="inverted ? 'uni-badge--' + type + ' uni-badge--' + size + ' uni-badge--' + type + '-inverted' : 'uni-badge--' + type + ' uni-badge--' + size"
+	 class="uni-badge" :style="width" @click="onClick()">{{ text }}</text>
+</template>
+
+<script>
+	export default {
+		name: 'UniBadge',
+		props: {
+			type: {
+				type: String,
+				default: 'default'
+			},
+			inverted: {
+				type: Boolean,
+				default: false
+			},
+			text: {
+				type: [String,Number],
+				default: ''
+			},
+			size: {
+				// small.normal
+				type: String,
+				default: 'normal'
+			}
+		},
+		data() {
+			return {
+				width: `display: inline-block;width: ${String(this.text).length * 15 + 25}rpx`
+			};
+		},
+		methods: {
+			onClick() {
+				this.$emit('click');
+			}
+		}
+	};
+</script>
+
+<style lang="scss" scoped>
+	$bage-size: 12px;
+	$bage-small: scale(0.8);
+	$bage-height: 40rpx;
+
+	.uni-badge {
+		/* #ifndef APP-PLUS */
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+		height: $bage-height;
+		line-height: $bage-height;
+		color: $uni-text-color;
+		border-radius: 100px;
+		background-color: $uni-bg-color-hover;
+		background-color: transparent;
+		text-align: center;
+		font-family: 'Helvetica Neue', Helvetica, sans-serif;
+		font-size: $bage-size;
+		padding: 0;
+	}
+
+	.uni-badge--inverted {
+		padding: 0 5px 0 0;
+		color: $uni-bg-color-hover;
+	}
+
+	.uni-badge--default {
+		color: $uni-text-color;
+		background-color: $uni-bg-color-hover;
+	}
+
+	.uni-badge--default-inverted {
+		color: $uni-text-color-grey;
+		background-color: transparent;
+	}
+
+	.uni-badge--primary {
+		color: $uni-text-color-inverse;
+		background-color: $uni-color-primary;
+	}
+
+	.uni-badge--primary-inverted {
+		color: $uni-color-primary;
+		background-color: transparent;
+	}
+
+	.uni-badge--success {
+		color: $uni-text-color-inverse;
+		background-color: $uni-color-success;
+	}
+
+	.uni-badge--success-inverted {
+		color: $uni-color-success;
+		background-color: transparent;
+	}
+
+	.uni-badge--warning {
+		color: $uni-text-color-inverse;
+		background-color: $uni-color-warning;
+	}
+
+	.uni-badge--warning-inverted {
+		color: $uni-color-warning;
+		background-color: transparent;
+	}
+
+	.uni-badge--error {
+		color: $uni-text-color-inverse;
+		background-color: $uni-color-error;
+	}
+
+	.uni-badge--error-inverted {
+		color: $uni-color-error;
+		background-color: transparent;
+	}
+
+	.uni-badge--small {
+		transform: $bage-small;
+		transform-origin: center center;
+	}
+</style>

+ 181 - 0
components/uni-countdown/uni-countdown.vue

@@ -0,0 +1,181 @@
+<template>
+	<view class="uni-countdown">
+		<text v-if="showDay" :style="{ borderColor: borderColor, color: color, backgroundColor: backgroundColor }" class="uni-countdown__number">{{ d }}</text>
+		<text v-if="showDay" :style="{ color: splitorColor }" class="uni-countdown__splitor">天</text>
+		<text :style="{ borderColor: borderColor, color: color, backgroundColor: backgroundColor }" class="uni-countdown__number">{{ h }}</text>
+		<text :style="{ color: splitorColor }" class="uni-countdown__splitor">{{ showColon ? ':' : '时' }}</text>
+		<text :style="{ borderColor: borderColor, color: color, backgroundColor: backgroundColor }" class="uni-countdown__number">{{ i }}</text>
+		<text :style="{ color: splitorColor }" class="uni-countdown__splitor">{{ showColon ? ':' : '分' }}</text>
+		<text :style="{ borderColor: borderColor, color: color, backgroundColor: backgroundColor }" class="uni-countdown__number">{{ s }}</text>
+		<text v-if="!showColon" :style="{ color: splitorColor }" class="uni-countdown__splitor">秒</text>
+	</view>
+</template>
+<script>
+	export default {
+		name: 'UniCountdown',
+		props: {
+			showDay: {
+				type: Boolean,
+				default: true
+			},
+			showColon: {
+				type: Boolean,
+				default: true
+			},
+			backgroundColor: {
+				type: String,
+				default: '#FFFFFF'
+			},
+			borderColor: {
+				type: String,
+				default: '#000000'
+			},
+			color: {
+				type: String,
+				default: '#000000'
+			},
+			splitorColor: {
+				type: String,
+				default: '#000000'
+			},
+			day:0,
+			hour:0,
+			minute:0,
+			second:0
+		},
+		data() {
+			return {
+				timer: null,
+				syncFlag: false,
+				d: '00',
+				h: '00',
+				i: '00',
+				s: '00',
+				leftTime: 0,
+				seconds: 0
+			}
+		},
+		watch: {
+			day(val) {
+				this.changeFlag()
+			},
+			hour(val) {
+				this.changeFlag()
+			},
+			minute(val) {
+				this.changeFlag()
+			},
+			second(val) {
+				this.changeFlag()
+			}
+		},
+		created: function(e) {
+			this.startData();
+		},
+		beforeDestroy() {
+			clearInterval(this.timer)
+		},
+		methods: {
+			toSeconds(day, hours, minutes, seconds) {
+				return day * 60 * 60 * 24 + hours * 60 * 60 + minutes * 60 + seconds
+			},
+			timeUp() {
+				clearInterval(this.timer)
+				this.$emit('timeup')
+			},
+			countDown() {
+				let seconds = this.seconds
+				let [day, hour, minute, second] = [0, 0, 0, 0]
+				if (seconds > 0) {
+					day = Math.floor(seconds / (60 * 60 * 24))
+					hour = Math.floor(seconds / (60 * 60)) - (day * 24)
+					minute = Math.floor(seconds / 60) - (day * 24 * 60) - (hour * 60)
+					second = Math.floor(seconds) - (day * 24 * 60 * 60) - (hour * 60 * 60) - (minute * 60)
+				} else {
+					this.timeUp()
+				}
+				hour = hour
+				if (day < 10) {
+					day = '0' + day
+				}
+				if (hour < 10) {
+					hour = '0' + hour
+				}
+				if (minute < 10) {
+					minute = '0' + minute
+				}
+				if (second < 10) {
+					second = '0' + second
+				}
+				this.d = day
+				this.h = hour
+				this.i = minute
+				this.s = second
+			},
+			startData() {
+				this.seconds = this.toSeconds(this.day, this.hour, this.minute, this.second)
+				if (this.seconds <= 0) {
+					return
+				}
+				this.countDown()
+				this.timer = setInterval(() => {
+					this.seconds--
+					if (this.seconds < 0) {
+						this.timeUp()
+						return
+					}
+					this.countDown()
+				}, 1000)
+			},
+			changeFlag() {
+				if (!this.syncFlag) {
+					this.seconds = this.toSeconds(this.day, this.hour, this.minute, this.second)
+					console.log(this.seconds)
+					this.startData();
+					this.syncFlag = true;
+				}
+			}
+		}
+	}
+</script>
+<style lang="scss" scoped>
+	@import '~@/uni.scss';
+	$countdown-height: 40rpx;
+	$countdown-width: 40rpx;
+
+	.uni-countdown {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+		justify-content: flex-start;
+		position: relative;
+		top: 5rpx;
+		left: 15rpx;
+	}
+
+	.uni-countdown__splitor {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		justify-content: center;
+		line-height: $countdown-height;
+		padding: 5rpx;
+		font-size: $uni-font-size-sm;
+	}
+
+	.uni-countdown__number {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		justify-content: center;
+		align-items: center;
+		width: $countdown-width;
+		height: $countdown-height;
+		line-height: $countdown-height;
+		// margin: 5rpx;
+		text-align: center;
+		font-size: $uni-font-size-sm;
+		border-radius: 8rpx;
+	}
+</style>

+ 188 - 0
components/uni-countdown/uni-countdowns.vue

@@ -0,0 +1,188 @@
+<template>
+	<view class="uni-countdown">
+		<text v-if="showDay" :style="{ borderColor: borderColor, color: color, backgroundColor: backgroundColor }" class="uni-countdown__number">{{ d }}</text>
+		<text v-if="showDay" :style="{ color: splitorColor }" class="uni-countdown__splitor">天</text>
+		<text :style="{ borderColor: borderColor, color: color, backgroundColor: backgroundColor }" class="uni-countdown__number">{{ h }}</text>
+		<text :style="{ color: splitorColor }" class="uni-countdown__splitor">{{ showColon ? ':' : '时' }}</text>
+		<text :style="{ borderColor: borderColor, color: color, backgroundColor: backgroundColor }" class="uni-countdown__number">{{ i }}</text>
+		<text :style="{ color: splitorColor }" class="uni-countdown__splitor">{{ showColon ? ':' : '分' }}</text>
+		<text :style="{ borderColor: borderColor, color: color, backgroundColor: backgroundColor }" class="uni-countdown__number">{{ s }}</text>
+		<text v-if="!showColon" :style="{ color: splitorColor }" class="uni-countdown__splitor">秒</text>
+	</view>
+</template>
+<script>
+	export default {
+		name: 'UniCountdown',
+		props: {
+			showDay: {
+				type: Boolean,
+				default: true
+			},
+			showColon: {
+				type: Boolean,
+				default: true
+			},
+			backgroundColor: {
+				type: String,
+				default: '#FFFFFF'
+			},
+			borderColor: {
+				type: String,
+				default: '#000000'
+			},
+			color: {
+				type: String,
+				default: '#000000'
+			},
+			splitorColor: {
+				type: String,
+				default: '#000000'
+			},
+			day: {
+				type: Number,
+				default: 0
+			},
+			hour: {
+				type: Number,
+				default: 0
+			},
+			minute: {
+				type: Number,
+				default: 0
+			},
+			second: {
+				type: Number,
+				default: 0
+			}
+		},
+		data() {
+			return {
+				timer: null,
+				syncFlag: false,
+				d: '00',
+				h: '00',
+				i: '00',
+				s: '00',
+				leftTime: 0,
+				seconds: 0
+			}
+		},
+		watch: {
+			day(val) {
+				this.changeFlag()
+			},
+			hour(val) {
+				this.changeFlag()
+			},
+			minute(val) {
+				this.changeFlag()
+			},
+			second(val) {
+				this.changeFlag()
+			}
+		},
+		created: function(e) {
+			this.startData();
+		},
+		beforeDestroy() {
+			clearInterval(this.timer)
+		},
+		methods: {
+			toSeconds(day, hours, minutes, seconds) {
+				return day * 60 * 60 * 24 + hours * 60 * 60 + minutes * 60 + seconds
+			},
+			timeUp() {
+				clearInterval(this.timer)
+				this.$emit('timeup')
+			},
+			countDown() {
+				let seconds = this.seconds
+				let [day, hour, minute, second] = [0, 0, 0, 0]
+				if (seconds > 0) {
+					day = Math.floor(seconds / (60 * 60 * 24))
+					hour = Math.floor(seconds / (60 * 60)) - (day * 24)
+					minute = Math.floor(seconds / 60) - (day * 24 * 60) - (hour * 60)
+					second = Math.floor(seconds) - (day * 24 * 60 * 60) - (hour * 60 * 60) - (minute * 60)
+				} else {
+					this.timeUp()
+				}
+				if (day < 10) {
+					day = '0' + day
+				}
+				if (hour < 10) {
+					hour = '0' + hour
+				}
+				if (minute < 10) {
+					minute = '0' + minute
+				}
+				if (second < 10) {
+					second = '0' + second
+				}
+				this.d = day
+				this.h = hour
+				this.i = minute
+				this.s = second
+			},
+			startData() {
+				this.seconds = this.toSeconds(this.day, this.hour, this.minute, this.second)
+				if (this.seconds <= 0) {
+					return
+				}
+				this.countDown()
+				this.timer = setInterval(() => {
+					this.seconds--
+					if (this.seconds < 0) {
+						this.timeUp()
+						return
+					}
+					this.countDown()
+				}, 1000)
+			},
+			changeFlag() {
+				if (!this.syncFlag) {
+					this.seconds = this.toSeconds(this.day, this.hour, this.minute, this.second)
+					this.startData();
+					this.syncFlag = true;
+				}
+			}
+		}
+	}
+</script>
+<style lang="scss" scoped>
+	@import '~@/uni.scss';
+	$countdown-height: 48rpx;
+	$countdown-width: 52rpx;
+
+	.uni-countdown {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+		justify-content: flex-start;
+		padding: 2rpx 0;
+	}
+
+	.uni-countdown__splitor {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		justify-content: center;
+		line-height: $countdown-height;
+		padding: 5rpx;
+		font-size: $uni-font-size-sm;
+	}
+
+	.uni-countdown__number {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		justify-content: center;
+		align-items: center;
+		width: $countdown-width;
+		height: $countdown-height;
+		line-height: $countdown-height;
+		margin: 5rpx;
+		text-align: center;
+		font-size: $uni-font-size-sm;
+	}
+</style>

+ 124 - 0
components/uni-fav/uni-fav.vue

@@ -0,0 +1,124 @@
+<template>
+	<view :class="[circle === true || circle === 'true' ? 'uni-fav--circle' : '']" :style="[{ backgroundColor: checked ? bgColorChecked : bgColor }]"
+	 @click="onClick" class="uni-fav">
+		<!-- #ifdef MP-ALIPAY -->
+		<view class="uni-fav-star" v-if="!checked && (star === true || star === 'true')">
+			<uni-icons :color="fgColor" :style="{color: checked ? fgColorChecked : fgColor}" size="14" type="star-filled" />
+		</view>
+		<!-- #endif -->
+		<!-- #ifndef MP-ALIPAY -->
+		<uni-icons :color="fgColor" :style="{color: checked ? fgColorChecked : fgColor}" class="uni-fav-star" size="14" type="star-filled"
+		 v-if="!checked && (star === true || star === 'true')" />
+		<!-- #endif -->
+		<text :style="{color: checked ? fgColorChecked : fgColor}" class="uni-fav-text">{{ checked ? contentText.contentFav : contentText.contentDefault }}</text>
+	</view>
+</template>
+
+<script>
+	import uniIcons from "../uni-icons/uni-icons.vue";
+	export default {
+		name: "UniFav",
+		components: {
+			uniIcons
+		},
+		props: {
+			star: {
+				type: [Boolean, String],
+				default: true
+			},
+			bgColor: {
+				type: String,
+				default: "#eeeeee"
+			},
+			fgColor: {
+				type: String,
+				default: "#666666"
+			},
+			bgColorChecked: {
+				type: String,
+				default: "#007aff"
+			},
+			fgColorChecked: {
+				type: String,
+				default: "#FFFFFF"
+			},
+			circle: {
+				type: [Boolean, String],
+				default: false
+			},
+			checked: {
+				type: Boolean,
+				default: false
+			},
+			contentText: {
+				type: Object,
+				default () {
+					return {
+						contentDefault: "收藏",
+						contentFav: "已收藏"
+					};
+				}
+			}
+		},
+		watch: {
+			checked() {
+				if (uni.report) {
+					if (this.checked) {
+						uni.report("收藏", "收藏");
+					} else {
+						uni.report("取消收藏", "取消收藏");
+					}
+				}
+			}
+		},
+		methods: {
+			onClick() {
+				this.$emit("click");
+			}
+		}
+	};
+</script>
+
+<style lang="scss" scoped>
+	$fav-height: 25px;
+
+	.uni-fav {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+		align-items: center;
+		justify-content: center;
+		width: 60px;
+		height: $fav-height;
+		line-height: $fav-height;
+		text-align: center;
+		border-radius: 3px;
+	}
+
+	.uni-fav--circle {
+		border-radius: 30px;
+	}
+
+	.uni-fav-star {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		height: $fav-height;
+		line-height: 24px;
+		margin-right: 3px;
+		align-items: center;
+		justify-content: center;
+	}
+
+	.uni-fav-text {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		height: $fav-height;
+		line-height: $fav-height;
+		align-items: center;
+		justify-content: center;
+		font-size: $uni-font-size-base;
+	}
+</style>

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

@@ -0,0 +1,96 @@
+export default {
+	'contact': '\ue100',
+	'person': '\ue101',
+	'personadd': '\ue102',
+	'contact-filled': '\ue130',
+	'person-filled': '\ue131',
+	'personadd-filled': '\ue132',
+	'phone': '\ue200',
+	'email': '\ue201',
+	'chatbubble': '\ue202',
+	'chatboxes': '\ue203',
+	'phone-filled': '\ue230',
+	'email-filled': '\ue231',
+	'chatbubble-filled': '\ue232',
+	'chatboxes-filled': '\ue233',
+	'weibo': '\ue260',
+	'weixin': '\ue261',
+	'pengyouquan': '\ue262',
+	'chat': '\ue263',
+	'qq': '\ue264',
+	'videocam': '\ue300',
+	'camera': '\ue301',
+	'mic': '\ue302',
+	'location': '\ue303',
+	'mic-filled': '\ue332',
+	'speech': '\ue332',
+	'location-filled': '\ue333',
+	'micoff': '\ue360',
+	'image': '\ue363',
+	'map': '\ue364',
+	'compose': '\ue400',
+	'trash': '\ue401',
+	'upload': '\ue402',
+	'download': '\ue403',
+	'close': '\ue404',
+	'redo': '\ue405',
+	'undo': '\ue406',
+	'refresh': '\ue407',
+	'star': '\ue408',
+	'plus': '\ue409',
+	'minus': '\ue410',
+	'circle': '\ue411',
+	'checkbox': '\ue411',
+	'close-filled': '\ue434',
+	'clear': '\ue434',
+	'refresh-filled': '\ue437',
+	'star-filled': '\ue438',
+	'plus-filled': '\ue439',
+	'minus-filled': '\ue440',
+	'circle-filled': '\ue441',
+	'checkbox-filled': '\ue442',
+	'closeempty': '\ue460',
+	'refreshempty': '\ue461',
+	'reload': '\ue462',
+	'starhalf': '\ue463',
+	'spinner': '\ue464',
+	'spinner-cycle': '\ue465',
+	'search': '\ue466',
+	'plusempty': '\ue468',
+	'forward': '\ue470',
+	'back': '\ue471',
+	'left-nav': '\ue471',
+	'checkmarkempty': '\ue472',
+	'home': '\ue500',
+	'navigate': '\ue501',
+	'gear': '\ue502',
+	'paperplane': '\ue503',
+	'info': '\ue504',
+	'help': '\ue505',
+	'locked': '\ue506',
+	'more': '\ue507',
+	'flag': '\ue508',
+	'home-filled': '\ue530',
+	'gear-filled': '\ue532',
+	'info-filled': '\ue534',
+	'help-filled': '\ue535',
+	'more-filled': '\ue537',
+	'settings': '\ue560',
+	'list': '\ue562',
+	'bars': '\ue563',
+	'loop': '\ue565',
+	'paperclip': '\ue567',
+	'eye': '\ue568',
+	'arrowup': '\ue580',
+	'arrowdown': '\ue581',
+	'arrowleft': '\ue582',
+	'arrowright': '\ue583',
+	'arrowthinup': '\ue584',
+	'arrowthindown': '\ue585',
+	'arrowthinleft': '\ue586',
+	'arrowthinright': '\ue587',
+	'pulldown': '\ue588',
+	'closefill': '\ue589',
+	'sound': '\ue590',
+	'scan': '\ue612'
+}

Datei-Diff unterdrückt, da er zu groß ist
+ 10 - 0
components/uni-icons/uni-icons.vue


+ 230 - 0
components/uni-list-item/uni-list-item.vue

@@ -0,0 +1,230 @@
+<template>
+	<!-- #ifdef APP-NVUE -->
+	<cell>
+	<!-- #endif -->
+	<view :class="disabled ? 'uni-list-item--disabled' : ''" :hover-class="disabled || showSwitch ? '' : 'uni-list-item--hover'"
+	 class="uni-list-item" @click="onClick">
+		<view class="uni-list-item__container" :class="{'uni-list-item--first':isFirstChild}">
+			<view v-if="thumb" class="uni-list-item__icon">
+				<image :src="thumb" mode="aspectFit" class="uni-list-item__icon-img" />
+			</view>
+			<view v-else-if="showExtraIcon" class="uni-list-item__icon">
+				<uni-icons :color="extraIcon.color" :size="extraIcon.size" :type="extraIcon.type" class="uni-icon-wrapper" />
+			</view>
+			<view class="uni-list-item__content">
+				<slot></slot>
+				<text class="uni-list-item__content-title">{{ title }}</text>
+				<text v-if="note" class="uni-list-item__content-note">{{ note }}</text>
+			</view>
+			<view class="uni-list-item__slot">
+				<slot name="right" ></slot>
+			</view>
+			<view v-if="showBadge || showArrow || showSwitch" class="uni-list-item__extra">
+				<uni-badge v-if="showBadge" :type="badgeType" :text="badgeText" />
+				<switch class="itemSwitch" v-if="showSwitch" :color="switchColor" :disabled="disabled" :checked="switchChecked" @change="onSwitchChange" />
+				<uni-icons v-if="showArrow" :size="20" class="uni-icon-wrapper" color="#bbb" type="arrowright" />
+			</view>
+		</view>
+	</view>
+	<!-- #ifdef APP-NVUE -->
+	</cell>
+	<!-- #endif -->
+</template>
+
+<script>
+	import uniIcons from '../uni-icons/uni-icons.vue'
+	import uniBadge from '../uni-badge/uni-badge.vue'
+	export default {
+		name: 'UniListItem',
+		components: {
+			uniIcons,
+			uniBadge
+		},
+		props: {
+			title: {
+				type: String,
+				default: ''
+			}, // 列表标题
+			note: {
+				type: String,
+				default: ''
+			}, // 列表描述
+			disabled: {
+				// 是否禁用
+				type: [Boolean, String],
+				default: false
+			},
+			showArrow: {
+				// 是否显示箭头
+				type: [Boolean, String],
+				default: true
+			},
+			showBadge: {
+				// 是否显示数字角标
+				type: [Boolean, String],
+				default: false
+			},
+			showSwitch: {
+				// 是否显示Switch
+				type: [Boolean, String],
+				default: false
+			},
+			switchChecked: {
+				// Switch是否被选中
+				type: [Boolean, String],
+				default: false
+			},
+			switchColor:{
+				type:String,
+				default:''
+			},
+			badgeText: {
+				// badge内容
+				type: String,
+				default: ''
+			},
+			badgeType: {
+				// badge类型
+				type: String,
+				default: 'success'
+			},
+			thumb: {
+				// 缩略图
+				type: String,
+				default: ''
+			},
+			showExtraIcon: {
+				// 是否显示扩展图标
+				type: [Boolean, String],
+				default: false
+			},
+			extraIcon: {
+				type: Object,
+				default () {
+					return {
+						type: 'contact',
+						color: '#000000',
+						size: 20
+					}
+				}
+			}
+		},
+		inject: ['list'],
+		data() {
+			return {
+				isFirstChild: false
+			}
+		},
+		mounted() {
+			if (!this.list.firstChildAppend) {
+				this.list.firstChildAppend = true
+				this.isFirstChild = true
+			}
+		},
+		methods: {
+			onClick() {
+				this.$emit('click')
+			},
+			onSwitchChange(e) {
+				this.$emit('switchChange', e.detail)
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	@import '~@/uni.scss';
+
+	$list-item-pd: $uni-spacing-col-lg $uni-spacing-row-lg;
+
+	.uni-list-item {
+		font-size: $uni-font-size-lg;
+		position: relative;
+		flex-direction: column;
+		justify-content: space-between;
+		padding-left: $uni-spacing-row-lg;
+	}
+
+	.uni-list-item--disabled {
+		opacity: 0.3;
+	}
+
+	.uni-list-item--hover {
+		background-color: $uni-bg-color-hover;
+	}
+
+	.uni-list-item__container {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+		padding: $list-item-pd;
+		padding-left: 0;
+		flex: 1;
+		position: relative;
+		justify-content: space-between;
+		align-items: center;
+		border-top-color: $uni-border-color;
+		border-top-style: solid;
+		border-top-width: 1px;
+	}
+
+	.uni-list-item--first {
+		border-top-width: 0px;
+	}
+
+	.uni-list-item__content {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex: 1;
+		overflow: hidden;
+		flex-direction: column;
+		color: #3b4144;
+
+	}
+
+	.uni-list-item__content-title {
+		font-size: $uni-font-size-base;
+		color: #3b4144;
+		overflow: hidden;
+	}
+
+	.uni-list-item__content-note {
+		margin-top: 6rpx;
+		color: $uni-text-color-grey;
+		font-size: $uni-font-size-sm;
+		overflow: hidden;
+	}
+
+	.uni-list-item__extra {
+		// width: 25%;
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+		justify-content: flex-end;
+		align-items: center;
+	}
+
+	.uni-list-item__icon {
+		margin-right: 18rpx;
+		flex-direction: row;
+		justify-content: center;
+		align-items: center;
+		display: flex;
+	}
+
+	.uni-list-item__icon-img {
+		height: $uni-img-size-base;
+		width: $uni-img-size-base;
+	}
+	// 修改switch默认大小
+	.itemSwitch{
+		transform: translateX(16rpx) scale(.84);
+	}
+	.uni-list-item__slot{
+		color: #909399;
+		font-size: 28rpx;
+	}
+</style>

+ 68 - 0
components/uni-list/uni-list.vue

@@ -0,0 +1,68 @@
+<template>
+	<!-- #ifndef APP-NVUE -->
+	<view class="uni-list">
+		<slot />
+	</view>
+	<!-- #endif -->
+	<!-- #ifdef APP-NVUE -->
+	<list class="uni-list" :enableBackToTop="enableBackToTop" loadmoreoffset="15" :scroll-y="scrollY" @loadmore="loadMore">
+		<slot />
+	</list>
+	<!-- #endif -->
+</template>
+
+<script>
+	export default {
+		name: 'UniList',
+		'mp-weixin': {
+			options: {
+				multipleSlots: false
+			}
+		},
+		props: {
+			enableBackToTop: {
+				type: [Boolean, String],
+				default: false
+			},
+			scrollY: {
+				type: [Boolean, String],
+				default: false
+			}
+		},
+		provide() {
+			return {
+				list: this
+			}
+		},
+		created() {
+			this.firstChildAppend = false
+		},
+		methods: {
+			loadMore(e) {
+				this.$emit("scrolltolower");
+			}
+		}
+	}
+</script>
+<style lang="scss" scoped>
+	.uni-list {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		background-color: $uni-bg-color;
+		position: relative;
+		flex-direction: column;
+		// border-bottom-color: $uni-border-color;
+		// border-bottom-style: solid;
+		// border-bottom-width: 1px;
+	}
+	/* #ifndef APP-NVUE */
+	.uni-list:before {
+		height: 0;
+	}
+	.uni-list:after {
+		height: 0;
+	}
+	/* #endif */
+
+</style>

+ 65 - 0
components/uni-list/uni-refresh.vue

@@ -0,0 +1,65 @@
+<template>
+    <!-- #ifdef APP-NVUE -->
+    <refresh :display="display" @refresh="onrefresh" @pullingdown="onpullingdown">
+        <slot />
+    </refresh>
+    <!-- #endif -->
+    <!-- #ifndef APP-NVUE -->
+    <view ref="uni-refresh" class="uni-refresh" v-show="isShow">
+        <slot />
+    </view>
+    <!-- #endif -->
+</template>
+
+<script>
+    export default {
+        name: 'UniRefresh',
+        props: {
+            display: {
+                type: [String],
+                default: "hide"
+            }
+        },
+        data() {
+            return {
+                pulling: false
+            }
+        },
+        computed: {
+            isShow() {
+                if (this.display === "show" || this.pulling === true) {
+                    return true;
+                }
+                return false;
+            }
+        },
+        created() {},
+        methods: {
+            onchange(value) {
+                this.pulling = value;
+            },
+            onrefresh(e) {
+                this.$emit("refresh", e);
+            },
+            onpullingdown(e) {
+                // #ifdef APP-NVUE
+                this.$emit("pullingdown", e);
+                // #endif
+                // #ifndef APP-NVUE
+                var detail = {
+                    viewHeight: 90,
+                    pullingDistance: e.height
+                }
+                this.$emit("pullingdown", detail);
+                // #endif
+            }
+        }
+    }
+</script>
+
+<style>
+    .uni-refresh {
+        height: 0;
+        overflow: hidden;
+    }
+</style>

+ 87 - 0
components/uni-list/uni-refresh.wxs

@@ -0,0 +1,87 @@
+var pullDown = {
+    threshold: 95,
+    maxHeight: 200,
+    callRefresh: 'onrefresh',
+    callPullingDown: 'onpullingdown',
+    refreshSelector: '.uni-refresh'
+};
+
+function ready(newValue, oldValue, ownerInstance, instance) {
+    var state = instance.getState()
+    state.canPullDown = newValue;
+    console.log(newValue);
+}
+
+function touchStart(e, instance) {
+    var state = instance.getState();
+    state.refreshInstance = instance.selectComponent(pullDown.refreshSelector);
+    state.canPullDown = (state.refreshInstance != null && state.refreshInstance != undefined);
+    if (!state.canPullDown) {
+        return
+    }
+
+    console.log("touchStart");
+
+    state.height = 0;
+    state.touchStartY = e.touches[0].pageY || e.changedTouches[0].pageY;
+    state.refreshInstance.setStyle({
+        'height': 0
+    });
+    state.refreshInstance.callMethod("onchange", true);
+}
+
+function touchMove(e, ownerInstance) {
+    var instance = e.instance;
+    var state = instance.getState();
+    if (!state.canPullDown) {
+        return
+    }
+
+    var oldHeight = state.height;
+    var endY = e.touches[0].pageY || e.changedTouches[0].pageY;
+    var height = endY - state.touchStartY;
+    if (height > pullDown.maxHeight) {
+        return;
+    }
+
+    var refreshInstance = state.refreshInstance;
+    refreshInstance.setStyle({
+        'height': height + 'px'
+    });
+
+    height = height < pullDown.maxHeight ? height : pullDown.maxHeight;
+    state.height = height;
+    refreshInstance.callMethod(pullDown.callPullingDown, {
+        height: height
+    });
+}
+
+function touchEnd(e, ownerInstance) {
+    var state = e.instance.getState();
+    if (!state.canPullDown) {
+        return
+    }
+
+    state.refreshInstance.callMethod("onchange", false);
+
+    var refreshInstance = state.refreshInstance;
+    if (state.height > pullDown.threshold) {
+        refreshInstance.callMethod(pullDown.callRefresh);
+        return;
+    }
+
+    refreshInstance.setStyle({
+        'height': 0
+    });
+}
+
+function propObserver(newValue, oldValue, instance) {
+    pullDown = newValue;
+}
+
+module.exports = {
+    touchmove: touchMove,
+    touchstart: touchStart,
+    touchend: touchEnd,
+    propObserver: propObserver
+}

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

@@ -0,0 +1,205 @@
+<template>
+	<view class="uni-load-more">
+		<view class="uni-load-more__img" v-show="status === 'loading' && showIcon">
+			<view class="load1 load">
+				<view class="item" :style="{background:color}"></view>
+				<view class="item" :style="{background:color}"></view>
+				<view class="item" :style="{background:color}"></view>
+				<view class="item" :style="{background:color}"></view>
+			</view>
+			<view class="load2 load">
+				<view class="item" :style="{background:color}"></view>
+				<view class="item"  :style="{background:color}"></view>
+				<view class="item" :style="{background:color}"></view>
+				<view class="item" :style="{background:color}"></view>
+			</view>
+			<view class="load3 load">
+				<view class="item" :style="{background:color}"></view>
+				<view class="item" :style="{background:color}"></view>
+				<view class="item" :style="{background:color}"></view>
+				<view class="item" :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>
+	import { mapState, mapMutations } from 'vuex';
+	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: this.$t('hea.sljzgd'),
+						contentrefresh: this.$t('hea.loading'),
+						contentnomore: this.$t('hea.nomore')
+					};
+				}
+			}
+		},
+		data() {
+			return {}
+		},
+		computed: {
+			...mapState(['lang'])
+		},
+		watch: {
+			lang(val) {
+				this.$set(this.contentText,'contentdown',this.$t('hea.sljzgd'))
+				this.$set(this.contentText,'contentrefresh',this.$t('hea.loading'))
+				this.$set(this.contentText,'contentnomore',this.$t('hea.nomore'))
+			}
+		},
+	}
+</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>.load {
+		position: absolute
+	}
+
+	.uni-load-more__img>.load .item {
+		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>.load .item:nth-child(1) {
+		transform: rotate(90deg);
+		top: 2px;
+		left: 9px
+	}
+
+	.uni-load-more__img>.load .item:nth-child(2) {
+		transform: rotate(180deg);
+		top: 11px;
+		right: 0
+	}
+
+	.uni-load-more__img>.load .item:nth-child(3) {
+		transform: rotate(270deg);
+		bottom: 2px;
+		left: 9px
+	}
+
+	.uni-load-more__img>.load .item:nth-child(4) {
+		top: 11px;
+		left: 0
+	}
+
+	.load1,
+	.load2,
+	.load3 {
+		height: 24px;
+		width: 24px
+	}
+
+	.load2 {
+		transform: rotate(30deg)
+	}
+
+	.load3 {
+		transform: rotate(60deg)
+	}
+
+	.load1 .item:nth-child(1) {
+		animation-delay: 0s
+	}
+
+	.load2 .item:nth-child(1) {
+		animation-delay: .13s
+	}
+
+	.load3 .item:nth-child(1) {
+		animation-delay: .26s
+	}
+
+	.load1 .item:nth-child(2) {
+		animation-delay: .39s
+	}
+
+	.load2 .item:nth-child(2) {
+		animation-delay: .52s
+	}
+
+	.load3 .item:nth-child(2) {
+		animation-delay: .65s
+	}
+
+	.load1 .item:nth-child(3) {
+		animation-delay: .78s
+	}
+
+	.load2 .item:nth-child(3) {
+		animation-delay: .91s
+	}
+
+	.load3 .item:nth-child(3) {
+		animation-delay: 1.04s
+	}
+
+	.load1 .item:nth-child(4) {
+		animation-delay: 1.17s
+	}
+
+	.load2 .item:nth-child(4) {
+		animation-delay: 1.3s
+	}
+
+	.load3 .item:nth-child(4) {
+		animation-delay: 1.43s
+	}
+
+	@-webkit-keyframes load {
+		0% {
+			opacity: 1
+		}
+
+		100% {
+			opacity: .2
+		}
+	}
+</style>

+ 396 - 0
components/uni-notice-bar/uni-notice-bar.vue

@@ -0,0 +1,396 @@
+<template>
+	<view v-if="show" class="uni-noticebar" :style="{ backgroundColor: backgroundColor }" @click="onClick">
+		<!-- #ifdef MP-ALIPAY -->
+		<view v-if="showClose === true || showClose === 'true'" class="uni-noticebar-close" @click="close">
+			<uni-icons type="closefill" :color="color" size="12" />
+		</view>
+		<view v-if="showIcon === true || showIcon === 'true'" class="uni-noticebar-icon">
+			<uni-icons type="sound" :color="color" size="14" />
+		</view>
+		<!-- #endif -->
+		<!-- #ifndef MP-ALIPAY -->
+		<uni-icons v-if="showClose === true || showClose === 'true'" class="uni-noticebar-close" type="closefill" :color="color"
+		 size="12" @click="close" />
+		<uni-icons v-if="showIcon === true || showIcon === 'true'" class="uni-noticebar-icon" type="sound" :color="color"
+		 size="14" />
+		<!-- #endif -->
+		<view ref="textBox" class="uni-noticebar__content-wrapper" :class="{'uni-noticebar__content-wrapper--scrollable':scrollable, 'uni-noticebar__content-wrapper--single':!scrollable && (single || moreText)}">
+			<view :id="elIdBox" class="uni-noticebar__content" :class="{'uni-noticebar__content--scrollable':scrollable, 'uni-noticebar__content--single':!scrollable && (single || moreText)}">
+				<text :id="elId" ref="animationEle" class="uni-noticebar__content-text" :class="{'uni-noticebar__content-text--scrollable':scrollable,'uni-noticebar__content-text--single':!scrollable && (single || moreText)}"
+				 :style="{color:color, width:wrapWidth+'px', 'animationDuration': animationDuration, '-webkit-animationDuration': animationDuration ,animationPlayState: webviewHide?'paused':animationPlayState,'-webkit-animationPlayState':webviewHide?'paused':animationPlayState, animationDelay: animationDelay, '-webkit-animationDelay':animationDelay}">{{text}}</text>
+			</view>
+		</view>
+		<view v-if="showGetMore === true || showGetMore === 'true'" class="uni-noticebar__more" @click="clickMore">
+			<text v-if="moreText" :style="{ color: moreColor }" class="uni-noticebar__more-text">{{ moreText }}</text>
+			<uni-icons type="arrowright" :color="moreColor" size="14" />
+		</view>
+	</view>
+</template>
+
+<script>
+	import uniIcons from '../uni-icons/uni-icons.vue'
+	// #ifdef APP-NVUE
+	const dom = weex.requireModule('dom');
+	const animation = weex.requireModule('animation');
+	// #endif
+
+	/**
+	 * NoticeBar 自定义导航栏
+	 * @description 通告栏组件
+	 * @tutorial https://ext.dcloud.net.cn/plugin?id=30
+	 * @property {Number} speed 文字滚动的速度,默认100px/秒
+	 * @property {String} text 显示文字
+	 * @property {String} backgroundColor 背景颜色
+	 * @property {String} color 文字颜色
+	 * @property {String} moreColor 查看更多文字的颜色
+	 * @property {String} moreText 设置“查看更多”的文本
+	 * @property {Boolean} single = [true|false] 是否单行
+	 * @property {Boolean} scrollable = [true|false] 是否滚动,为true时,NoticeBar为单行
+	 * @property {Boolean} showIcon = [true|false] 是否显示左侧喇叭图标
+	 * @property {Boolean} showClose = [true|false] 是否显示左侧关闭按钮
+	 * @property {Boolean} showGetMore = [true|false] 是否显示右侧查看更多图标,为true时,NoticeBar为单行
+	 * @event {Function} click 点击 NoticeBar 触发事件
+	 * @event {Function} close 关闭 NoticeBar 触发事件
+	 * @event {Function} getmore 点击”查看更多“时触发事件
+	 */
+
+	export default {
+		name: 'UniNoticeBar',
+		components: {
+			uniIcons
+		},
+		props: {
+			text: {
+				type: String,
+				default: ''
+			},
+			moreText: {
+				type: String,
+				default: ''
+			},
+			backgroundColor: {
+				type: String,
+				default: '#fffbe8'
+			},
+			speed: {
+				// 默认1s滚动100px
+				type: Number,
+				default: 100
+			},
+			color: {
+				type: String,
+				default: '#de8c17'
+			},
+			moreColor: {
+				type: String,
+				default: '#999999'
+			},
+			single: {
+				// 是否单行
+				type: [Boolean, String],
+				default: false
+			},
+			scrollable: {
+				// 是否滚动,添加后控制单行效果取消
+				type: [Boolean, String],
+				default: false
+			},
+			showIcon: {
+				// 是否显示左侧icon
+				type: [Boolean, String],
+				default: false
+			},
+			showGetMore: {
+				// 是否显示右侧查看更多
+				type: [Boolean, String],
+				default: false
+			},
+			showClose: {
+				// 是否显示左侧关闭按钮
+				type: [Boolean, String],
+				default: false
+			}
+		},
+		data() {
+			const elId = `Uni_${Math.ceil(Math.random() * 10e5).toString(36)}`
+			const elIdBox = `Uni_${Math.ceil(Math.random() * 10e5).toString(36)}`
+			return {
+				textWidth: 0,
+				boxWidth: 0,
+				wrapWidth: '',
+				webviewHide: false,
+				// #ifdef APP-NVUE
+				stopAnimation: false,
+				// #endif
+				elId: elId,
+				elIdBox: elIdBox,
+				show: true,
+				animationDuration: 'none',
+				animationPlayState: 'paused',
+				animationDelay: '0s'
+			}
+		},
+		mounted() {
+			// #ifdef APP-PLUS
+			var pages = getCurrentPages();
+			var page = pages[pages.length - 1];
+			var currentWebview = page.$getAppWebview();
+			currentWebview.addEventListener('hide',()=>{
+				this.webviewHide = true
+			})
+			currentWebview.addEventListener('show',()=>{
+				this.webviewHide = false
+			})
+			// #endif
+			this.$nextTick(() => {
+				this.initSize()
+			})
+		},
+		// #ifdef APP-NVUE
+		beforeDestroy() {
+			this.stopAnimation = true
+		},
+		// #endif
+		methods: {
+			initSize() {
+				if (this.scrollable) {
+					// #ifndef APP-NVUE
+					let query = [],
+						boxWidth = 0,
+						textWidth = 0;
+					let textQuery = new Promise((resolve, reject) => {
+						uni.createSelectorQuery()
+							// #ifndef MP-ALIPAY
+							.in(this)
+							// #endif
+							.select(`#${this.elId}`)
+							.boundingClientRect()
+							.exec(ret => {
+								this.textWidth = ret[0].width
+								resolve()
+							})
+					})
+					let boxQuery = new Promise((resolve, reject) => {
+						uni.createSelectorQuery()
+							// #ifndef MP-ALIPAY
+							.in(this)
+							// #endif
+							.select(`#${this.elIdBox}`)
+							.boundingClientRect()
+							.exec(ret => {
+								this.boxWidth = ret[0].width
+								resolve()
+							})
+					})
+					query.push(textQuery)
+					query.push(boxQuery)
+					Promise.all(query).then(() => {
+						this.animationDuration = `${this.textWidth / this.speed}s`
+						this.animationDelay = `-${this.boxWidth / this.speed}s`
+						setTimeout(() => {
+							this.animationPlayState = 'running'
+						}, 1000)
+					})
+					// #endif
+					// #ifdef APP-NVUE
+					dom.getComponentRect(this.$refs['animationEle'], (res) => {
+						let winWidth = uni.getSystemInfoSync().windowWidth
+						this.textWidth = res.size.width
+						animation.transition(this.$refs['animationEle'], {
+							styles: {
+								transform: `translateX(-${winWidth}px)`
+							},
+							duration: 0,
+							timingFunction: 'linear',
+							delay: 0
+						}, () => {
+							if (!this.stopAnimation) {
+								animation.transition(this.$refs['animationEle'], {
+									styles: {
+										transform: `translateX(-${this.textWidth}px)`
+									},
+									timingFunction: 'linear',
+									duration: (this.textWidth - winWidth) / this.speed * 1000,
+									delay: 1000
+								}, () => {
+									if (!this.stopAnimation) {
+										this.loopAnimation()
+									}
+								});
+							}
+						});
+					})
+					// #endif
+				}
+				// #ifdef APP-NVUE
+				if (!this.scrollable && (this.single || this.moreText)) {
+					dom.getComponentRect(this.$refs['textBox'], (res) => {
+						this.wrapWidth = res.size.width
+					})
+				}
+				// #endif
+			},
+			loopAnimation() {
+				// #ifdef APP-NVUE
+				animation.transition(this.$refs['animationEle'], {
+					styles: {
+						transform: `translateX(0px)`
+					},
+					duration: 0
+				}, () => {
+					if (!this.stopAnimation) {
+						animation.transition(this.$refs['animationEle'], {
+							styles: {
+								transform: `translateX(-${this.textWidth}px)`
+							},
+							duration: this.textWidth / this.speed * 1000,
+							timingFunction: 'linear',
+							delay: 0
+						}, () => {
+							if (!this.stopAnimation) {
+								this.loopAnimation()
+							}
+						});
+					}
+				});
+				// #endif
+			},
+			clickMore() {
+				this.$emit('getmore')
+			},
+			close() {
+				this.show = false;
+				this.$emit('close')
+			},
+			onClick() {
+				this.$emit('click')
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	@import '@/uni.scss';
+
+	.uni-noticebar {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		width: 100%;
+		box-sizing: border-box;
+		/* #endif */
+		flex-direction: row;
+		align-items: center;
+		padding: 6px 12px;
+		margin-bottom: 10px;
+	}
+
+	.uni-noticebar-close {
+		margin-right: 5px;
+	}
+
+	.uni-noticebar-icon {
+		margin-right: 5px;
+	}
+
+	.uni-noticebar__content-wrapper {
+		flex: 1;
+		flex-direction: column;
+		overflow: hidden;
+	}
+
+	.uni-noticebar__content-wrapper--single {
+		/* #ifndef APP-NVUE */
+		line-height: 18px;
+		/* #endif */
+	}
+
+	.uni-noticebar__content-wrapper--single,
+	.uni-noticebar__content-wrapper--scrollable {
+		flex-direction: row;
+	}
+
+	/* #ifndef APP-NVUE */
+	.uni-noticebar__content-wrapper--scrollable {
+		position: relative;
+		height: 18px;
+	}
+	/* #endif */
+
+	.uni-noticebar__content--scrollable {
+		/* #ifdef APP-NVUE */
+		flex: 0;
+		/* #endif */
+		/* #ifndef APP-NVUE */
+		flex: 1;
+		display: block;
+		overflow: hidden;
+		/* #endif */
+	}
+
+	.uni-noticebar__content--single {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		flex: none;
+		width: 100%;
+		justify-content: center;
+		/* #endif */
+	}
+
+	.uni-noticebar__content-text {
+		font-size: 14px;
+		line-height: 18px;
+		/* #ifndef APP-NVUE */
+		word-break: break-all;
+		/* #endif */
+	}
+
+	.uni-noticebar__content-text--single {
+		/* #ifdef APP-NVUE */
+		lines: 1;
+		/* #endif */
+		/* #ifndef APP-NVUE */
+		display: block;
+		width: 100%;
+		white-space: nowrap;
+		/* #endif */
+		overflow: hidden;
+		text-overflow: ellipsis;
+	}
+
+	.uni-noticebar__content-text--scrollable {
+		/* #ifdef APP-NVUE */
+		lines: 1;
+		padding-left: 750rpx;
+		/* #endif */
+		/* #ifndef APP-NVUE */
+		position: absolute;
+		display: block;
+		height: 18px;
+		line-height: 18px;
+		white-space: nowrap;
+		padding-left: 100%;
+		animation: notice 10s 0s linear infinite both;
+		animation-play-state: paused;
+		/* #endif */
+	}
+
+	.uni-noticebar__more {
+		/* #ifndef APP-NVUE */
+		display: inline-flex;
+		/* #endif */
+		flex-direction: row;
+		flex-wrap: nowrap;
+		align-items: center;
+		padding-left: 5px;
+	}
+
+	.uni-noticebar__more-text {
+		font-size: 14px;
+	}
+
+	@keyframes notice {
+		100% {
+			transform: translate3d(-100%, 0, 0);
+		}
+	}
+</style>

+ 198 - 0
components/uni-number-box.vue

@@ -0,0 +1,198 @@
+<template>
+	<view class="uni-numbox">
+		<view class="uni-numbox-minus" 
+			@click="_calcValue('subtract')"
+		>
+			<text class="iconfont iconmove" :class="minDisabled?'uni-numbox-disabled': ''" ></text>
+		</view>
+		<input 
+			class="uni-numbox-value" 
+			type="number"  
+			:disabled="disabled"
+			:value="inputValue" 
+			@blur="_onBlur"
+		>
+		<view 
+			class="uni-numbox-plus" 
+			@click="_calcValue('add')"
+		>
+			<text class="iconfont iconadd" :class="maxDisabled?'uni-numbox-disabled': ''" ></text>
+		</view>
+	</view>
+</template>
+<script>
+	export default {
+		name: 'uni-number-box',
+		props: {
+			isMax: {
+				type: Boolean,
+				default: false
+			},
+			isMin: {
+				type: Boolean,
+				default: false
+			},
+			index: {
+				type: Number,
+				default: 0
+			},
+			value: {
+				type: Number,
+				default: 0
+			},
+			min: {
+				type: Number,
+				default: -Infinity
+			},
+			max: {
+				type: Number,
+				default: Infinity
+			},
+			step: {
+				type: Number,
+				default: 1
+			},
+			disabled: {
+				type: Boolean,
+				default: false
+			}
+		},
+		data() {
+			return {
+				inputValue: this.value,
+				minDisabled: false,
+				maxDisabled: false
+			}
+		},
+		created(){
+			this.maxDisabled = this.isMax;
+			this.minDisabled = this.isMin;
+		},
+		computed: {
+
+		},
+		watch: {
+			inputValue(number) {
+				const data = {
+					number: number,
+					index: this.index
+				}
+				this.$emit('eventChange', data);
+			}
+		},
+		methods: {
+			_calcValue(type) {
+				const scale = this._getDecimalScale();
+				let value = this.inputValue * scale;
+				let newValue = 0;
+				let step = this.step * scale;
+				
+				if(type === 'subtract'){
+					newValue = value - step;
+					if (newValue <= this.min){
+						this.minDisabled = true;
+					}
+					if(newValue < this.min){
+						newValue = this.min
+					}
+					if(newValue < this.max && this.maxDisabled === true){
+						this.maxDisabled = false;
+					}
+				}else if(type === 'add'){
+					newValue = value + step;
+					if (newValue >= this.max){
+						this.maxDisabled = true;
+					}
+					if(newValue > this.max){
+						newValue = this.max
+					}
+					if(newValue > this.min && this.minDisabled === true){
+						this.minDisabled = false;
+					}
+				}
+				if(newValue === value){
+					return;
+				}
+				this.inputValue = newValue / scale;
+			},
+			_getDecimalScale() {
+				let scale = 1;
+				// 浮点型
+				if (~~this.step !== this.step) {
+					scale = Math.pow(10, (this.step + '').split('.')[1].length);
+				}
+				return scale;
+			},
+			_onBlur(event) {
+				let value = event.detail.value;
+				if (!value) {
+					this.inputValue = 0;
+					return
+				}
+				value = +value;
+				if (value > this.max) {
+					value = this.max;
+				} else if (value < this.min) {
+					value = this.min
+				}
+
+				this.inputValue = value
+			}
+		}
+	}
+</script>
+<style>
+	.uni-numbox {
+		/* position:absolute; */
+		/* left: 30rpx; */
+		/* bottom: 0; */
+		display: flex;
+		justify-content: flex-start;
+		align-items: center;
+		width:230rpx;
+		height: 70rpx;
+		background:#f5f5f5;
+	}
+
+	.uni-numbox-minus,
+	.uni-numbox-plus {
+		margin: 0;
+		background-color: #f5f5f5;
+		width: 70rpx;
+		height: 100%;
+		line-height: 70rpx;
+		text-align: center;
+		position: relative;
+	}
+	.uni-numbox-minus .yticon,
+	.uni-numbox-plus .yticon{
+		font-size: 36rpx;
+		color: #555;
+	}
+
+	.uni-numbox-minus {
+		border-right: none;
+		border-top-left-radius: 6rpx;
+		border-bottom-left-radius: 6rpx;
+	}
+
+	.uni-numbox-plus {
+		border-left: none;
+		border-top-right-radius: 6rpx;
+		border-bottom-right-radius: 6rpx;
+	}
+
+	.uni-numbox-value {
+		position: relative;
+		background-color: #f5f5f5;
+		width: 90rpx;
+		height: 50rpx;
+		text-align: center;
+		padding: 0;
+		font-size: 30rpx;
+	}
+
+	.uni-numbox-disabled.iconfont {
+		color: #d6d6d6;
+	}
+</style>

+ 243 - 0
components/uni-popup/uni-popup-dialog.vue

@@ -0,0 +1,243 @@
+<template>
+	<view class="uni-popup-dialog">
+		<view class="uni-dialog-title">
+			<text class="uni-dialog-title-text" :class="['uni-popup__'+dialogType]">{{title}}</text>
+		</view>
+		<view class="uni-dialog-content">
+			<text class="uni-dialog-content-text" v-if="mode === 'base'">{{content}}</text>
+			<input v-else class="uni-dialog-input" v-model="val" type="text" :placeholder="placeholder" :focus="focus" >
+		</view>
+		<view class="uni-dialog-button-group">
+			<view class="uni-dialog-button" @click="close">
+				<text class="uni-dialog-button-text">取消</text>
+			</view>
+			<view class="uni-dialog-button uni-border-left" @click="onOk">
+				<text class="uni-dialog-button-text uni-button-color">确定</text>
+			</view>
+		</view>
+
+	</view>
+</template>
+
+<script>
+	/**
+	 * PopUp 弹出层-对话框样式
+	 * @description 弹出层-对话框样式
+	 * @tutorial https://ext.dcloud.net.cn/plugin?id=329
+	 * @property {String} value input 模式下的默认值
+	 * @property {String} placeholder input 模式下输入提示
+	 * @property {String} type = [success|warning|info|error] 主题样式
+	 *  @value success 成功
+	 * 	@value warning 提示
+	 * 	@value info 消息
+	 * 	@value error 错误
+	 * @property {String} mode = [base|input] 模式、
+	 * 	@value base 基础对话框
+	 * 	@value input 可输入对话框
+	 * @property {String} content 对话框内容
+	 * @property {Boolean} beforeClose 是否拦截取消事件
+	 * @event {Function} confirm 点击确认按钮触发
+	 * @event {Function} close 点击取消按钮触发
+	 */
+
+	export default {
+		name: "uniPopupDialog",
+		props: {
+			value: {
+				type: [String, Number],
+				default: ''
+			},
+			placeholder: {
+				type: [String, Number],
+				default: '请输入内容'
+			},
+			/**
+			 * 对话框主题 success/warning/info/error	  默认 success
+			 */
+			type: {
+				type: String,
+				default: 'error'
+			},
+			/**
+			 * 对话框模式 base/input
+			 */
+			mode: {
+				type: String,
+				default: 'base'
+			},
+			/**
+			 * 对话框标题
+			 */
+			title: {
+				type: String,
+				default: '提示'
+			},
+			/**
+			 * 对话框内容
+			 */
+			content: {
+				type: String,
+				default: ''
+			},
+			/**
+			 * 拦截取消事件 ,如果拦截取消事件,必须监听close事件,执行 done()
+			 */
+			beforeClose: {
+				type: Boolean,
+				default: false
+			}
+		},
+		data() {
+			return {
+				dialogType: 'error',
+				focus: false,
+				val: ""
+			}
+		},
+		inject: ['popup'],
+		watch: {
+			type(val) {
+				this.dialogType = val
+			},
+			mode(val) {
+				if (val === 'input') {
+					this.dialogType = 'info'
+				}
+			},
+			value(val) {
+				this.val = val
+			}
+		},
+		created() {
+			// 对话框遮罩不可点击
+			this.popup.mkclick = false
+			if (this.mode === 'input') {
+				this.dialogType = 'info'
+				this.val = this.value
+			} else {
+				this.dialogType = this.type
+			}
+		},
+		mounted() {
+			this.focus = true
+		},
+		methods: {
+			/**
+			 * 点击确认按钮
+			 */
+			onOk() {
+				this.$emit('confirm', () => {
+					this.popup.close()
+					if (this.mode === 'input') this.val = this.value
+				}, this.mode === 'input' ? this.val : '')
+			},
+			/**
+			 * 点击取消按钮
+			 */
+			close() {
+				if (this.beforeClose) {
+					this.$emit('close', () => {
+						this.popup.close()
+					})
+					return
+				}
+				this.popup.close()
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	.uni-popup-dialog {
+		width: 300px;
+		border-radius: 15px;
+		background-color: #fff;
+	}
+
+	.uni-dialog-title {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+		justify-content: center;
+		padding-top: 15px;
+		padding-bottom: 5px;
+	}
+
+	.uni-dialog-title-text {
+		font-size: 16px;
+		font-weight: 500;
+	}
+
+	.uni-dialog-content {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+		justify-content: center;
+		align-items: center;
+		padding: 5px 15px 15px 15px;
+	}
+
+	.uni-dialog-content-text {
+		font-size: 14px;
+		color: #6e6e6e;
+	}
+
+	.uni-dialog-button-group {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+		border-top-color: #f5f5f5;
+		border-top-style: solid;
+		border-top-width: 1px;
+	}
+
+	.uni-dialog-button {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+
+		flex: 1;
+		flex-direction: row;
+		justify-content: center;
+		align-items: center;
+		height: 45px;
+	}
+
+	.uni-border-left {
+		border-left-color: #f0f0f0;
+		border-left-style: solid;
+		border-left-width: 1px;
+	}
+
+	.uni-dialog-button-text {
+		font-size: 14px;
+	}
+
+	.uni-button-color {
+		color: $uni-color-primary;
+	}
+
+	.uni-dialog-input {
+		flex: 1;
+		font-size: 14px;
+	}
+
+	.uni-popup__success {
+		color: $uni-color-success;
+	}
+
+	.uni-popup__warn {
+		color: $uni-color-warning;
+	}
+
+	.uni-popup__error {
+		color: $uni-color-error;
+	}
+
+	.uni-popup__info {
+		color: #909399;
+	}
+</style>

+ 116 - 0
components/uni-popup/uni-popup-message.vue

@@ -0,0 +1,116 @@
+<template>
+	<view class="uni-popup-message" :class="'uni-popup__'+[type]">
+		<text class="uni-popup-message-text" :class="'uni-popup__'+[type]+'-text'">{{message}}</text>
+	</view>
+</template>
+
+<script>
+	
+	/**
+	 * PopUp 弹出层-消息提示
+	 * @description 弹出层-消息提示
+	 * @tutorial https://ext.dcloud.net.cn/plugin?id=329
+	 * @property {String} type = [success|warning|info|error] 主题样式
+	 *  @value success 成功
+	 * 	@value warning 提示
+	 * 	@value info 消息
+	 * 	@value error 错误
+	 * @property {String} message 消息提示文字
+	 * @property {String} duration 显示时间,设置为 0 则不会自动关闭
+	 */
+	
+	export default {
+		name: 'UniPopupMessage',
+		props: {
+			/**
+			 * 主题 success/warning/info/error	  默认 success
+			 */
+			type: {
+				type: String,
+				default: 'success'
+			},
+			/**
+			 * 消息文字
+			 */
+			message: {
+				type: String,
+				default: ''
+			},
+			/**
+			 * 显示时间,设置为 0 则不会自动关闭
+			 */
+			duration: {
+				type: Number,
+				default: 3000
+			}
+		},
+		inject: ['popup'],
+		data() {
+			return {}
+		},
+		created() {
+			this.popup.childrenMsg = this
+		},
+		methods: {
+			open() {
+				if (this.duration === 0) return
+				clearTimeout(this.popuptimer)
+				this.popuptimer = setTimeout(() => {
+					this.popup.close()
+				}, this.duration)
+			},
+			close() {
+				clearTimeout(this.popuptimer)
+			}
+		}
+	}
+</script>
+<style lang="scss" scoped>
+	.uni-popup-message {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+		background-color: #e1f3d8;
+		padding: 10px 15px;
+		border-color: #eee;
+		border-style: solid;
+		border-width: 1px;
+	}
+	.uni-popup-message-text {
+		font-size: 14px;
+		padding: 0;
+	}
+
+	.uni-popup__success {
+		background-color: #e1f3d8;
+	}
+
+	.uni-popup__success-text {
+		color: #67C23A;
+	}
+
+	.uni-popup__warn {
+		background-color: #faecd8;
+	}
+
+	.uni-popup__warn-text {
+		color: #E6A23C;
+	}
+
+	.uni-popup__error {
+		background-color: #fde2e2;
+	}
+
+	.uni-popup__error-text {
+		color: #F56C6C;
+	}
+
+	.uni-popup__info {
+		background-color: #F2F6FC;
+	}
+
+	.uni-popup__info-text {
+		color: #909399;
+	}
+</style>

+ 263 - 0
components/uni-popup/uni-popup-ori.vue

@@ -0,0 +1,263 @@
+<template>
+	<view v-if="showPopup" class="uni-popup" @touchmove.stop.prevent="clear">
+		<uni-transition :mode-class="['fade']" :styles="maskClass" :duration="duration" :show="showTrans" @click="onTap" />
+		<uni-transition :mode-class="ani" :styles="transClass" :duration="duration" :show="showTrans" @click="onTap">
+			<view class="uni-popup__wrapper-box" @click.stop="clear">
+				<slot />
+			</view>
+		</uni-transition>
+	</view>
+</template>
+
+<script>
+	import uniTransition from '../uni-transition/uni-transition.vue'
+
+	/**
+	 * PopUp 弹出层
+	 * @description 弹出层组件,为了解决遮罩弹层的问题
+	 * @tutorial https://ext.dcloud.net.cn/plugin?id=329
+	 * @property {String} type = [top|center|bottom] 弹出方式
+	 * 	@value top 顶部弹出
+	 * 	@value center 中间弹出
+	 * 	@value bottom 底部弹出
+	 * @property {Boolean} animation = [ture|false] 是否开启动画
+	 * @property {Boolean} maskClick = [ture|false] 蒙版点击是否关闭弹窗
+	 * @event {Function} change 打开关闭弹窗触发,e={show: false}
+	 */
+
+	export default {
+		name: 'UniPopup',
+		components: {
+			uniTransition
+		},
+		props: {
+			// 开启动画
+			animation: {
+				type: Boolean,
+				default: true
+			},
+			// 弹出层类型,可选值,top: 顶部弹出层;bottom:底部弹出层;center:全屏弹出层
+			type: {
+				type: String,
+				default: 'center'
+			},
+			// maskClick
+			maskClick: {
+				type: Boolean,
+				default: true
+			}
+		},
+		data() {
+			return {
+				duration: 300,
+				ani: [],
+				showPopup: false,
+				showTrans: false,
+				maskClass: {
+					'position': 'fixed',
+					'bottom': 0,
+					'top': 0,
+					'left': 0,
+					'right': 0,
+					'backgroundColor': 'rgba(0, 0, 0, 0.4)'
+				},
+				transClass: {
+					'position': 'fixed',
+					'left': 0,
+					'right': 0,
+				}
+			}
+		},
+		watch: {
+			type: {
+				handler: function(newVal) {
+					switch (this.type) {
+						case 'top':
+							this.ani = ['slide-top']
+							this.transClass = {
+								'position': 'fixed',
+								'left': 0,
+								'right': 0,
+							}
+							break
+						case 'bottom':
+							this.ani = ['slide-bottom']
+							this.transClass = {
+								'position': 'fixed',
+								'left': 0,
+								'right': 0,
+								'bottom': 0
+							}
+							break
+						case 'center':
+							this.ani = ['zoom-out', 'fade']
+							this.transClass = {
+								'position': 'fixed',
+								/* #ifndef APP-NVUE */
+								'display': 'flex',
+								'flexDirection': 'column',
+								/* #endif */
+								'bottom': 0,
+								'left': 0,
+								'right': 0,
+								'top': 0,
+								'justifyContent': 'center',
+								'alignItems': 'center'
+							}
+
+							break
+					}
+				},
+				immediate: true
+			}
+		},
+		created() {
+			if (this.animation) {
+				this.duration = 300
+			} else {
+				this.duration = 0
+			}
+		},
+		methods: {
+			clear(e) {
+				// TODO nvue 取消冒泡
+				e.stopPropagation()
+			},
+			open() {
+				this.showPopup = true
+				this.$nextTick(() => {
+					clearTimeout(this.timer)
+					this.timer = setTimeout(() => {
+						this.showTrans = true
+					}, 50);
+				})
+				this.$emit('change', {
+					show: true
+				})
+			},
+			close(type) {
+				this.showTrans = false
+				this.$nextTick(() => {
+					clearTimeout(this.timer)
+					this.timer = setTimeout(() => {
+						this.$emit('change', {
+							show: false
+						})
+						this.showPopup = false
+					}, 300)
+				})
+			},
+			onTap() {
+				if (!this.maskClick) return
+				this.close()
+			}
+		}
+	}
+</script>
+<style lang="scss" scoped>
+	.uni-popup {
+		position: fixed;
+		/* #ifdef H5 */
+		top: var(--window-top);
+		/* #endif */
+		/* #ifndef H5 */
+		top: 0;
+		/* #endif */
+		bottom: 0;
+		left: 0;
+		right: 0;
+		/* #ifndef APP-NVUE */
+		z-index: 99;
+		/* #endif */
+	}
+
+	.uni-popup__mask {
+		position: absolute;
+		top: 0;
+		bottom: 0;
+		left: 0;
+		right: 0;
+		background-color: $uni-bg-color-mask;
+		opacity: 0;
+	}
+
+	.mask-ani {
+		transition-property: opacity;
+		transition-duration: 0.2s;
+	}
+
+	.uni-top-mask {
+		opacity: 1;
+	}
+
+	.uni-bottom-mask {
+		opacity: 1;
+	}
+
+	.uni-center-mask {
+		opacity: 1;
+	}
+
+	.uni-popup__wrapper {
+		/* #ifndef APP-NVUE */
+		display: block;
+		/* #endif */
+		position: absolute;
+	}
+
+	.top {
+		top: 0;
+		left: 0;
+		right: 0;
+		transform: translateY(-500px);
+	}
+
+	.bottom {
+		bottom: 0;
+		left: 0;
+		right: 0;
+		transform: translateY(500px);
+	}
+
+	.center {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		flex-direction: column;
+		/* #endif */
+		bottom: 0;
+		left: 0;
+		right: 0;
+		top: 0;
+		justify-content: center;
+		align-items: center;
+		transform: scale(1.2);
+		opacity: 0;
+	}
+
+	.uni-popup__wrapper-box {
+		/* #ifndef APP-NVUE */
+		display: block;
+		/* #endif */
+		position: relative;
+	}
+
+	.content-ani {
+		// transition: transform 0.3s;
+		transition-property: transform, opacity;
+		transition-duration: 0.2s;
+	}
+
+
+	.uni-top-content {
+		transform: translateY(0);
+	}
+
+	.uni-bottom-content {
+		transform: translateY(0);
+	}
+
+	.uni-center-content {
+		transform: scale(1);
+		opacity: 1;
+	}
+</style>

+ 282 - 0
components/uni-popup/uni-popup-share.vue

@@ -0,0 +1,282 @@
+<template>
+	<view>
+		<uni-popup-ori ref="showshare" type="bottom">
+			<view class="uni-share">
+				<text class="uni-share-title">分享到</text>
+				<view class="uni-share-content">
+					<view v-for="(item, index) in bottomData" :key="index" class="uni-share-content-box" @click="shareTo(item.name)">
+						<view class="uni-share-content-image"><image :src="item.icon" class="content-image" mode="widthFix" /></view>
+						<text class="uni-share-content-text">{{ item.text }}</text>
+					</view>
+				</view>
+				<text class="uni-share-btn" @click="cancel()">取消分享</text>
+			</view>
+		</uni-popup-ori>
+		<uni-popup-ori ref="showPast" type="center" class="popupPast">
+			<view class="backPop">
+				<view class="popPast">
+					<view class="popTitle">口令已复制</view>
+					<view class="popContent">
+						<view>{{ describe }}</view>
+					</view>
+					<view class="popBtn" @click="goWhere(1)" v-if="popType == 'wx'">
+						<!-- <image src="../../static/spend/wxin.png" mode="widthFix"></image> -->
+						<view>去微信粘贴给好友</view>
+					</view>
+					<view class="popBtn" @click="goWhere(2)" v-if="popType == 'timeline'">
+						<!-- <image src="../../static/spend/wechat.png" mode="widthFix"></image> -->
+						<view>粘贴到朋友圈</view>
+					</view>
+				</view>
+				<icon type="cancel" size="26" color="white" style="margin-top: 40rpx;" @click="cancelPo" />
+			</view>
+		</uni-popup-ori>
+	</view>
+</template>
+
+<script>
+import uniPopupOri from '@/components/uni-popup/uni-popup-ori.vue';
+import { mapState, mapMutations } from 'vuex';
+import { getActionPage } from '@/utils/loginUtils.js';
+export default {
+	name: 'SharePopup',
+	components: {
+		uniPopupOri
+	},
+	props: ['opt', 'type','option'],
+	data() {
+		return {
+			describe: '',
+			shareoption: '',
+			bottomData: [
+				{
+					text: '微信',
+					icon: '../../static/spend/wxin.png',
+					name: 'wx'
+				},
+				{
+					text: '朋友圈',
+					icon: '../../static/spend/wechat.png',
+					name: 'timeline'
+				}
+			],
+			popType: '',
+			uid: ''
+		};
+	},
+	computed: {
+		...mapState(['userInfo', 'baseURL'])
+	},
+	mounted() {},
+	methods: {
+		loadData() {
+			try {
+				let prePage = getActionPage();
+				var path = prePage.route;
+				this.uid = this.userInfo.uid;
+				//获取object转化成
+				var parm = '';
+				var i = 0;
+				var option = this.option; //其他页面传值
+				console.log(option,'option')
+				if(this.type == 4){
+					parm = parm + '?' + 'promo_code=' + option;
+				}else{
+					for (let item in option) {
+						//拼接参数
+						if (i == 0) {
+							parm = '?' + item + '=' + option[item];
+						} else {
+							parm = parm + '&' + item + '=' + option[item];
+						}
+						i++;
+					}
+				}
+				if(this.type == 4){
+					var url = 'pages/index/index' + parm;
+				}else{
+					var url = path + parm;
+				}
+				console.log(path,'path')
+				console.log(parm,'parm')
+				//用后台加密
+				//第一个参数是判断是不是我们的链接
+				//第二个参数是访问地址
+				//第三个参数是,类型,type:0商品,type=1拼团,type=2邀请注册,type=3邀请好友参团,type=4邀请好友助力
+				//第四个参数是share的id
+				console.log(option,'option')
+				if(this.type == 4){
+					this.describe = this.baseURL + '@' + url + '@' + this.type + '@' + this.uid + '@复制这段话进入美美赚,自动打开页面';
+				}else{
+					this.describe = this.baseURL + '@' + url + '@' + this.type + '@' + this.uid + '@复制这段话进入美美赚,自动打开页面';
+				}
+				console.log(this.describe);
+				let obj = this;
+				// #ifndef H5
+				uni.setClipboardData({
+					data: this.describe,
+					  success: function () {
+					        uni.hideToast();
+					    }
+				});
+				// #endif
+			} catch (e) {
+				console.log(e);
+				//TODO handle the exception
+			}
+		},
+		goWhere(type) {
+			this.$api.msg('复制成功');
+		},
+		cancelPo() {
+			this.$nextTick(() => {
+				this.$refs['showPast'].close();
+			});
+		},
+		shareTo(name) {
+			this.popType = name;
+			this.$nextTick(() => {
+				this.$refs.showPast.open();
+				this.$refs['showshare'].close();
+			});
+		},
+		cancel() {
+			this.$nextTick(() => {
+				this.$refs['showshare'].close();
+			});
+		},
+		open() {
+			this.$nextTick(() => {
+				this.$refs['showshare'].open();
+			});
+		}
+	}
+};
+</script>
+<style lang="scss" scoped>
+.backPop {
+	padding: 0rpx 25rpx;
+	display: flex;
+	flex-direction: column;
+	justify-content: center;
+	align-items: center;
+}
+.popupPast {
+	display: flex;
+	flex-direction: column;
+	justify-content: center;
+	align-items: center;
+	text-align: center;
+}
+.popPast {
+	display: flex;
+	flex-direction: column;
+	justify-content: center;
+	padding: 40rpx 30rpx;
+	width: 90%;
+	background-color: white;
+	border-radius: 18rpx;
+	align-items: center;
+	.popTitle {
+		color: #2f2f2f;
+		font-size: 32rpx;
+		font-weight: bold;
+		margin-bottom: 40rpx;
+	}
+	.popContent {
+		background-color: #f4f4f4;
+		padding: 30rpx 24rpx;
+		border-radius: 16rpx;
+		view {
+			font-size: 24rpx;
+			color: #939393;
+		}
+		margin-bottom: 40rpx;
+	}
+	.popBtn {
+		display: flex;
+		align-items: center;
+		padding: 20rpx 40rpx;
+		background-color: #04be02;
+		border-radius: 60rpx;
+		image {
+			width: 36rpx;
+		}
+		view {
+			color: white;
+			font-size: 36rpx;
+			margin-left: 10rpx;
+		}
+	}
+}
+/* 底部分享 */
+.uni-share {
+	/* #ifndef APP-NVUE */
+	display: flex;
+	flex-direction: column;
+	/* #endif */
+	background-color: #fff;
+}
+
+.uni-share-title {
+	line-height: 60rpx;
+	font-size: 24rpx;
+	padding: 15rpx 0;
+	text-align: center;
+}
+
+.uni-share-content {
+	/* #ifndef APP-NVUE */
+	display: flex;
+	/* #endif */
+	flex-direction: row;
+	flex-wrap: wrap;
+	justify-content: center;
+	padding: 15px;
+}
+
+.uni-share-content-box {
+	/* #ifndef APP-NVUE */
+	display: flex;
+	/* #endif */
+	flex-direction: column;
+	align-items: center;
+	width: 200rpx;
+}
+
+.uni-share-content-image {
+	/* #ifndef APP-NVUE */
+	display: flex;
+	/* #endif */
+	flex-direction: row;
+	justify-content: center;
+	align-items: center;
+	width: 60rpx;
+	height: 60rpx;
+	overflow: hidden;
+	border-radius: 10rpx;
+}
+
+.content-image {
+	width: 60rpx;
+	height: 60rpx;
+}
+
+.uni-share-content-text {
+	font-size: 26rpx;
+	color: #333;
+	padding-top: 5px;
+	padding-bottom: 10px;
+}
+
+.uni-share-btn {
+	height: 90rpx;
+	line-height: 90rpx;
+	font-size: 14px;
+	border-top-color: #f5f5f5;
+	border-top-width: 1px;
+	border-top-style: solid;
+	text-align: center;
+	color: #666;
+}
+</style>

+ 263 - 0
components/uni-popup/uni-popup.vue

@@ -0,0 +1,263 @@
+<template>
+	<view v-if="showPopup" class="uni-popup" @touchmove.stop.prevent="clear">
+		<uni-transition :mode-class="['fade']" :styles="maskClass" :duration="duration" :show="showTrans" @click="onTap" />
+		<uni-transition :mode-class="ani" :styles="transClass" :duration="duration" :show="showTrans" @click="onTap">
+			<view class="uni-popup__wrapper-box" @click.stop="clear">
+				<slot />
+			</view>
+		</uni-transition>
+	</view>
+</template>
+
+<script>
+	import uniTransition from '../uni-transition/uni-transition.vue'
+
+	/**
+	 * PopUp 弹出层
+	 * @description 弹出层组件,为了解决遮罩弹层的问题
+	 * @tutorial https://ext.dcloud.net.cn/plugin?id=329
+	 * @property {String} type = [top|center|bottom] 弹出方式
+	 * 	@value top 顶部弹出
+	 * 	@value center 中间弹出
+	 * 	@value bottom 底部弹出
+	 * @property {Boolean} animation = [ture|false] 是否开启动画
+	 * @property {Boolean} maskClick = [ture|false] 蒙版点击是否关闭弹窗
+	 * @event {Function} change 打开关闭弹窗触发,e={show: false}
+	 */
+
+	export default {
+		name: 'UniPopup',
+		components: {
+			uniTransition
+		},
+		props: {
+			// 开启动画
+			animation: {
+				type: Boolean,
+				default: true
+			},
+			// 弹出层类型,可选值,top: 顶部弹出层;bottom:底部弹出层;center:全屏弹出层
+			type: {
+				type: String,
+				default: 'center'
+			},
+			// maskClick
+			maskClick: {
+				type: Boolean,
+				default: true
+			}
+		},
+		data() {
+			return {
+				duration: 300,
+				ani: [],
+				showPopup: false,
+				showTrans: false,
+				maskClass: {
+					'position': 'fixed',
+					'bottom': 0,
+					'top': 0,
+					'left': 0,
+					'right': 0,
+					'backgroundColor': 'rgba(0, 0, 0, 0.4)'
+				},
+				transClass: {
+					'position': 'fixed',
+					'left': 0,
+					'right': 0,
+				}
+			}
+		},
+		watch: {
+			type: {
+				handler: function(newVal) {
+					switch (this.type) {
+						case 'top':
+							this.ani = ['slide-top']
+							this.transClass = {
+								'position': 'fixed',
+								'left': 0,
+								'right': 0,
+							}
+							break
+						case 'bottom':
+							this.ani = ['slide-bottom']
+							this.transClass = {
+								'position': 'fixed',
+								'left': 0,
+								'right': 0,
+								'bottom': 0
+							}
+							break
+						case 'center':
+							this.ani = ['zoom-out', 'fade']
+							this.transClass = {
+								'position': 'fixed',
+								/* #ifndef APP-NVUE */
+								'display': 'flex',
+								'flexDirection': 'column',
+								/* #endif */
+								'bottom': 0,
+								'left': 0,
+								'right': 0,
+								'top': 0,
+								'justifyContent': 'center',
+								'alignItems': 'center'
+							}
+
+							break
+					}
+				},
+				immediate: true
+			}
+		},
+		created() {
+			if (this.animation) {
+				this.duration = 300
+			} else {
+				this.duration = 0
+			}
+		},
+		methods: {
+			clear(e) {
+				// TODO nvue 取消冒泡
+				e.stopPropagation()
+			},
+			open() {
+				this.showPopup = true
+				this.$nextTick(() => {
+					clearTimeout(this.timer)
+					this.timer = setTimeout(() => {
+						this.showTrans = true
+					}, 50);
+				})
+				this.$emit('change', {
+					show: true
+				})
+			},
+			close(type) {
+				this.showTrans = false
+				this.$nextTick(() => {
+					clearTimeout(this.timer)
+					this.timer = setTimeout(() => {
+						this.$emit('change', {
+							show: false
+						})
+						this.showPopup = false
+					}, 300)
+				})
+			},
+			onTap() {
+				if (!this.maskClick) return
+				this.close()
+			}
+		}
+	}
+</script>
+<style lang="scss" scoped>
+	.uni-popup {
+		position: fixed;
+		/* #ifdef H5 */
+		top: var(--window-top);
+		/* #endif */
+		/* #ifndef H5 */
+		top: 0;
+		/* #endif */
+		bottom: 0;
+		left: 0;
+		right: 0;
+		/* #ifndef APP-NVUE */
+		z-index: 99;
+		/* #endif */
+	}
+
+	.uni-popup__mask {
+		position: absolute;
+		top: 0;
+		bottom: 0;
+		left: 0;
+		right: 0;
+		background-color: $uni-bg-color-mask;
+		opacity: 0;
+	}
+
+	.mask-ani {
+		transition-property: opacity;
+		transition-duration: 0.2s;
+	}
+
+	.uni-top-mask {
+		opacity: 1;
+	}
+
+	.uni-bottom-mask {
+		opacity: 1;
+	}
+
+	.uni-center-mask {
+		opacity: 1;
+	}
+
+	.uni-popup__wrapper {
+		/* #ifndef APP-NVUE */
+		display: block;
+		/* #endif */
+		position: absolute;
+	}
+
+	.top {
+		top: 0;
+		left: 0;
+		right: 0;
+		transform: translateY(-500px);
+	}
+
+	.bottom {
+		bottom: 0;
+		left: 0;
+		right: 0;
+		transform: translateY(500px);
+	}
+
+	.center {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		flex-direction: column;
+		/* #endif */
+		bottom: 0;
+		left: 0;
+		right: 0;
+		top: 0;
+		justify-content: center;
+		align-items: center;
+		transform: scale(1.2);
+		opacity: 0;
+	}
+
+	.uni-popup__wrapper-box {
+		/* #ifndef APP-NVUE */
+		display: block;
+		/* #endif */
+		position: relative;
+	}
+
+	.content-ani {
+		// transition: transform 0.3s;
+		transition-property: transform, opacity;
+		transition-duration: 0.2s;
+	}
+
+
+	.uni-top-content {
+		transform: translateY(0);
+	}
+
+	.uni-bottom-content {
+		transform: translateY(0);
+	}
+
+	.uni-center-content {
+		transform: scale(1);
+		opacity: 1;
+	}
+</style>

+ 141 - 0
components/uni-rate/uni-rate.vue

@@ -0,0 +1,141 @@
+<template>
+	<view class="uni-rate">
+		<view :key="index" :style="{ marginLeft: margin + 'px' }" @click="_onClick(index)" class="uni-rate__icon" v-for="(star, index) in stars">
+			<uni-icons :color="color" :size="size" :type="isFill ? 'star-filled' : 'star'" />
+			<!-- #ifdef APP-NVUE -->
+			<view :style="{ width: star.activeWitch.replace('%','')*size/100+'px'}" class="uni-rate__icon-on">
+				<uni-icons style="text-align: left;" :color="activeColor" :size="size" type="star-filled" />
+			</view>
+			<!-- #endif -->
+			<!-- #ifndef APP-NVUE -->
+			<view :style="{ width: star.activeWitch,top:-size/2+'px' }" class="uni-rate__icon-on">
+				<uni-icons :color="activeColor" :size="size" type="star-filled" />
+			</view>
+			<!-- #endif -->
+		</view>
+	</view>
+</template>
+
+<script>
+	import uniIcons from "../uni-icons/uni-icons.vue";
+	export default {
+		name: "UniRate",
+		components: {
+			uniIcons
+		},
+		props: {
+			isFill: {
+				// 星星的类型,是否镂空
+				type: [Boolean, String],
+				default: true
+			},
+			color: {
+				// 星星的颜色
+				type: String,
+				default: "#ececec"
+			},
+			activeColor: {
+				// 星星选中状态颜色
+				type: String,
+				default: "#ffca3e"
+			},
+			size: {
+				// 星星的大小
+				type: [Number, String],
+				default: 24
+			},
+			value: {
+				// 当前评分
+				type: [Number, String],
+				default: 0
+			},
+			max: {
+				// 最大评分
+				type: [Number, String],
+				default: 5
+			},
+			margin: {
+				// 星星的间距
+				type: [Number, String],
+				default: 0
+			},
+			disabled: {
+				// 是否可点击
+				type: [Boolean, String],
+				default: false
+			}
+		},
+		data() {
+			return {
+				valueSync: ""
+			};
+		},
+		computed: {
+			stars() {
+				const value = this.valueSync ? this.valueSync : 0;
+				const starList = [];
+				const floorValue = Math.floor(value);
+				const ceilValue = Math.ceil(value);
+				// console.log("ceilValue: " + ceilValue);
+				// console.log("floorValue: " + floorValue);
+				for (let i = 0; i < this.max; i++) {
+					if (floorValue > i) {
+						starList.push({
+							activeWitch: "100%"
+						});
+					} else if (ceilValue - 1 === i) {
+						starList.push({
+							activeWitch: (value - floorValue) * 100 + "%"
+						});
+					} else {
+						starList.push({
+							activeWitch: "0"
+						});
+					}
+				}
+				console.log("starList[4]: " + starList[4].activeWitch);
+				return starList;
+			}
+		},
+		created() {
+			this.valueSync = Number(this.value);
+		},
+		methods: {
+			_onClick(index) {
+				if (this.disabled) {
+					return;
+				}
+				this.valueSync = index + 1;
+				this.$emit("change", {
+					value: this.valueSync
+				});
+			}
+		}
+	};
+</script>
+
+<style lang="scss" scoped>
+	.uni-rate {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		line-height: 0;
+		font-size: 0;
+		flex-direction: row;
+	}
+
+	.uni-rate__icon {
+		position: relative;
+		line-height: 0;
+		font-size: 0;
+	}
+
+	.uni-rate__icon-on {
+		overflow: hidden;
+		position: absolute;
+		top: 0;
+		left: 0;
+		line-height: 1;
+		text-align: left;
+	}
+</style>

+ 244 - 0
components/uni-steps/uni-steps.vue

@@ -0,0 +1,244 @@
+<template>
+	<view class="uni-steps">
+		<view :class="[direction==='column'?'uni-steps__column':'uni-steps__row']">
+			<view :class="[direction==='column'?'uni-steps__column-text-container':'uni-steps__row-text-container']">
+				<view v-for="(item,index) in options" :key="index" :class="[direction==='column'?'uni-steps__column-text':'uni-steps__row-text']">
+					<text :style="{color:index<=active?activeColor:deactiveColor}" :class="[direction==='column'?'uni-steps__column-title':'uni-steps__row-title']">{{item.status}}</text>
+					<text :style="{color:index<=active?activeColor:deactiveColor}" :class="[direction==='column'?'uni-steps__column-desc':'uni-steps__row-desc']">{{item.context}}</text>
+					<text :style="{color:index<=active?activeColor:deactiveColor}" :class="[direction==='column'?'uni-steps__column-desc':'uni-steps__row-desc']">{{item.time}}</text>
+				</view>
+			</view>
+			<view :class="[direction==='column'?'uni-steps__column-container':'uni-steps__row-container']">
+				<view :class="[direction==='column'?'uni-steps__column-line-item':'uni-steps__row-line-item']" v-for="(item,index) in options"
+				 :key="index">
+					<view :class="[direction==='column'?'uni-steps__column-line':'uni-steps__row-line',direction==='column'?'uni-steps__column-line--before':'uni-steps__row-line--before']"
+					 :style="{backgroundColor:index<=active&&index!==0?activeColor:index===0?'transparent':deactiveColor}"></view>
+					<view :class="[direction==='column'?'uni-steps__column-check':'uni-steps__row-check']" v-if="index === active">
+						<uni-icons :color="activeColor" type="checkbox-filled" size="14"></uni-icons>
+					</view>
+					<view :class="[direction==='column'?'uni-steps__column-circle':'uni-steps__row-circle']" v-else :style="{backgroundColor:index<active?activeColor:deactiveColor}"></view>
+					<view :class="[direction==='column'?'uni-steps__column-line':'uni-steps__row-line',direction==='column'?'uni-steps__column-line--after':'uni-steps__row-line--after']"
+					 :style="{backgroundColor:index<active&&index!==options.length-1?activeColor:index===options.length-1?'transparent':deactiveColor}"></view>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	import uniIcons from '../uni-icons/uni-icons.vue'
+	export default {
+		name: 'UniSteps',
+		components: {
+			uniIcons
+		},
+		props: {
+			direction: {
+				// 排列方向 row column
+				type: String,
+				default: 'row'
+			},
+			activeColor: {
+				// 激活状态颜色
+				type: String,
+				default: '#1aad19'
+			},
+			deactiveColor: {
+				// 未激活状态颜色
+				type: String,
+				default: '#999999'
+			},
+			active: {
+				// 当前步骤
+				type: Number,
+				default: 0
+			},
+			options: {
+				type: Array,
+				default () {
+					return []
+				}
+			} // 数据
+		},
+		data() {
+			return {}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	.uni-steps {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		width: 100%;
+		/* #endif */
+		/* #ifdef APP-NVUE */
+		flex: 1;
+		/* #endif */
+		flex-direction: column;
+	}
+
+	.uni-steps__row {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: column;
+	}
+
+	.uni-steps__column {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: row-reverse;
+	}
+
+	.uni-steps__row-text-container {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+	}
+
+	.uni-steps__column-text-container {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: column;
+		flex: 1;
+	}
+
+	.uni-steps__row-text {
+		/* #ifndef APP-NVUE */
+		display: inline-flex;
+		/* #endif */
+		flex: 1;
+		flex-direction: column;
+	}
+
+	.uni-steps__column-text {
+		padding: 6px 0px;
+		border-bottom-style: solid;
+		border-bottom-width: 1px;
+		border-bottom-color: $uni-border-color;
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: column;
+	}
+
+	.uni-steps__row-title {
+		font-size: $uni-font-size-base;
+		line-height: 16px;
+		text-align: center;
+	}
+
+	.uni-steps__column-title {
+		font-size: $uni-font-size-base;
+		text-align: left;
+		line-height: 18px;
+	}
+
+	.uni-steps__row-desc {
+		font-size: 12px;
+		line-height: 14px;
+		text-align: center;
+	}
+
+	.uni-steps__column-desc {
+		font-size: $uni-font-size-sm;
+		text-align: left;
+		line-height: 18px;
+	}
+
+	.uni-steps__row-container {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+	}
+
+	.uni-steps__column-container {
+		/* #ifndef APP-NVUE */
+		display: inline-flex;
+		/* #endif */
+		width: 30px;
+		flex-direction: column;
+	}
+
+	.uni-steps__row-line-item {
+		/* #ifndef APP-NVUE */
+		display: inline-flex;
+		/* #endif */
+		flex-direction: row;
+		flex: 1;
+		height: 14px;
+		line-height: 14px;
+		align-items: center;
+		justify-content: center;
+	}
+
+	.uni-steps__column-line-item {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: column;
+		flex: 1;
+		align-items: center;
+		justify-content: center;
+	}
+
+	.uni-steps__row-line {
+		flex: 1;
+		height: 1px;
+		background-color: $uni-text-color-grey;
+	}
+
+	.uni-steps__column-line {
+		width: 1px;
+		background-color: $uni-text-color-grey;
+	}
+
+	.uni-steps__row-line--after {
+		transform: translateX(1px);
+	}
+
+	.uni-steps__column-line--after {
+		flex: 1;
+		transform: translate(0px, 1px);
+	}
+
+	.uni-steps__row-line--before {
+		transform: translateX(-1px);
+	}
+
+	.uni-steps__column-line--before {
+		height: 6px;
+		transform: translate(0px, -1px);
+	}
+
+	.uni-steps__row-circle {
+		width: 5px;
+		height: 5px;
+		border-radius: 100px;
+		background-color: $uni-text-color-grey;
+		margin: 0px 3px;
+	}
+
+	.uni-steps__column-circle {
+		width: 5px;
+		height: 5px;
+		border-radius: 100px;
+		background-color: $uni-text-color-grey;
+		margin: 4px 0px 5px 0px;
+	}
+
+	.uni-steps__row-check {
+		margin: 0px 6px;
+	}
+
+	.uni-steps__column-check {
+		height: 14px;
+		line-height: 14px;
+		margin: 2px 0px;
+	}
+</style>

+ 279 - 0
components/uni-transition/uni-transition.vue

@@ -0,0 +1,279 @@
+<template>
+	<view v-if="isShow" ref="ani" class="uni-transition" :class="[ani.in]" :style="'transform:' +transform+';'+stylesObject"
+	 @click="change">
+		 <slot></slot>
+	</view>
+</template>
+
+<script>
+	// #ifdef APP-NVUE
+	const animation = uni.requireNativePlugin('animation');
+	// #endif
+	/**
+	 * Transition 过渡动画
+	 * @description 简单过渡动画组件
+	 * @tutorial https://ext.dcloud.net.cn/plugin?id=985
+	 * @property {Boolean} show = [false|true] 控制组件显示或隐藏
+     * @property {Array} modeClass = [fade|slide-top|slide-right|slide-bottom|slide-left|zoom-in|zoom-out] 过渡动画类型
+     *  @value fade 渐隐渐出过渡
+     *  @value slide-top 由上至下过渡
+     *  @value slide-right 由右至左过渡
+     *  @value slide-bottom 由下至上过渡
+     *  @value slide-left 由左至右过渡
+     *  @value zoom-in 由小到大过渡
+     *  @value zoom-out 由大到小过渡
+	 * @property {Number} duration 过渡动画持续时间
+	 * @property {Object} styles 组件样式,同 css 样式,注意带’-‘连接符的属性需要使用小驼峰写法如:`backgroundColor:red`
+	 */
+	export default {
+		name: 'uniTransition',
+		props: {
+			show: {
+				type: Boolean,
+				default: false
+			},
+			modeClass: {
+				type: Array,
+				default () {
+					return []
+				}
+			},
+			duration: {
+				type: Number,
+				default: 300
+			},
+			styles: {
+				type: Object,
+				default () {
+					return {}
+				}
+			}
+		},
+		data() {
+			return {
+				isShow: false,
+				transform: '',
+				ani: { in: '',
+					active: ''
+				}
+			};
+		},
+		watch: {
+			show: {
+				handler(newVal) {
+					if (newVal) {
+						this.open()
+					} else {
+						this.close()
+					}
+				},
+				immediate: true
+			}
+		},
+		computed: {
+			stylesObject() {
+				let styles = {
+					...this.styles,
+					'transition-duration': this.duration / 1000 + 's'
+				}
+				let transfrom = ''
+				for (let i in styles) {
+					let line = this.toLine(i)
+					transfrom += line + ':' + styles[i] + ';'
+				}
+				return transfrom
+			}
+		},
+		created() {
+			// this.timer = null
+			// this.nextTick = (time = 50) => new Promise(resolve => {
+			// 	clearTimeout(this.timer)
+			// 	this.timer = setTimeout(resolve, time)
+			// 	return this.timer
+			// });
+		},
+		methods: {
+			change() {
+				this.$emit('click', {
+					detail: this.isShow
+				})
+			},
+			open() {
+				clearTimeout(this.timer)
+				this.isShow = true
+				this.transform = ''
+				this.ani.in = ''
+				for (let i in this.getTranfrom(false)) {
+					if (i === 'opacity') {
+						this.ani.in = 'fade-in'
+					} else {
+						this.transform += `${this.getTranfrom(false)[i]} `
+					}
+				}
+				this.$nextTick(() => {
+					setTimeout(() => {
+						this._animation(true)
+					}, 50)
+				})
+
+			},
+			close(type) {
+				clearTimeout(this.timer)
+				this._animation(false)
+			},
+			_animation(type) {
+				let styles = this.getTranfrom(type)
+				// #ifdef APP-NVUE
+				if(!this.$refs['ani']) return
+				animation.transition(this.$refs['ani'].ref, {
+					styles,
+					duration: this.duration, //ms
+					timingFunction: 'ease',
+					needLayout: false,
+					delay: 0 //ms
+				}, () => {
+					if (!type) {
+						this.isShow = false
+					}
+					this.$emit('change', {
+						detail: this.isShow
+					})
+				})
+				// #endif
+				// #ifndef APP-NVUE
+				this.transform = ''
+				for (let i in styles) {
+					if (i === 'opacity') {
+						this.ani.in = `fade-${type?'out':'in'}`
+					} else {
+						this.transform += `${styles[i]} `
+					}
+				}
+				this.timer = setTimeout(() => {
+					if (!type) {
+						this.isShow = false
+					}
+					this.$emit('change', {
+						detail: this.isShow
+					})
+
+				}, this.duration)
+				// #endif
+
+			},
+			getTranfrom(type) {
+				let styles = {
+					transform: ''
+				}
+				this.modeClass.forEach((mode) => {
+					switch (mode) {
+						case 'fade':
+							styles.opacity = type ? 1 : 0
+							break;
+						case 'slide-top':
+							styles.transform += `translateY(${type?'0':'-100%'}) `
+							break;
+						case 'slide-right':
+							styles.transform += `translateX(${type?'0':'100%'}) `
+							break;
+						case 'slide-bottom':
+							styles.transform += `translateY(${type?'0':'100%'}) `
+							break;
+						case 'slide-left':
+							styles.transform += `translateX(${type?'0':'-100%'}) `
+							break;
+						case 'zoom-in':
+							styles.transform += `scale(${type?1:0.8}) `
+							break;
+						case 'zoom-out':
+							styles.transform += `scale(${type?1:1.2}) `
+							break;
+					}
+				})
+				return styles
+			},
+			_modeClassArr(type) {
+				let mode = this.modeClass
+				if (typeof(mode) !== "string") {
+					let modestr = ''
+					mode.forEach((item) => {
+						modestr += (item + '-' + type + ',')
+					})
+					return modestr.substr(0, modestr.length - 1)
+				} else {
+					return mode + '-' + type
+				}
+			},
+			// getEl(el) {
+			// 	console.log(el || el.ref || null);
+			// 	return el || el.ref || null
+			// },
+			toLine(name) {
+				return name.replace(/([A-Z])/g, "-$1").toLowerCase();
+			}
+		}
+	}
+</script>
+
+<style>
+	.uni-transition {
+		transition-timing-function: ease;
+		transition-duration: 0.3s;
+		transition-property: transform, opacity;
+	}
+
+	.fade-in {
+		opacity: 0;
+	}
+
+	.fade-active {
+		opacity: 1;
+	}
+
+	.slide-top-in {
+		/* transition-property: transform, opacity; */
+		transform: translateY(-100%);
+	}
+
+	.slide-top-active {
+		transform: translateY(0);
+		/* opacity: 1; */
+	}
+
+	.slide-right-in {
+		transform: translateX(100%);
+	}
+
+	.slide-right-active {
+		transform: translateX(0);
+	}
+
+	.slide-bottom-in {
+		transform: translateY(100%);
+	}
+
+	.slide-bottom-active {
+		transform: translateY(0);
+	}
+
+	.slide-left-in {
+		transform: translateX(-100%);
+	}
+
+	.slide-left-active {
+		transform: translateX(0);
+		opacity: 1;
+	}
+
+	.zoom-in-in {
+		transform: scale(0.8);
+	}
+
+	.zoom-out-active {
+		transform: scale(1);
+	}
+
+	.zoom-out-in {
+		transform: scale(1.2);
+	}
+</style>

+ 226 - 0
components/upload-images.vue

@@ -0,0 +1,226 @@
+<template>
+	<view class="upload-content">
+		<block v-for="(item, index) in imageList" :key="index">
+			<view class="upload-item">
+				<image class="upload-img" :src="item.filePath" mode="aspectFill" @click="previewImage(index)"></image>
+				<image class="upload-del-btn" 
+					@click="delImage(index)" 
+					src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACYAAAAmCAYAAACoPemuAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6RjNBODgzQjUwNDM5MTFFOUJDMjlGN0UwRTJGMjVCNjQiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6RjNBODgzQjYwNDM5MTFFOUJDMjlGN0UwRTJGMjVCNjQiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpGM0E4ODNCMzA0MzkxMUU5QkMyOUY3RTBFMkYyNUI2NCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpGM0E4ODNCNDA0MzkxMUU5QkMyOUY3RTBFMkYyNUI2NCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PuYQTIAAAAHNSURBVHjazJgxasMwFIbjnsD4BDmCLxDQDZrNQ5bcILlBvXvpDRLIWmiHFkyH2t0LyVbwEkPpbE/eYlUCGYQax096UuIffkIgij7ryU/vyZuYy2e+Z56K70R8lsJcOfPn5AriMCvmZ2YKdMW8EQ/hRA9iEorwXlpZtObMRySQ6kysvrFWloHUEIcmUBuHUDLccmxQskErt7wyVLdy00tQ4Q2g5De2V9kNwWjffiNDA33f/42i6E13Qj5uNptlwJD+SyODuaqqqi/KVBTFOxSKA7VtW/NxSZI8AcbE2nuraZpvKgSBk6G4drsdBOwogz1CwAghH/JEl+BUKJ1VltPHHjoIAoeE4l53YFqb+RKcBSgqyqXhtxEKZwkKB3YOThYCqjPuCDoHZwGK3mGLtdPp5LmqTI1Dqe4p26EkNqA4iE6eg2z+iQ0okyQMATvYgLIIt9Y6knTyFBIudHqIq3BpmkL60VJ9O0sXZY8MBwSLtdNGEAQ/i8XiVXcz83EcEPDbuq/fzMdYWt+6GTmMsX2rh9q3TtsxNrzXhKt1rwg6rR1Dhdjqo7QMlWOvoWTF4imxQMRF7eaL8L5ohmwrLgDB8pCQc8DlcG7y538CDABJNGPqfaJgfgAAAABJRU5ErkJggg==" 
+					mode="scaleToFill">
+				</image>
+				<view class="upload-progress" v-if="item.progress < 100">{{item.progress}}%</view>
+			</view>
+		</block>
+		<view class="upload-add-btn" v-if="rduLength > 0" @click="chooseImage"></view>
+	</view>
+</template>
+
+<script>
+export default {
+	data() {
+		return {
+			imageList: []
+		};
+	},
+	props: {
+		url: {
+			type: String,
+			value: '' //上传接口地址
+		},
+		count: {
+			type: Number,
+			value: 4 //单次可选择的图片数量
+		},
+		length: {
+			type: Number,
+			value: 50 //可上传总数量
+		}
+	},
+	computed: {
+		rduLength(){
+			return this.length - this.imageList.length;
+		}
+	},
+	methods: {
+		//选择图片
+		chooseImage: function(){
+			uni.chooseImage({
+				count: this.rduLength < this.count ? this.rduLength : this.count, //最多可以选择的图片张数,默认9
+				sizeType: ['original', 'compressed'], //original 原图,compressed 压缩图,默认二者都有
+				sourceType: ['album'], //album 从相册选图,camera 使用相机,默认二者都有
+				success: (res)=> {
+					const images = res.tempFilePaths;
+					this.uploadFiles(images);
+				}
+			});
+		},
+		//上传图片
+		async uploadFiles(images){
+			this.imageList.push({
+				filePath: images[0],
+				progress: 0
+			});
+			uni.showLoading({
+				title: '请稍后..',
+				mask: true,
+			})
+			try{
+				const uploadUrl = await this.uploadImage(images[0]);
+			}catch(err){
+				console.log(err);
+				return;
+			}
+			
+			if(uploadUrl !== false){
+				images.splice(0, 1);
+				this.imageList[this.imageList.length - 1].src = uploadUrl;
+
+				//判断是否需要继续上传
+				if(images.length > 0 && this.rduLength > 0){
+					this.uploadFiles(images);
+				}else{
+					uni.hideLoading();
+				}
+			}else{
+				//上传失败处理
+				this.imageList.pop();
+				uni.hideLoading();
+				uni.showToast({
+					title: '上传中出现问题,已终止上传',
+					icon: 'none',
+					mask: true,
+					duration: 2000
+				});
+			}
+		},
+		uploadImage: function(file){
+			return new Promise((resolve, reject)=> {
+				//发送给后端的附加参数
+				const formData = {
+					thumb_mode: 1,  
+				};
+				this.uploadTask = uni.uploadFile({
+					url: this.url, 
+					filePath: file,
+					name: 'file',
+					formData: formData,
+					success(uploadFileResult){
+						const uploadFileRes = JSON.parse(uploadFileResult.data) || {};
+						if(uploadFileRes.status === 1 && uploadFileRes.data){
+							resolve(uploadFileRes.data);
+						}else{
+							reject('接口返回错误');
+						}
+					}, 
+					fail(){
+						reject('网络链接错误');
+					}
+				});
+				//上传进度
+				this.uploadTask.onProgressUpdate((progressRes)=> {
+					this.imageList[this.imageList.length - 1].progress = progressRes.progress;
+				});
+			});
+		},
+		//删除图片
+		delImage: function(index){
+			uni.showModal({
+				content: '确定要放弃这张图片么?',
+				success: (confirmRes)=> {
+					if (confirmRes.confirm) {
+						this.imageList.splice(index, 1);
+					} 
+				}
+			});
+		},
+		//预览图片
+		previewImage: function(index){
+			const urls = [];
+			this.imageList.forEach((item)=> {
+				urls.push(item.filePath);
+			})
+			uni.previewImage({
+				current: urls[index],
+				urls: urls,
+				indicator: "number"
+			})
+		}
+	}
+}
+</script>
+
+<style lang="scss">
+	.upload-content{
+		padding:24upx 0 0 28upx;
+		background-color: #fff;
+		overflow:hidden;
+	}
+	.upload-item{
+		position: relative;
+		float:left;
+		width:150upx;
+		height:150upx;
+		margin-right:30upx;
+		margin-bottom:30upx;
+		&:nth-child(4n){
+			margin-right:0;
+		}
+		.upload-img{
+			width:100%;
+			height:100%;
+			border-radius:8upx;
+		}
+		.upload-del-btn{
+			position: absolute;
+			right:-16upx;
+			top:-14upx;
+			width:36upx;
+			height:36upx;
+			border: 4upx solid #fff;
+			border-radius: 100px;
+		}
+		.upload-progress{
+			position: absolute;
+			left:0;
+			top:0;
+			display:flex;
+			align-items:center;
+			justify-content: center;
+			width:100%;
+			height:100%;
+			background-color: rgba(0,0,0,.4);
+			color:#fff;
+			font-size:24upx;
+			border-radius:8upx;
+		}
+	}
+	.upload-add-btn {
+		position: relative;
+		float:left;
+		width: 150upx;
+		height: 150upx;
+		z-index: 99;
+		border-radius:8upx;
+		background:#f9f9f9;
+		&:before,
+		&:after {
+			content: " ";
+			position: absolute;
+			top: 50%;
+			left: 50%;
+			-webkit-transform: translate(-50%, -50%);
+			transform: translate(-50%, -50%);
+			width: 4upx;
+			height: 60upx;
+			background-color: #d6d6d6;
+		}
+		&:after {
+			width: 60upx;
+			height: 4upx;
+		}
+		&:active {
+			background-color: #f7f7f7;
+		}
+	}
+
+</style>

+ 4914 - 0
components/wangding-pickerAddress/data.js

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

+ 25 - 0
components/wyb-drop-down/iconfont.css

@@ -0,0 +1,25 @@
+@font-face {font-family: "iconfont";
+  src: url('data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAAAMoAAsAAAAABxwAAALZAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHEIGVgCDHAqBYIFNATYCJAMQCwoABCAFhG0HPRs0BsgusG3YowCKZblG/A248I8FOyAe+Oye987uPHySFUCVQo0KTFJCRZnz739uc6TZ6xcSFkmmSfPs/p8faiVYyJAysa8jwJbrp83gaCY/j/s8l9O7q4ACmd/Ochtr0Z5+1AswDijQsQZt4QI6QW6pXcSBwSv/1nYCCqqqsdC7M5oKSRHpFxDmudwQkr2QKEbZvCA7MzUKh2Tkk93JOxzUvw//aBnyJDIpMnTQjLYt1H4v9UeQN/18GCcJYQJ3dgYpGiMSZs2mppCFiJ8qyI6iLfLyEn7P/b7mjxDH1smHZfQvjyCRErJIZWHQ2kun/J7jNIHfTzQJf4gVUvghxnw/0x4DfV5lXaBHlFVi7aO0/bxWdv6+cPdRmdlrbzn+qfO6PVN6994+P/v5PWXZ++LdvWUs9gsXP7QrtrfB91P9aflbn1u37JV5vmPHmbH/a+bW/pculdBKpMfTy/pJ27c1d3s3Ha40b4Xnb7c73PMn7hxVM+r+psb222nvS698xqM+qUXITmlvaakgkG5KhgsC6ePkJarZcby5/0ws1vn5QhH8iDY/1tYIZoAvBViBWAWwLpYq4ogxV3N5FYzKghIJClIX6MxR0m9XGMuAawnyyhwI5lRpkcqrTUZeY2QU0hpZed1QUCP9ZhdSyhKyEAugoe0OguIeIlHUK6SKe09G3q/IKOs/ZBUPKQoaEUotWUjdMDvda4XGBm2YcoTZFkWMuTZpUz1Gvg2MVVelBXNcrd0otGjafJjtYoSrK0Ys3fHKsg2DGSsKoa+zGwYBwaIVeWhtCrM2m0W1WbMZc3cqTKVQSOtlBRk2kA1McQRms5AIk8030qPvjyFuK2BYrTAW0nNoZc1tH7TQVHMA2dVHIMav3LLUDlexzAYDZlghIdDX8aKApghYNN/JgywbBbMOJBaqmnmNZkD1heeNIYZboCByVgxJSEMMGdlpW5fTVplG+0he5RqCAAAAAA==') format('woff2')
+}
+
+.iconfont {
+  font-family: "iconfont" !important;
+  margin-top: 5rpx;
+  font-style: normal;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+
+.icon-selected:before {
+  content: "\e613";
+}
+
+.icon-down:before {
+  font-weight: bold;
+  content: "\e67a";
+}
+
+.icon-down-fill:before {
+  content: "\e701";
+}
+

+ 421 - 0
components/wyb-drop-down/wyb-drop-down.vue

@@ -0,0 +1,421 @@
+<template>
+	<view 
+	 class="wyb-drop-down-box"
+	 :style="{
+		 '--duration': duration + 'ms',
+		 '--autoContentTop': autoContentTop}">
+		
+		<view class="wyb-drop-down-container" @tap.stop.prevent @touchmove.stop.prevent>
+			<view 
+			 class="wyb-drop-down-header"
+			 :style="{
+				 zIndex: zIndex,
+				 backgroundColor: bgColor.header}">
+				<view 
+				 class="wyb-drop-down-header-item"
+				 v-for="(item,index) in options"
+				 :key="item.header"
+				 @tap.stop="onHeaderTap(index)"
+				 :style="{fontSize: fontSize.header + 'rpx'}">
+					<text
+					 class="wyb-drop-down-header-item-label"
+					 :style="{
+						 fontWeight: headerActiveIndex === index && dropOver && activeWeight ? 'bold' : 'normal',
+						 color: headerActiveIndex === index && dropOver ? activeColor: defaultColor}">{{item.header}}</text>
+					<text
+					 v-if="dropIcon === 'fill'"
+					 class="iconfont icon-down-fill wyb-drop-down-header-item-icon"
+					 :class="[headerActiveIndex === index && dropOver ? 'wyb-drop-down-header-item-icon-active' : '']"
+					 :style="{
+						 fontSize: fontSize.header - 5 + 'rpx',
+						 color: headerActiveIndex === index && dropOver ? activeColor: defaultColor}" />
+					<text
+					 v-if="dropIcon==='line'"
+					 class="iconfont icon-down wyb-drop-down-header-item-icon"
+					 :class="[headerActiveIndex === index && dropOver ? 'wyb-drop-down-header-item-icon-active' : '']"
+					 :style="{
+						 fontSize: fontSize.header - 5 + 'rpx',
+						 transformOrigin: '50% 45%',
+						 color: headerActiveIndex === index && dropOver ? activeColor: defaultColor}" />
+					<view class="wyb-drop-down-vline" v-if="index !== options.length - 1" />
+				</view>
+			</view>
+			
+			<scroll-view 
+			 v-if="dropDown"
+			 class="wyb-drop-down-content" 
+			 :class="[dropOver ? 'wyb-drop-down-content-active' : '']"
+			 :scroll-y="scroll"
+			 :enable-flex="true"
+			 :scroll-anchoring="true"
+			 :style="{
+				 zIndex: zIndex - 1,
+				 fontSize: fontSize.content + 'rpx',
+				 backgroundColor: bgColor.content,
+				 borderBottomLeftRadius: radius + 'px',
+				 borderBottomRightRadius: radius + 'px',
+				 minHeight: minHeight + 'rpx',
+				 height: autoHeight ? 'auto' : minHeight + 'rpx',
+				 maxHeight: autoHeight && maxHeight ? maxHeight + 'rpx' : 'auto'}">
+				 <view class="wyb-drop-down-content-box" v-for="(item,index) in options" :key="contentBoxKey(index)">
+				 	<view v-if="item['custom'] && headerActiveIndex === index && dropDown" class="wyb-drop-down-content-slot">
+						<slot></slot>
+					</view>
+					<view
+					 v-if="!item['custom'] && headerActiveIndex === index && dropDown"
+					 class="wyb-drop-down-content-item"
+					 v-for="(content,zIndex) in item['contents']"
+					 :key="content"
+					 @tap.stop="onContentItemsTap(zIndex)">
+						<text
+						 class="wyb-drop-down-content-item-label"
+						 :style="{color: contentActiveIndexList[headerActiveIndex]['index'] === zIndex && dropOver ? activeColor: defaultColor}">
+							{{content}}
+						</text>
+						<text
+						 v-if="contentActiveIndexList[headerActiveIndex]['index'] === zIndex && dropOver"
+						 :style="{color: activeColor}"
+						 class="iconfont icon-selected wyb-drop-down-content-item-icon" />
+						<view class="wyb-drop-down-line" v-if="zIndex !== options[headerActiveIndex].contents.length - 1" />
+					</view>
+				 </view>
+			</scroll-view>
+		</view>
+		
+		<view
+		 v-if="dropDown"
+		 class="wyb-drop-down-mask"
+		 :class="[dropOver ? 'wyb-drop-down-mask-active' : '']"
+		 @tap.stop="close"
+		 @touchmove.stop.prevent
+		 :style="{
+			 zIndex: zIndex - 2,
+			 height: screenHeight + 'px',
+			 backgroundColor: 'rgba(0, 0, 0, ' + maskAlpha + ')'}" />
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				dropDown: false,
+				dropOver: false,
+				duration: 500,
+				contents: this.options[0].contents || [0],
+				headerActiveIndex: 0,
+				contentActiveIndexList: []
+			}
+		},
+		computed: {
+			autoContentTop() {
+				return `${44 + this.rpxToPx(100)}px`
+			},
+			screenHeight() {
+				return uni.getSystemInfoSync().screenHeight
+			},
+			screenWidth() {
+				return uni.getSystemInfoSync().screenWidth
+			},
+			contentBoxKey() {
+				return function(index) {
+					return `option${index}`
+				}
+			}
+		},
+		props: {
+			options: {
+				type: Array,
+				default() {
+					return [{
+						header: 'A',
+						contents: ['1', '2']
+					}]
+				}
+			},
+			defaultIndexList: {
+				type: Array,
+				default() {
+					return []
+				}
+			},
+			autoHeight: {
+				type: Boolean,
+				default: true
+			},
+			minHeight: {
+				type: [String, Number],
+				default: 10
+			},
+			maxHeight: {
+				type: [String, Number],
+				default: 600
+			},
+			scroll: {
+				type: Boolean,
+				default: true
+			},
+			radius: {
+				type: [String, Number],
+				default: '0'
+			},
+			activeColor: {
+				type: String,
+				default: '#e1c0a0'
+			},
+			activeWeight: {
+				type: Boolean,
+				default: true
+			},
+			defaultColor: {
+				type: String,
+				default: '#fff'
+			},
+			bgColor: {
+				type: Object,
+				default() {
+					return {
+						header: '#fff',
+						content: '#fff'
+					}
+				}
+			},
+			dropIcon: {
+				type: String,
+				default: 'fill'
+			},
+			fontSize: {
+				type: Object,
+				default() {
+					return {
+						header: 30,
+						content: 30
+					}
+				}
+			},
+			maskAlpha: {
+				type: [String, Number],
+				default: '0.5'
+			},
+			zIndex: {
+				type: Number,
+				default: 500
+			}
+		},
+		mounted() {
+			if (this.defaultIndexList.length === 0) {
+				this.options.forEach((item, index) => {
+					if (!item.custom) {
+						this.contentActiveIndexList.push({headerIndex: index, index: 0})
+					} else {
+						this.contentActiveIndexList.push({headerIndex: index, custom: true})
+					}
+				})
+			} else {
+				let i = 0
+				this.options.forEach((item, index) => {
+					if (!item.custom) {
+						this.contentActiveIndexList.push([...this.defaultIndexList][i])
+						i++
+					} else {
+						this.contentActiveIndexList.push({headerIndex: index, custom: true})
+					}
+				})
+			}
+		},
+		methods: {
+			onHeaderTap(index) {
+				let item = this.options[index]
+				if (Object.is(this.headerActiveIndex, index) && this.dropOver) {
+					this.close()
+				} else {
+					this.headerActiveIndex = index
+					if (item.custom) {
+						this.$emit('change', {
+							headerIndex: index,
+							header: this.options[index].header
+						})
+					}
+					this.dropDown = true
+					this.$nextTick(() => {
+						this.dropOver = true
+						this.$emit('show')
+					})
+				}
+			},
+			onContentItemsTap(index) {
+				this.contentActiveIndexList[this.headerActiveIndex]['index'] = index
+				this.$forceUpdate()
+				let event = {
+					headerIndex: this.headerActiveIndex,
+					header: this.options[this.headerActiveIndex]['header'],
+					contentIndex: index,
+					content: this.options[this.headerActiveIndex]['contents'][index],
+					contentActiveIndexList: this.contentActiveIndexList
+				}
+				this.$emit('select', event)
+			},
+			close() {
+				this.dropOver = false
+				setTimeout(() => {
+					this.dropDown = false
+					this.$emit('hide')
+				}, this.duration)
+			},
+			rpxToPx(rpx) {
+				return rpx / 750 * this.screenWidth
+			}
+		}
+	}
+</script>
+
+<style>
+	@import './iconfont.css';
+	.wyb-drop-down-mask {
+		position: fixed;
+		top: 44px;
+		/* #ifndef H5 */
+		top: 0;
+		/* #endif */
+		left: 0;
+		bottom: 0;
+		right: 0;
+		opacity: 0;
+		transition: opacity var(--duration);
+		z-index: 498;
+	}
+	
+	.wyb-drop-down-mask-active {
+		opacity: 1;
+		transition: opacity var(--duration);
+	}
+	
+	.wyb-drop-down-header {
+		position: fixed;
+		top: 44px;
+		/* #ifndef H5 */
+		top: 0;
+		/* #endif */
+		left: 0;
+		right: 0;
+		display: flex;
+		flex-direction: row;
+		background-color: #fff;
+		z-index: 500;
+	}
+	
+	.wyb-drop-down-header-item {
+		flex: 1;
+		height: 100rpx;
+		font-size: 30rpx;
+		border-bottom: 1px solid #eee;
+		display: flex;
+		flex-direction: row;
+		align-items: center;
+		justify-content: center;
+		position: relative;
+		background: #000 !important;
+	}
+	
+	.wyb-drop-down-header-item-label {
+		color: #fff !important;
+		display: flex;
+		flex-direction: row;
+		align-items: center;
+		justify-content: flex-end;
+	}
+	
+	.wyb-drop-down-header-item-icon {
+		color: #fff !important;
+		margin-left: 20rpx;
+		display: flex;
+		flex-direction: row;
+		align-items: center;
+		justify-content: flex-start;
+		transform-origin: 50% 40%;
+		transform: rotate(0);
+		transition: transform var(--duration);
+	}
+	
+	.wyb-drop-down-header-item-icon-active {
+		transform: rotate(180deg);
+		transition: transform var(--duration);
+	}
+	
+	.wyb-drop-down-content {
+		position: fixed;
+		top: var(--autoContentTop);
+		/* #ifndef H5 */
+		top: 100rpx;
+		/* #endif */
+		left: 0;
+		right: 0;
+		z-index: 499;
+		display: flex;
+		flex-direction: column;
+		align-items: flex-start;
+		background-color: #fff;
+		transform: translateY(-100%);
+		transition: transform var(--duration);
+	}
+	
+	.wyb-drop-down-content-active {
+		transform: translateY(0);
+		transition: transform var(--duration);
+	}
+	
+	.wyb-drop-down-content-item {
+		background: #000;
+		color: #fff;
+		width: 100%;
+		height: 100rpx;
+		font-size: 30rpx;
+		display: flex;
+		flex-direction: column;
+		position: relative;
+	}
+	
+	.wyb-drop-down-content-item-label {
+		width: 90%;
+		height: 100%;
+		display: flex;
+		flex-direction: row;
+		align-items: center;
+		justify-content: flex-start;
+		padding-left: 50rpx;
+	}
+	
+	.wyb-drop-down-content-item-icon {
+		position: absolute;
+		top: 50%;
+		right: 40rpx;
+		font-size: 40rpx;
+		transform: translateY(-50%);
+	}
+	
+	.wyb-drop-down-content-box {
+		padding-top: var(--status-bar-height);
+		width: 100%;
+		background-color: #000;
+	}
+	
+	.wyb-drop-down-content-slot {
+		width: 100%;
+		height: 100%;
+	}
+	
+	.wyb-drop-down-vline {
+		width: 1px;
+		height: 40rpx;
+		background-color: #eee;
+		position: absolute;
+		right: 0;
+	}
+
+	.wyb-drop-down-line {
+		width: 100%;
+		height: 1px;
+		background-color: #eee;
+		margin-left: 50rpx;
+	}
+</style>

+ 18 - 0
config/app.js

@@ -0,0 +1,18 @@
+module.exports = {
+	// 请求域名 格式: https://您的域名
+	
+	HTTP_REQUEST_URL:'http://base.liuniu946.com',
+
+	
+	
+	// #ifdef H5
+	// HTTP_REQUEST_URL: window.location.protocol+"//"+window.location.host,
+	// #endif
+	HEADER:{
+		'content-type': 'application/json'
+	},
+	// 回话密钥名称 请勿修改此配置
+	TOKENNAME: 'Authori-zation',
+	// 缓存时间 0 永久
+	EXPIRE:0,
+};

+ 32 - 0
config/cache.js

@@ -0,0 +1,32 @@
+module.exports = {
+	//token
+	LOGIN_STATUS: 'LOGIN_STATUS_TOKEN',
+	// uid
+	UID:'UID',
+	//�û�
+	USER_INFO: 'USER_INFO',
+	//token�����¼�
+	EXPIRES_TIME: 'EXPIRES_TIME',
+	//�Ƿ���Ȩ
+	WX_AUTH: 'WX_AUTH',
+	//���ں���Ȩcode
+	STATE_KEY: 'wx_authorize_state',
+	//�û�����
+	LOGINTYPE: 'loginType',
+	//���ں���ת����
+	BACK_URL: 'login_back_url',
+	// ����code
+	STATE_R_KEY: 'roution_authorize_state',
+	//��ȨlogoС����
+	LOGO_URL: 'LOGO_URL',
+	//模板缓存
+	SUBSCRIBE_MESSAGE: 'SUBSCRIBE_MESSAGE',
+
+	TIPS_KEY: 'TIPS_KEY',
+
+	SPREAD: 'spread',
+	//缓存经度
+	CACHE_LONGITUDE: 'LONGITUDE',
+	//缓存纬度
+	CACHE_LATITUDE: 'LATITUDE',
+}

+ 28 - 0
index.html

@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html lang="zh-CN">
+    <head>
+        <meta charset="utf-8">
+        <meta http-equiv="X-UA-Compatible" content="IE=edge">
+		<link rel="shortcut icon" type="image/x-icon" href="./static/img/Nlog.png">
+        <title>
+            <%= htmlWebpackPlugin.options.title %>
+        </title>
+        <!-- Open Graph data -->
+        <!-- <meta property="og:title" content="Title Here" /> -->
+        <!-- <meta property="og:url" content="http://www.example.com/" /> -->
+        <!-- <meta property="og:image" content="http://example.com/image.jpg" /> -->
+        <!-- <meta property="og:description" content="Description Here" /> -->
+        <script>
+            var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') || CSS.supports('top: constant(a)'))
+            document.write('<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' + (coverSupport ? ', viewport-fit=cover' : '') + '" />')
+        </script>
+        <link rel="stylesheet" href="<%= BASE_URL %>static/index.<%= VUE_APP_INDEX_CSS_HASH %>.css" />
+    </head>
+    <body>
+        <noscript>
+            <strong>Please enable JavaScript to continue.</strong>
+        </noscript>
+        <div id="app"></div>
+        <!-- built files will be auto injected -->
+    </body>
+</html>

+ 31 - 0
lang/en.js

@@ -0,0 +1,31 @@
+module.exports = {
+	index: {
+		gm: 'Game',
+		qwfxl: 'Net Circulation',
+		yxh: 'Destroyed',
+		ltsz: 'Circulation Market(U)',
+		ldxwk: 'Flow Mining',
+		st: 'Ecology',
+		hzhb: 'Partner',
+		sq: 'Community'
+	},
+	main: {
+		dbzyl:'One Pledge Num',
+		sbzyl:'Two Pledge Num',
+		dbzy: 'One Pledge',
+		sbzy: 'Two Pledge',
+		shNLP: 'Gain NLP',
+		nhsy: 'Annualized',
+		nhfl: 'Annual Interest',
+		syed: 'Remaining Amount',
+		qwzy: 'So the pledge',
+		ylNLP: 'Received NLP',
+		wdzy: 'My pledge(NLP)',
+		klNLP: 'Brought NLP',
+		lq: 'Get',
+		js: 'Unlock',
+		tjldc:'Add a flow pool',
+		tc: 'Logout',
+		ljqb: 'Login'
+	}
+}

+ 19 - 0
lang/i18n.js

@@ -0,0 +1,19 @@
+import Vue from "vue";
+import VueI18n from "./i18nJs.js";
+import en from "./en";
+import zh_cn from './zh_cn'
+
+Vue.use(VueI18n);
+
+const messages = {
+  en: en,
+  zh_cn:zh_cn
+};
+
+const i18n = new VueI18n({
+  locale: "zh_cn",
+  messages
+});
+
+export default i18n;
+

+ 2210 - 0
lang/i18nJs.js

@@ -0,0 +1,2210 @@
+/*!
+ * vue-i18n v8.24.1 
+ * (c) 2021 kazuya kawaguchi
+ * Released under the MIT License.
+ */
+(function (global, factory) {
+  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
+  typeof define === 'function' && define.amd ? define(factory) :
+  (global.VueI18n = factory());
+}(this, (function () { 'use strict';
+
+  /*  */
+
+  /**
+   * constants
+   */
+
+  var numberFormatKeys = [
+    'compactDisplay',
+    'currency',
+    'currencyDisplay',
+    'currencySign',
+    'localeMatcher',
+    'notation',
+    'numberingSystem',
+    'signDisplay',
+    'style',
+    'unit',
+    'unitDisplay',
+    'useGrouping',
+    'minimumIntegerDigits',
+    'minimumFractionDigits',
+    'maximumFractionDigits',
+    'minimumSignificantDigits',
+    'maximumSignificantDigits'
+  ];
+
+  /**
+   * utilities
+   */
+
+  function warn (msg, err) {
+    if (typeof console !== 'undefined') {
+      console.warn('[vue-i18n] ' + msg);
+      /* istanbul ignore if */
+      if (err) {
+        console.warn(err.stack);
+      }
+    }
+  }
+
+  function error (msg, err) {
+    if (typeof console !== 'undefined') {
+      console.error('[vue-i18n] ' + msg);
+      /* istanbul ignore if */
+      if (err) {
+        console.error(err.stack);
+      }
+    }
+  }
+
+  var isArray = Array.isArray;
+
+  function isObject (obj) {
+    return obj !== null && typeof obj === 'object'
+  }
+
+  function isBoolean (val) {
+    return typeof val === 'boolean'
+  }
+
+  function isString (val) {
+    return typeof val === 'string'
+  }
+
+  var toString = Object.prototype.toString;
+  var OBJECT_STRING = '[object Object]';
+  function isPlainObject (obj) {
+    return toString.call(obj) === OBJECT_STRING
+  }
+
+  function isNull (val) {
+    return val === null || val === undefined
+  }
+
+  function isFunction (val) {
+    return typeof val === 'function'
+  }
+
+  function parseArgs () {
+    var args = [], len = arguments.length;
+    while ( len-- ) args[ len ] = arguments[ len ];
+
+    var locale = null;
+    var params = null;
+    if (args.length === 1) {
+      if (isObject(args[0]) || isArray(args[0])) {
+        params = args[0];
+      } else if (typeof args[0] === 'string') {
+        locale = args[0];
+      }
+    } else if (args.length === 2) {
+      if (typeof args[0] === 'string') {
+        locale = args[0];
+      }
+      /* istanbul ignore if */
+      if (isObject(args[1]) || isArray(args[1])) {
+        params = args[1];
+      }
+    }
+
+    return { locale: locale, params: params }
+  }
+
+  function looseClone (obj) {
+    return JSON.parse(JSON.stringify(obj))
+  }
+
+  function remove (arr, item) {
+    if (arr.length) {
+      var index = arr.indexOf(item);
+      if (index > -1) {
+        return arr.splice(index, 1)
+      }
+    }
+  }
+
+  function includes (arr, item) {
+    return !!~arr.indexOf(item)
+  }
+
+  var hasOwnProperty = Object.prototype.hasOwnProperty;
+  function hasOwn (obj, key) {
+    return hasOwnProperty.call(obj, key)
+  }
+
+  function merge (target) {
+    var arguments$1 = arguments;
+
+    var output = Object(target);
+    for (var i = 1; i < arguments.length; i++) {
+      var source = arguments$1[i];
+      if (source !== undefined && source !== null) {
+        var key = (void 0);
+        for (key in source) {
+          if (hasOwn(source, key)) {
+            if (isObject(source[key])) {
+              output[key] = merge(output[key], source[key]);
+            } else {
+              output[key] = source[key];
+            }
+          }
+        }
+      }
+    }
+    return output
+  }
+
+  function looseEqual (a, b) {
+    if (a === b) { return true }
+    var isObjectA = isObject(a);
+    var isObjectB = isObject(b);
+    if (isObjectA && isObjectB) {
+      try {
+        var isArrayA = isArray(a);
+        var isArrayB = isArray(b);
+        if (isArrayA && isArrayB) {
+          return a.length === b.length && a.every(function (e, i) {
+            return looseEqual(e, b[i])
+          })
+        } else if (!isArrayA && !isArrayB) {
+          var keysA = Object.keys(a);
+          var keysB = Object.keys(b);
+          return keysA.length === keysB.length && keysA.every(function (key) {
+            return looseEqual(a[key], b[key])
+          })
+        } else {
+          /* istanbul ignore next */
+          return false
+        }
+      } catch (e) {
+        /* istanbul ignore next */
+        return false
+      }
+    } else if (!isObjectA && !isObjectB) {
+      return String(a) === String(b)
+    } else {
+      return false
+    }
+  }
+
+  /**
+   * Sanitizes html special characters from input strings. For mitigating risk of XSS attacks.
+   * @param rawText The raw input from the user that should be escaped.
+   */
+  function escapeHtml(rawText) {
+    return rawText
+      .replace(/</g, '&lt;')
+      .replace(/>/g, '&gt;')
+      .replace(/"/g, '&quot;')
+      .replace(/'/g, '&apos;')
+  }
+
+  /**
+   * Escapes html tags and special symbols from all provided params which were returned from parseArgs().params.
+   * This method performs an in-place operation on the params object.
+   *
+   * @param {any} params Parameters as provided from `parseArgs().params`.
+   *                     May be either an array of strings or a string->any map.
+   *
+   * @returns The manipulated `params` object.
+   */
+  function escapeParams(params) {
+    if(params != null) {
+      Object.keys(params).forEach(function (key) {
+        if(typeof(params[key]) == 'string') {
+          params[key] = escapeHtml(params[key]);
+        }
+      });
+    }
+    return params
+  }
+
+  /*  */
+
+  function extend (Vue) {
+    if (!Vue.prototype.hasOwnProperty('$i18n')) {
+      // $FlowFixMe
+      Object.defineProperty(Vue.prototype, '$i18n', {
+        get: function get () { return this._i18n }
+      });
+    }
+
+    Vue.prototype.$t = function (key) {
+      var values = [], len = arguments.length - 1;
+      while ( len-- > 0 ) values[ len ] = arguments[ len + 1 ];
+
+      var i18n = this.$i18n;
+      return i18n._t.apply(i18n, [ key, i18n.locale, i18n._getMessages(), this ].concat( values ))
+    };
+
+    Vue.prototype.$tc = function (key, choice) {
+      var values = [], len = arguments.length - 2;
+      while ( len-- > 0 ) values[ len ] = arguments[ len + 2 ];
+
+      var i18n = this.$i18n;
+      return i18n._tc.apply(i18n, [ key, i18n.locale, i18n._getMessages(), this, choice ].concat( values ))
+    };
+
+    Vue.prototype.$te = function (key, locale) {
+      var i18n = this.$i18n;
+      return i18n._te(key, i18n.locale, i18n._getMessages(), locale)
+    };
+
+    Vue.prototype.$d = function (value) {
+      var ref;
+
+      var args = [], len = arguments.length - 1;
+      while ( len-- > 0 ) args[ len ] = arguments[ len + 1 ];
+      return (ref = this.$i18n).d.apply(ref, [ value ].concat( args ))
+    };
+
+    Vue.prototype.$n = function (value) {
+      var ref;
+
+      var args = [], len = arguments.length - 1;
+      while ( len-- > 0 ) args[ len ] = arguments[ len + 1 ];
+      return (ref = this.$i18n).n.apply(ref, [ value ].concat( args ))
+    };
+  }
+
+  /*  */
+
+  var mixin = {
+    beforeCreate: function beforeCreate () {
+      var options = this.$options;
+      options.i18n = options.i18n || (options.__i18n ? {} : null);
+
+      if (options.i18n) {
+        if (options.i18n instanceof VueI18n) {
+          // init locale messages via custom blocks
+          if (options.__i18n) {
+            try {
+              var localeMessages = options.i18n && options.i18n.messages ? options.i18n.messages : {};
+              options.__i18n.forEach(function (resource) {
+                localeMessages = merge(localeMessages, JSON.parse(resource));
+              });
+              Object.keys(localeMessages).forEach(function (locale) {
+                options.i18n.mergeLocaleMessage(locale, localeMessages[locale]);
+              });
+            } catch (e) {
+              {
+                error("Cannot parse locale messages via custom blocks.", e);
+              }
+            }
+          }
+          this._i18n = options.i18n;
+          this._i18nWatcher = this._i18n.watchI18nData();
+        } else if (isPlainObject(options.i18n)) {
+          var rootI18n = this.$root && this.$root.$i18n && this.$root.$i18n instanceof VueI18n
+            ? this.$root.$i18n
+            : null;
+          // component local i18n
+          if (rootI18n) {
+            options.i18n.root = this.$root;
+            options.i18n.formatter = rootI18n.formatter;
+            options.i18n.fallbackLocale = rootI18n.fallbackLocale;
+            options.i18n.formatFallbackMessages = rootI18n.formatFallbackMessages;
+            options.i18n.silentTranslationWarn = rootI18n.silentTranslationWarn;
+            options.i18n.silentFallbackWarn = rootI18n.silentFallbackWarn;
+            options.i18n.pluralizationRules = rootI18n.pluralizationRules;
+            options.i18n.preserveDirectiveContent = rootI18n.preserveDirectiveContent;
+          }
+
+          // init locale messages via custom blocks
+          if (options.__i18n) {
+            try {
+              var localeMessages$1 = options.i18n && options.i18n.messages ? options.i18n.messages : {};
+              options.__i18n.forEach(function (resource) {
+                localeMessages$1 = merge(localeMessages$1, JSON.parse(resource));
+              });
+              options.i18n.messages = localeMessages$1;
+            } catch (e) {
+              {
+                warn("Cannot parse locale messages via custom blocks.", e);
+              }
+            }
+          }
+
+          var ref = options.i18n;
+          var sharedMessages = ref.sharedMessages;
+          if (sharedMessages && isPlainObject(sharedMessages)) {
+            options.i18n.messages = merge(options.i18n.messages, sharedMessages);
+          }
+
+          this._i18n = new VueI18n(options.i18n);
+          this._i18nWatcher = this._i18n.watchI18nData();
+
+          if (options.i18n.sync === undefined || !!options.i18n.sync) {
+            this._localeWatcher = this.$i18n.watchLocale();
+          }
+
+          if (rootI18n) {
+            rootI18n.onComponentInstanceCreated(this._i18n);
+          }
+        } else {
+          {
+            warn("Cannot be interpreted 'i18n' option.");
+          }
+        }
+      } else if (this.$root && this.$root.$i18n && this.$root.$i18n instanceof VueI18n) {
+        // root i18n
+        this._i18n = this.$root.$i18n;
+      } else if (options.parent && options.parent.$i18n && options.parent.$i18n instanceof VueI18n) {
+        // parent i18n
+        this._i18n = options.parent.$i18n;
+      }
+    },
+
+    beforeMount: function beforeMount () {
+      var options = this.$options;
+      options.i18n = options.i18n || (options.__i18n ? {} : null);
+
+      if (options.i18n) {
+        if (options.i18n instanceof VueI18n) {
+          // init locale messages via custom blocks
+          this._i18n.subscribeDataChanging(this);
+          this._subscribing = true;
+        } else if (isPlainObject(options.i18n)) {
+          this._i18n.subscribeDataChanging(this);
+          this._subscribing = true;
+        } else {
+          {
+            warn("Cannot be interpreted 'i18n' option.");
+          }
+        }
+      } else if (this.$root && this.$root.$i18n && this.$root.$i18n instanceof VueI18n) {
+        this._i18n.subscribeDataChanging(this);
+        this._subscribing = true;
+      } else if (options.parent && options.parent.$i18n && options.parent.$i18n instanceof VueI18n) {
+        this._i18n.subscribeDataChanging(this);
+        this._subscribing = true;
+      }
+    },
+
+    mounted: function mounted () {
+      if (this !== this.$root && this.$options.__INTLIFY_META__ && this.$el) {
+        this.$el.setAttribute('data-intlify', this.$options.__INTLIFY_META__);
+      }
+    },
+
+    beforeDestroy: function beforeDestroy () {
+      if (!this._i18n) { return }
+
+      var self = this;
+      this.$nextTick(function () {
+        if (self._subscribing) {
+          self._i18n.unsubscribeDataChanging(self);
+          delete self._subscribing;
+        }
+
+        if (self._i18nWatcher) {
+          self._i18nWatcher();
+          self._i18n.destroyVM();
+          delete self._i18nWatcher;
+        }
+
+        if (self._localeWatcher) {
+          self._localeWatcher();
+          delete self._localeWatcher;
+        }
+      });
+    }
+  };
+
+  /*  */
+
+  var interpolationComponent = {
+    name: 'i18n',
+    functional: true,
+    props: {
+      tag: {
+        type: [String, Boolean, Object],
+        default: 'span'
+      },
+      path: {
+        type: String,
+        required: true
+      },
+      locale: {
+        type: String
+      },
+      places: {
+        type: [Array, Object]
+      }
+    },
+    render: function render (h, ref) {
+      var data = ref.data;
+      var parent = ref.parent;
+      var props = ref.props;
+      var slots = ref.slots;
+
+      var $i18n = parent.$i18n;
+      if (!$i18n) {
+        {
+          warn('Cannot find VueI18n instance!');
+        }
+        return
+      }
+
+      var path = props.path;
+      var locale = props.locale;
+      var places = props.places;
+      var params = slots();
+      var children = $i18n.i(
+        path,
+        locale,
+        onlyHasDefaultPlace(params) || places
+          ? useLegacyPlaces(params.default, places)
+          : params
+      );
+
+      var tag = (!!props.tag && props.tag !== true) || props.tag === false ? props.tag : 'span';
+      return tag ? h(tag, data, children) : children
+    }
+  };
+
+  function onlyHasDefaultPlace (params) {
+    var prop;
+    for (prop in params) {
+      if (prop !== 'default') { return false }
+    }
+    return Boolean(prop)
+  }
+
+  function useLegacyPlaces (children, places) {
+    var params = places ? createParamsFromPlaces(places) : {};
+
+    if (!children) { return params }
+
+    // Filter empty text nodes
+    children = children.filter(function (child) {
+      return child.tag || child.text.trim() !== ''
+    });
+
+    var everyPlace = children.every(vnodeHasPlaceAttribute);
+    if (everyPlace) {
+      warn('`place` attribute is deprecated in next major version. Please switch to Vue slots.');
+    }
+
+    return children.reduce(
+      everyPlace ? assignChildPlace : assignChildIndex,
+      params
+    )
+  }
+
+  function createParamsFromPlaces (places) {
+    {
+      warn('`places` prop is deprecated in next major version. Please switch to Vue slots.');
+    }
+
+    return Array.isArray(places)
+      ? places.reduce(assignChildIndex, {})
+      : Object.assign({}, places)
+  }
+
+  function assignChildPlace (params, child) {
+    if (child.data && child.data.attrs && child.data.attrs.place) {
+      params[child.data.attrs.place] = child;
+    }
+    return params
+  }
+
+  function assignChildIndex (params, child, index) {
+    params[index] = child;
+    return params
+  }
+
+  function vnodeHasPlaceAttribute (vnode) {
+    return Boolean(vnode.data && vnode.data.attrs && vnode.data.attrs.place)
+  }
+
+  /*  */
+
+  var numberComponent = {
+    name: 'i18n-n',
+    functional: true,
+    props: {
+      tag: {
+        type: [String, Boolean, Object],
+        default: 'span'
+      },
+      value: {
+        type: Number,
+        required: true
+      },
+      format: {
+        type: [String, Object]
+      },
+      locale: {
+        type: String
+      }
+    },
+    render: function render (h, ref) {
+      var props = ref.props;
+      var parent = ref.parent;
+      var data = ref.data;
+
+      var i18n = parent.$i18n;
+
+      if (!i18n) {
+        {
+          warn('Cannot find VueI18n instance!');
+        }
+        return null
+      }
+
+      var key = null;
+      var options = null;
+
+      if (isString(props.format)) {
+        key = props.format;
+      } else if (isObject(props.format)) {
+        if (props.format.key) {
+          key = props.format.key;
+        }
+
+        // Filter out number format options only
+        options = Object.keys(props.format).reduce(function (acc, prop) {
+          var obj;
+
+          if (includes(numberFormatKeys, prop)) {
+            return Object.assign({}, acc, ( obj = {}, obj[prop] = props.format[prop], obj ))
+          }
+          return acc
+        }, null);
+      }
+
+      var locale = props.locale || i18n.locale;
+      var parts = i18n._ntp(props.value, locale, key, options);
+
+      var values = parts.map(function (part, index) {
+        var obj;
+
+        var slot = data.scopedSlots && data.scopedSlots[part.type];
+        return slot ? slot(( obj = {}, obj[part.type] = part.value, obj.index = index, obj.parts = parts, obj )) : part.value
+      });
+
+      var tag = (!!props.tag && props.tag !== true) || props.tag === false ? props.tag : 'span';
+      return tag
+        ? h(tag, {
+          attrs: data.attrs,
+          'class': data['class'],
+          staticClass: data.staticClass
+        }, values)
+        : values
+    }
+  };
+
+  /*  */
+
+  function bind (el, binding, vnode) {
+    if (!assert(el, vnode)) { return }
+
+    t(el, binding, vnode);
+  }
+
+  function update (el, binding, vnode, oldVNode) {
+    if (!assert(el, vnode)) { return }
+
+    var i18n = vnode.context.$i18n;
+    if (localeEqual(el, vnode) &&
+      (looseEqual(binding.value, binding.oldValue) &&
+       looseEqual(el._localeMessage, i18n.getLocaleMessage(i18n.locale)))) { return }
+
+    t(el, binding, vnode);
+  }
+
+  function unbind (el, binding, vnode, oldVNode) {
+    var vm = vnode.context;
+    if (!vm) {
+      warn('Vue instance does not exists in VNode context');
+      return
+    }
+
+    var i18n = vnode.context.$i18n || {};
+    if (!binding.modifiers.preserve && !i18n.preserveDirectiveContent) {
+      el.textContent = '';
+    }
+    el._vt = undefined;
+    delete el['_vt'];
+    el._locale = undefined;
+    delete el['_locale'];
+    el._localeMessage = undefined;
+    delete el['_localeMessage'];
+  }
+
+  function assert (el, vnode) {
+    var vm = vnode.context;
+    if (!vm) {
+      warn('Vue instance does not exists in VNode context');
+      return false
+    }
+
+    if (!vm.$i18n) {
+      warn('VueI18n instance does not exists in Vue instance');
+      return false
+    }
+
+    return true
+  }
+
+  function localeEqual (el, vnode) {
+    var vm = vnode.context;
+    return el._locale === vm.$i18n.locale
+  }
+
+  function t (el, binding, vnode) {
+    var ref$1, ref$2;
+
+    var value = binding.value;
+
+    var ref = parseValue(value);
+    var path = ref.path;
+    var locale = ref.locale;
+    var args = ref.args;
+    var choice = ref.choice;
+    if (!path && !locale && !args) {
+      warn('value type not supported');
+      return
+    }
+
+    if (!path) {
+      warn('`path` is required in v-t directive');
+      return
+    }
+
+    var vm = vnode.context;
+    if (choice != null) {
+      el._vt = el.textContent = (ref$1 = vm.$i18n).tc.apply(ref$1, [ path, choice ].concat( makeParams(locale, args) ));
+    } else {
+      el._vt = el.textContent = (ref$2 = vm.$i18n).t.apply(ref$2, [ path ].concat( makeParams(locale, args) ));
+    }
+    el._locale = vm.$i18n.locale;
+    el._localeMessage = vm.$i18n.getLocaleMessage(vm.$i18n.locale);
+  }
+
+  function parseValue (value) {
+    var path;
+    var locale;
+    var args;
+    var choice;
+
+    if (isString(value)) {
+      path = value;
+    } else if (isPlainObject(value)) {
+      path = value.path;
+      locale = value.locale;
+      args = value.args;
+      choice = value.choice;
+    }
+
+    return { path: path, locale: locale, args: args, choice: choice }
+  }
+
+  function makeParams (locale, args) {
+    var params = [];
+
+    locale && params.push(locale);
+    if (args && (Array.isArray(args) || isPlainObject(args))) {
+      params.push(args);
+    }
+
+    return params
+  }
+
+  var Vue;
+
+  function install (_Vue) {
+    /* istanbul ignore if */
+    if (install.installed && _Vue === Vue) {
+      warn('already installed.');
+      return
+    }
+    install.installed = true;
+
+    Vue = _Vue;
+
+    var version = (Vue.version && Number(Vue.version.split('.')[0])) || -1;
+    /* istanbul ignore if */
+    if (version < 2) {
+      warn(("vue-i18n (" + (install.version) + ") need to use Vue 2.0 or later (Vue: " + (Vue.version) + ")."));
+      return
+    }
+
+    extend(Vue);
+    Vue.mixin(mixin);
+    Vue.directive('t', { bind: bind, update: update, unbind: unbind });
+    Vue.component(interpolationComponent.name, interpolationComponent);
+    Vue.component(numberComponent.name, numberComponent);
+
+    // use simple mergeStrategies to prevent i18n instance lose '__proto__'
+    var strats = Vue.config.optionMergeStrategies;
+    strats.i18n = function (parentVal, childVal) {
+      return childVal === undefined
+        ? parentVal
+        : childVal
+    };
+  }
+
+  /*  */
+
+  var BaseFormatter = function BaseFormatter () {
+    this._caches = Object.create(null);
+  };
+
+  BaseFormatter.prototype.interpolate = function interpolate (message, values) {
+    if (!values) {
+      return [message]
+    }
+    var tokens = this._caches[message];
+    if (!tokens) {
+      tokens = parse(message);
+      this._caches[message] = tokens;
+    }
+    return compile(tokens, values)
+  };
+
+
+
+  var RE_TOKEN_LIST_VALUE = /^(?:\d)+/;
+  var RE_TOKEN_NAMED_VALUE = /^(?:\w)+/;
+
+  function parse (format) {
+    var tokens = [];
+    var position = 0;
+
+    var text = '';
+    while (position < format.length) {
+      var char = format[position++];
+      if (char === '{') {
+        if (text) {
+          tokens.push({ type: 'text', value: text });
+        }
+
+        text = '';
+        var sub = '';
+        char = format[position++];
+        while (char !== undefined && char !== '}') {
+          sub += char;
+          char = format[position++];
+        }
+        var isClosed = char === '}';
+
+        var type = RE_TOKEN_LIST_VALUE.test(sub)
+          ? 'list'
+          : isClosed && RE_TOKEN_NAMED_VALUE.test(sub)
+            ? 'named'
+            : 'unknown';
+        tokens.push({ value: sub, type: type });
+      } else if (char === '%') {
+        // when found rails i18n syntax, skip text capture
+        if (format[(position)] !== '{') {
+          text += char;
+        }
+      } else {
+        text += char;
+      }
+    }
+
+    text && tokens.push({ type: 'text', value: text });
+
+    return tokens
+  }
+
+  function compile (tokens, values) {
+    var compiled = [];
+    var index = 0;
+
+    var mode = Array.isArray(values)
+      ? 'list'
+      : isObject(values)
+        ? 'named'
+        : 'unknown';
+    if (mode === 'unknown') { return compiled }
+
+    while (index < tokens.length) {
+      var token = tokens[index];
+      switch (token.type) {
+        case 'text':
+          compiled.push(token.value);
+          break
+        case 'list':
+          compiled.push(values[parseInt(token.value, 10)]);
+          break
+        case 'named':
+          if (mode === 'named') {
+            compiled.push((values)[token.value]);
+          } else {
+            {
+              warn(("Type of token '" + (token.type) + "' and format of value '" + mode + "' don't match!"));
+            }
+          }
+          break
+        case 'unknown':
+          {
+            warn("Detect 'unknown' type of token!");
+          }
+          break
+      }
+      index++;
+    }
+
+    return compiled
+  }
+
+  /*  */
+
+  /**
+   *  Path parser
+   *  - Inspired:
+   *    Vue.js Path parser
+   */
+
+  // actions
+  var APPEND = 0;
+  var PUSH = 1;
+  var INC_SUB_PATH_DEPTH = 2;
+  var PUSH_SUB_PATH = 3;
+
+  // states
+  var BEFORE_PATH = 0;
+  var IN_PATH = 1;
+  var BEFORE_IDENT = 2;
+  var IN_IDENT = 3;
+  var IN_SUB_PATH = 4;
+  var IN_SINGLE_QUOTE = 5;
+  var IN_DOUBLE_QUOTE = 6;
+  var AFTER_PATH = 7;
+  var ERROR = 8;
+
+  var pathStateMachine = [];
+
+  pathStateMachine[BEFORE_PATH] = {
+    'ws': [BEFORE_PATH],
+    'ident': [IN_IDENT, APPEND],
+    '[': [IN_SUB_PATH],
+    'eof': [AFTER_PATH]
+  };
+
+  pathStateMachine[IN_PATH] = {
+    'ws': [IN_PATH],
+    '.': [BEFORE_IDENT],
+    '[': [IN_SUB_PATH],
+    'eof': [AFTER_PATH]
+  };
+
+  pathStateMachine[BEFORE_IDENT] = {
+    'ws': [BEFORE_IDENT],
+    'ident': [IN_IDENT, APPEND],
+    '0': [IN_IDENT, APPEND],
+    'number': [IN_IDENT, APPEND]
+  };
+
+  pathStateMachine[IN_IDENT] = {
+    'ident': [IN_IDENT, APPEND],
+    '0': [IN_IDENT, APPEND],
+    'number': [IN_IDENT, APPEND],
+    'ws': [IN_PATH, PUSH],
+    '.': [BEFORE_IDENT, PUSH],
+    '[': [IN_SUB_PATH, PUSH],
+    'eof': [AFTER_PATH, PUSH]
+  };
+
+  pathStateMachine[IN_SUB_PATH] = {
+    "'": [IN_SINGLE_QUOTE, APPEND],
+    '"': [IN_DOUBLE_QUOTE, APPEND],
+    '[': [IN_SUB_PATH, INC_SUB_PATH_DEPTH],
+    ']': [IN_PATH, PUSH_SUB_PATH],
+    'eof': ERROR,
+    'else': [IN_SUB_PATH, APPEND]
+  };
+
+  pathStateMachine[IN_SINGLE_QUOTE] = {
+    "'": [IN_SUB_PATH, APPEND],
+    'eof': ERROR,
+    'else': [IN_SINGLE_QUOTE, APPEND]
+  };
+
+  pathStateMachine[IN_DOUBLE_QUOTE] = {
+    '"': [IN_SUB_PATH, APPEND],
+    'eof': ERROR,
+    'else': [IN_DOUBLE_QUOTE, APPEND]
+  };
+
+  /**
+   * Check if an expression is a literal value.
+   */
+
+  var literalValueRE = /^\s?(?:true|false|-?[\d.]+|'[^']*'|"[^"]*")\s?$/;
+  function isLiteral (exp) {
+    return literalValueRE.test(exp)
+  }
+
+  /**
+   * Strip quotes from a string
+   */
+
+  function stripQuotes (str) {
+    var a = str.charCodeAt(0);
+    var b = str.charCodeAt(str.length - 1);
+    return a === b && (a === 0x22 || a === 0x27)
+      ? str.slice(1, -1)
+      : str
+  }
+
+  /**
+   * Determine the type of a character in a keypath.
+   */
+
+  function getPathCharType (ch) {
+    if (ch === undefined || ch === null) { return 'eof' }
+
+    var code = ch.charCodeAt(0);
+
+    switch (code) {
+      case 0x5B: // [
+      case 0x5D: // ]
+      case 0x2E: // .
+      case 0x22: // "
+      case 0x27: // '
+        return ch
+
+      case 0x5F: // _
+      case 0x24: // $
+      case 0x2D: // -
+        return 'ident'
+
+      case 0x09: // Tab
+      case 0x0A: // Newline
+      case 0x0D: // Return
+      case 0xA0:  // No-break space
+      case 0xFEFF:  // Byte Order Mark
+      case 0x2028:  // Line Separator
+      case 0x2029:  // Paragraph Separator
+        return 'ws'
+    }
+
+    return 'ident'
+  }
+
+  /**
+   * Format a subPath, return its plain form if it is
+   * a literal string or number. Otherwise prepend the
+   * dynamic indicator (*).
+   */
+
+  function formatSubPath (path) {
+    var trimmed = path.trim();
+    // invalid leading 0
+    if (path.charAt(0) === '0' && isNaN(path)) { return false }
+
+    return isLiteral(trimmed) ? stripQuotes(trimmed) : '*' + trimmed
+  }
+
+  /**
+   * Parse a string path into an array of segments
+   */
+
+  function parse$1 (path) {
+    var keys = [];
+    var index = -1;
+    var mode = BEFORE_PATH;
+    var subPathDepth = 0;
+    var c;
+    var key;
+    var newChar;
+    var type;
+    var transition;
+    var action;
+    var typeMap;
+    var actions = [];
+
+    actions[PUSH] = function () {
+      if (key !== undefined) {
+        keys.push(key);
+        key = undefined;
+      }
+    };
+
+    actions[APPEND] = function () {
+      if (key === undefined) {
+        key = newChar;
+      } else {
+        key += newChar;
+      }
+    };
+
+    actions[INC_SUB_PATH_DEPTH] = function () {
+      actions[APPEND]();
+      subPathDepth++;
+    };
+
+    actions[PUSH_SUB_PATH] = function () {
+      if (subPathDepth > 0) {
+        subPathDepth--;
+        mode = IN_SUB_PATH;
+        actions[APPEND]();
+      } else {
+        subPathDepth = 0;
+        if (key === undefined) { return false }
+        key = formatSubPath(key);
+        if (key === false) {
+          return false
+        } else {
+          actions[PUSH]();
+        }
+      }
+    };
+
+    function maybeUnescapeQuote () {
+      var nextChar = path[index + 1];
+      if ((mode === IN_SINGLE_QUOTE && nextChar === "'") ||
+        (mode === IN_DOUBLE_QUOTE && nextChar === '"')) {
+        index++;
+        newChar = '\\' + nextChar;
+        actions[APPEND]();
+        return true
+      }
+    }
+
+    while (mode !== null) {
+      index++;
+      c = path[index];
+
+      if (c === '\\' && maybeUnescapeQuote()) {
+        continue
+      }
+
+      type = getPathCharType(c);
+      typeMap = pathStateMachine[mode];
+      transition = typeMap[type] || typeMap['else'] || ERROR;
+
+      if (transition === ERROR) {
+        return // parse error
+      }
+
+      mode = transition[0];
+      action = actions[transition[1]];
+      if (action) {
+        newChar = transition[2];
+        newChar = newChar === undefined
+          ? c
+          : newChar;
+        if (action() === false) {
+          return
+        }
+      }
+
+      if (mode === AFTER_PATH) {
+        return keys
+      }
+    }
+  }
+
+
+
+
+
+  var I18nPath = function I18nPath () {
+    this._cache = Object.create(null);
+  };
+
+  /**
+   * External parse that check for a cache hit first
+   */
+  I18nPath.prototype.parsePath = function parsePath (path) {
+    var hit = this._cache[path];
+    if (!hit) {
+      hit = parse$1(path);
+      if (hit) {
+        this._cache[path] = hit;
+      }
+    }
+    return hit || []
+  };
+
+  /**
+   * Get path value from path string
+   */
+  I18nPath.prototype.getPathValue = function getPathValue (obj, path) {
+    if (!isObject(obj)) { return null }
+
+    var paths = this.parsePath(path);
+    if (paths.length === 0) {
+      return null
+    } else {
+      var length = paths.length;
+      var last = obj;
+      var i = 0;
+      while (i < length) {
+        var value = last[paths[i]];
+        if (value === undefined || value === null) {
+          return null
+        }
+        last = value;
+        i++;
+      }
+
+      return last
+    }
+  };
+
+  /*  */
+
+
+
+  var htmlTagMatcher = /<\/?[\w\s="/.':;#-\/]+>/;
+  var linkKeyMatcher = /(?:@(?:\.[a-z]+)?:(?:[\w\-_|.]+|\([\w\-_|.]+\)))/g;
+  var linkKeyPrefixMatcher = /^@(?:\.([a-z]+))?:/;
+  var bracketsMatcher = /[()]/g;
+  var defaultModifiers = {
+    'upper': function (str) { return str.toLocaleUpperCase(); },
+    'lower': function (str) { return str.toLocaleLowerCase(); },
+    'capitalize': function (str) { return ("" + (str.charAt(0).toLocaleUpperCase()) + (str.substr(1))); }
+  };
+
+  var defaultFormatter = new BaseFormatter();
+
+  var VueI18n = function VueI18n (options) {
+    var this$1 = this;
+    if ( options === void 0 ) options = {};
+
+    // Auto install if it is not done yet and `window` has `Vue`.
+    // To allow users to avoid auto-installation in some cases,
+    // this code should be placed here. See #290
+    /* istanbul ignore if */
+    if (!Vue && typeof window !== 'undefined' && window.Vue) {
+      install(window.Vue);
+    }
+
+    var locale = options.locale || 'en-US';
+    var fallbackLocale = options.fallbackLocale === false
+      ? false
+      : options.fallbackLocale || 'en-US';
+    var messages = options.messages || {};
+    var dateTimeFormats = options.dateTimeFormats || {};
+    var numberFormats = options.numberFormats || {};
+
+    this._vm = null;
+    this._formatter = options.formatter || defaultFormatter;
+    this._modifiers = options.modifiers || {};
+    this._missing = options.missing || null;
+    this._root = options.root || null;
+    this._sync = options.sync === undefined ? true : !!options.sync;
+    this._fallbackRoot = options.fallbackRoot === undefined
+      ? true
+      : !!options.fallbackRoot;
+    this._formatFallbackMessages = options.formatFallbackMessages === undefined
+      ? false
+      : !!options.formatFallbackMessages;
+    this._silentTranslationWarn = options.silentTranslationWarn === undefined
+      ? false
+      : options.silentTranslationWarn;
+    this._silentFallbackWarn = options.silentFallbackWarn === undefined
+      ? false
+      : !!options.silentFallbackWarn;
+    this._dateTimeFormatters = {};
+    this._numberFormatters = {};
+    this._path = new I18nPath();
+    this._dataListeners = [];
+    this._componentInstanceCreatedListener = options.componentInstanceCreatedListener || null;
+    this._preserveDirectiveContent = options.preserveDirectiveContent === undefined
+      ? false
+      : !!options.preserveDirectiveContent;
+    this.pluralizationRules = options.pluralizationRules || {};
+    this._warnHtmlInMessage = options.warnHtmlInMessage || 'off';
+    this._postTranslation = options.postTranslation || null;
+    this._escapeParameterHtml = options.escapeParameterHtml || false;
+
+    /**
+     * @param choice {number} a choice index given by the input to $tc: `$tc('path.to.rule', choiceIndex)`
+     * @param choicesLength {number} an overall amount of available choices
+     * @returns a final choice index
+    */
+    this.getChoiceIndex = function (choice, choicesLength) {
+      var thisPrototype = Object.getPrototypeOf(this$1);
+      if (thisPrototype && thisPrototype.getChoiceIndex) {
+        var prototypeGetChoiceIndex = (thisPrototype.getChoiceIndex);
+        return (prototypeGetChoiceIndex).call(this$1, choice, choicesLength)
+      }
+
+      // Default (old) getChoiceIndex implementation - english-compatible
+      var defaultImpl = function (_choice, _choicesLength) {
+        _choice = Math.abs(_choice);
+
+        if (_choicesLength === 2) {
+          return _choice
+            ? _choice > 1
+              ? 1
+              : 0
+            : 1
+        }
+
+        return _choice ? Math.min(_choice, 2) : 0
+      };
+
+      if (this$1.locale in this$1.pluralizationRules) {
+        return this$1.pluralizationRules[this$1.locale].apply(this$1, [choice, choicesLength])
+      } else {
+        return defaultImpl(choice, choicesLength)
+      }
+    };
+
+
+    this._exist = function (message, key) {
+      if (!message || !key) { return false }
+      if (!isNull(this$1._path.getPathValue(message, key))) { return true }
+      // fallback for flat key
+      if (message[key]) { return true }
+      return false
+    };
+
+    if (this._warnHtmlInMessage === 'warn' || this._warnHtmlInMessage === 'error') {
+      Object.keys(messages).forEach(function (locale) {
+        this$1._checkLocaleMessage(locale, this$1._warnHtmlInMessage, messages[locale]);
+      });
+    }
+
+    this._initVM({
+      locale: locale,
+      fallbackLocale: fallbackLocale,
+      messages: messages,
+      dateTimeFormats: dateTimeFormats,
+      numberFormats: numberFormats
+    });
+  };
+
+  var prototypeAccessors = { vm: { configurable: true },messages: { configurable: true },dateTimeFormats: { configurable: true },numberFormats: { configurable: true },availableLocales: { configurable: true },locale: { configurable: true },fallbackLocale: { configurable: true },formatFallbackMessages: { configurable: true },missing: { configurable: true },formatter: { configurable: true },silentTranslationWarn: { configurable: true },silentFallbackWarn: { configurable: true },preserveDirectiveContent: { configurable: true },warnHtmlInMessage: { configurable: true },postTranslation: { configurable: true } };
+
+  VueI18n.prototype._checkLocaleMessage = function _checkLocaleMessage (locale, level, message) {
+    var paths = [];
+
+    var fn = function (level, locale, message, paths) {
+      if (isPlainObject(message)) {
+        Object.keys(message).forEach(function (key) {
+          var val = message[key];
+          if (isPlainObject(val)) {
+            paths.push(key);
+            paths.push('.');
+            fn(level, locale, val, paths);
+            paths.pop();
+            paths.pop();
+          } else {
+            paths.push(key);
+            fn(level, locale, val, paths);
+            paths.pop();
+          }
+        });
+      } else if (isArray(message)) {
+        message.forEach(function (item, index) {
+          if (isPlainObject(item)) {
+            paths.push(("[" + index + "]"));
+            paths.push('.');
+            fn(level, locale, item, paths);
+            paths.pop();
+            paths.pop();
+          } else {
+            paths.push(("[" + index + "]"));
+            fn(level, locale, item, paths);
+            paths.pop();
+          }
+        });
+      } else if (isString(message)) {
+        var ret = htmlTagMatcher.test(message);
+        if (ret) {
+          var msg = "Detected HTML in message '" + message + "' of keypath '" + (paths.join('')) + "' at '" + locale + "'. Consider component interpolation with '<i18n>' to avoid XSS. See https://bit.ly/2ZqJzkp";
+          if (level === 'warn') {
+            warn(msg);
+          } else if (level === 'error') {
+            error(msg);
+          }
+        }
+      }
+    };
+
+    fn(level, locale, message, paths);
+  };
+
+  VueI18n.prototype._initVM = function _initVM (data) {
+    var silent = Vue.config.silent;
+    Vue.config.silent = true;
+    this._vm = new Vue({ data: data });
+    Vue.config.silent = silent;
+  };
+
+  VueI18n.prototype.destroyVM = function destroyVM () {
+    this._vm.$destroy();
+  };
+
+  VueI18n.prototype.subscribeDataChanging = function subscribeDataChanging (vm) {
+    this._dataListeners.push(vm);
+  };
+
+  VueI18n.prototype.unsubscribeDataChanging = function unsubscribeDataChanging (vm) {
+    remove(this._dataListeners, vm);
+  };
+
+  VueI18n.prototype.watchI18nData = function watchI18nData () {
+    var self = this;
+    return this._vm.$watch('$data', function () {
+      var i = self._dataListeners.length;
+      while (i--) {
+        Vue.nextTick(function () {
+          self._dataListeners[i] && self._dataListeners[i].$forceUpdate();
+        });
+      }
+    }, { deep: true })
+  };
+
+  VueI18n.prototype.watchLocale = function watchLocale () {
+    /* istanbul ignore if */
+    if (!this._sync || !this._root) { return null }
+    var target = this._vm;
+    return this._root.$i18n.vm.$watch('locale', function (val) {
+      target.$set(target, 'locale', val);
+      target.$forceUpdate();
+    }, { immediate: true })
+  };
+
+  VueI18n.prototype.onComponentInstanceCreated = function onComponentInstanceCreated (newI18n) {
+    if (this._componentInstanceCreatedListener) {
+      this._componentInstanceCreatedListener(newI18n, this);
+    }
+  };
+
+  prototypeAccessors.vm.get = function () { return this._vm };
+
+  prototypeAccessors.messages.get = function () { return looseClone(this._getMessages()) };
+  prototypeAccessors.dateTimeFormats.get = function () { return looseClone(this._getDateTimeFormats()) };
+  prototypeAccessors.numberFormats.get = function () { return looseClone(this._getNumberFormats()) };
+  prototypeAccessors.availableLocales.get = function () { return Object.keys(this.messages).sort() };
+
+  prototypeAccessors.locale.get = function () { return this._vm.locale };
+  prototypeAccessors.locale.set = function (locale) {
+    this._vm.$set(this._vm, 'locale', locale);
+  };
+
+  prototypeAccessors.fallbackLocale.get = function () { return this._vm.fallbackLocale };
+  prototypeAccessors.fallbackLocale.set = function (locale) {
+    this._localeChainCache = {};
+    this._vm.$set(this._vm, 'fallbackLocale', locale);
+  };
+
+  prototypeAccessors.formatFallbackMessages.get = function () { return this._formatFallbackMessages };
+  prototypeAccessors.formatFallbackMessages.set = function (fallback) { this._formatFallbackMessages = fallback; };
+
+  prototypeAccessors.missing.get = function () { return this._missing };
+  prototypeAccessors.missing.set = function (handler) { this._missing = handler; };
+
+  prototypeAccessors.formatter.get = function () { return this._formatter };
+  prototypeAccessors.formatter.set = function (formatter) { this._formatter = formatter; };
+
+  prototypeAccessors.silentTranslationWarn.get = function () { return this._silentTranslationWarn };
+  prototypeAccessors.silentTranslationWarn.set = function (silent) { this._silentTranslationWarn = silent; };
+
+  prototypeAccessors.silentFallbackWarn.get = function () { return this._silentFallbackWarn };
+  prototypeAccessors.silentFallbackWarn.set = function (silent) { this._silentFallbackWarn = silent; };
+
+  prototypeAccessors.preserveDirectiveContent.get = function () { return this._preserveDirectiveContent };
+  prototypeAccessors.preserveDirectiveContent.set = function (preserve) { this._preserveDirectiveContent = preserve; };
+
+  prototypeAccessors.warnHtmlInMessage.get = function () { return this._warnHtmlInMessage };
+  prototypeAccessors.warnHtmlInMessage.set = function (level) {
+      var this$1 = this;
+
+    var orgLevel = this._warnHtmlInMessage;
+    this._warnHtmlInMessage = level;
+    if (orgLevel !== level && (level === 'warn' || level === 'error')) {
+      var messages = this._getMessages();
+      Object.keys(messages).forEach(function (locale) {
+        this$1._checkLocaleMessage(locale, this$1._warnHtmlInMessage, messages[locale]);
+      });
+    }
+  };
+
+  prototypeAccessors.postTranslation.get = function () { return this._postTranslation };
+  prototypeAccessors.postTranslation.set = function (handler) { this._postTranslation = handler; };
+
+  VueI18n.prototype._getMessages = function _getMessages () { return this._vm.messages };
+  VueI18n.prototype._getDateTimeFormats = function _getDateTimeFormats () { return this._vm.dateTimeFormats };
+  VueI18n.prototype._getNumberFormats = function _getNumberFormats () { return this._vm.numberFormats };
+
+  VueI18n.prototype._warnDefault = function _warnDefault (locale, key, result, vm, values, interpolateMode) {
+    if (!isNull(result)) { return result }
+    if (this._missing) {
+      var missingRet = this._missing.apply(null, [locale, key, vm, values]);
+      if (isString(missingRet)) {
+        return missingRet
+      }
+    } else {
+      if (!this._isSilentTranslationWarn(key)) {
+        warn(
+          "Cannot translate the value of keypath '" + key + "'. " +
+          'Use the value of keypath as default.'
+        );
+      }
+    }
+
+    if (this._formatFallbackMessages) {
+      var parsedArgs = parseArgs.apply(void 0, values);
+      return this._render(key, interpolateMode, parsedArgs.params, key)
+    } else {
+      return key
+    }
+  };
+
+  VueI18n.prototype._isFallbackRoot = function _isFallbackRoot (val) {
+    return !val && !isNull(this._root) && this._fallbackRoot
+  };
+
+  VueI18n.prototype._isSilentFallbackWarn = function _isSilentFallbackWarn (key) {
+    return this._silentFallbackWarn instanceof RegExp
+      ? this._silentFallbackWarn.test(key)
+      : this._silentFallbackWarn
+  };
+
+  VueI18n.prototype._isSilentFallback = function _isSilentFallback (locale, key) {
+    return this._isSilentFallbackWarn(key) && (this._isFallbackRoot() || locale !== this.fallbackLocale)
+  };
+
+  VueI18n.prototype._isSilentTranslationWarn = function _isSilentTranslationWarn (key) {
+    return this._silentTranslationWarn instanceof RegExp
+      ? this._silentTranslationWarn.test(key)
+      : this._silentTranslationWarn
+  };
+
+  VueI18n.prototype._interpolate = function _interpolate (
+    locale,
+    message,
+    key,
+    host,
+    interpolateMode,
+    values,
+    visitedLinkStack
+  ) {
+    if (!message) { return null }
+
+    var pathRet = this._path.getPathValue(message, key);
+    if (isArray(pathRet) || isPlainObject(pathRet)) { return pathRet }
+
+    var ret;
+    if (isNull(pathRet)) {
+      /* istanbul ignore else */
+      if (isPlainObject(message)) {
+        ret = message[key];
+        if (!(isString(ret) || isFunction(ret))) {
+          if (!this._isSilentTranslationWarn(key) && !this._isSilentFallback(locale, key)) {
+            warn(("Value of key '" + key + "' is not a string or function !"));
+          }
+          return null
+        }
+      } else {
+        return null
+      }
+    } else {
+      /* istanbul ignore else */
+      if (isString(pathRet) || isFunction(pathRet)) {
+        ret = pathRet;
+      } else {
+        if (!this._isSilentTranslationWarn(key) && !this._isSilentFallback(locale, key)) {
+          warn(("Value of key '" + key + "' is not a string or function!"));
+        }
+        return null
+      }
+    }
+
+    // Check for the existence of links within the translated string
+    if (isString(ret) && (ret.indexOf('@:') >= 0 || ret.indexOf('@.') >= 0)) {
+      ret = this._link(locale, message, ret, host, 'raw', values, visitedLinkStack);
+    }
+
+    return this._render(ret, interpolateMode, values, key)
+  };
+
+  VueI18n.prototype._link = function _link (
+    locale,
+    message,
+    str,
+    host,
+    interpolateMode,
+    values,
+    visitedLinkStack
+  ) {
+    var ret = str;
+
+    // Match all the links within the local
+    // We are going to replace each of
+    // them with its translation
+    var matches = ret.match(linkKeyMatcher);
+    for (var idx in matches) {
+      // ie compatible: filter custom array
+      // prototype method
+      if (!matches.hasOwnProperty(idx)) {
+        continue
+      }
+      var link = matches[idx];
+      var linkKeyPrefixMatches = link.match(linkKeyPrefixMatcher);
+      var linkPrefix = linkKeyPrefixMatches[0];
+        var formatterName = linkKeyPrefixMatches[1];
+
+      // Remove the leading @:, @.case: and the brackets
+      var linkPlaceholder = link.replace(linkPrefix, '').replace(bracketsMatcher, '');
+
+      if (includes(visitedLinkStack, linkPlaceholder)) {
+        {
+          warn(("Circular reference found. \"" + link + "\" is already visited in the chain of " + (visitedLinkStack.reverse().join(' <- '))));
+        }
+        return ret
+      }
+      visitedLinkStack.push(linkPlaceholder);
+
+      // Translate the link
+      var translated = this._interpolate(
+        locale, message, linkPlaceholder, host,
+        interpolateMode === 'raw' ? 'string' : interpolateMode,
+        interpolateMode === 'raw' ? undefined : values,
+        visitedLinkStack
+      );
+
+      if (this._isFallbackRoot(translated)) {
+        if (!this._isSilentTranslationWarn(linkPlaceholder)) {
+          warn(("Fall back to translate the link placeholder '" + linkPlaceholder + "' with root locale."));
+        }
+        /* istanbul ignore if */
+        if (!this._root) { throw Error('unexpected error') }
+        var root = this._root.$i18n;
+        translated = root._translate(
+          root._getMessages(), root.locale, root.fallbackLocale,
+          linkPlaceholder, host, interpolateMode, values
+        );
+      }
+      translated = this._warnDefault(
+        locale, linkPlaceholder, translated, host,
+        isArray(values) ? values : [values],
+        interpolateMode
+      );
+
+      if (this._modifiers.hasOwnProperty(formatterName)) {
+        translated = this._modifiers[formatterName](translated);
+      } else if (defaultModifiers.hasOwnProperty(formatterName)) {
+        translated = defaultModifiers[formatterName](translated);
+      }
+
+      visitedLinkStack.pop();
+
+      // Replace the link with the translated
+      ret = !translated ? ret : ret.replace(link, translated);
+    }
+
+    return ret
+  };
+
+  VueI18n.prototype._createMessageContext = function _createMessageContext (values) {
+    var _list = isArray(values) ? values : [];
+    var _named = isObject(values) ? values : {};
+    var list = function (index) { return _list[index]; };
+    var named = function (key) { return _named[key]; };
+    return {
+      list: list,
+      named: named
+    }
+  };
+
+  VueI18n.prototype._render = function _render (message, interpolateMode, values, path) {
+    if (isFunction(message)) {
+      return message(this._createMessageContext(values))
+    }
+
+    var ret = this._formatter.interpolate(message, values, path);
+
+    // If the custom formatter refuses to work - apply the default one
+    if (!ret) {
+      ret = defaultFormatter.interpolate(message, values, path);
+    }
+
+    // if interpolateMode is **not** 'string' ('row'),
+    // return the compiled data (e.g. ['foo', VNode, 'bar']) with formatter
+    return interpolateMode === 'string' && !isString(ret) ? ret.join('') : ret
+  };
+
+  VueI18n.prototype._appendItemToChain = function _appendItemToChain (chain, item, blocks) {
+    var follow = false;
+    if (!includes(chain, item)) {
+      follow = true;
+      if (item) {
+        follow = item[item.length - 1] !== '!';
+        item = item.replace(/!/g, '');
+        chain.push(item);
+        if (blocks && blocks[item]) {
+          follow = blocks[item];
+        }
+      }
+    }
+    return follow
+  };
+
+  VueI18n.prototype._appendLocaleToChain = function _appendLocaleToChain (chain, locale, blocks) {
+    var follow;
+    var tokens = locale.split('-');
+    do {
+      var item = tokens.join('-');
+      follow = this._appendItemToChain(chain, item, blocks);
+      tokens.splice(-1, 1);
+    } while (tokens.length && (follow === true))
+    return follow
+  };
+
+  VueI18n.prototype._appendBlockToChain = function _appendBlockToChain (chain, block, blocks) {
+    var follow = true;
+    for (var i = 0; (i < block.length) && (isBoolean(follow)); i++) {
+      var locale = block[i];
+      if (isString(locale)) {
+        follow = this._appendLocaleToChain(chain, locale, blocks);
+      }
+    }
+    return follow
+  };
+
+  VueI18n.prototype._getLocaleChain = function _getLocaleChain (start, fallbackLocale) {
+    if (start === '') { return [] }
+
+    if (!this._localeChainCache) {
+      this._localeChainCache = {};
+    }
+
+    var chain = this._localeChainCache[start];
+    if (!chain) {
+      if (!fallbackLocale) {
+        fallbackLocale = this.fallbackLocale;
+      }
+      chain = [];
+
+      // first block defined by start
+      var block = [start];
+
+      // while any intervening block found
+      while (isArray(block)) {
+        block = this._appendBlockToChain(
+          chain,
+          block,
+          fallbackLocale
+        );
+      }
+
+      // last block defined by default
+      var defaults;
+      if (isArray(fallbackLocale)) {
+        defaults = fallbackLocale;
+      } else if (isObject(fallbackLocale)) {
+        /* $FlowFixMe */
+        if (fallbackLocale['default']) {
+          defaults = fallbackLocale['default'];
+        } else {
+          defaults = null;
+        }
+      } else {
+        defaults = fallbackLocale;
+      }
+
+      // convert defaults to array
+      if (isString(defaults)) {
+        block = [defaults];
+      } else {
+        block = defaults;
+      }
+      if (block) {
+        this._appendBlockToChain(
+          chain,
+          block,
+          null
+        );
+      }
+      this._localeChainCache[start] = chain;
+    }
+    return chain
+  };
+
+  VueI18n.prototype._translate = function _translate (
+    messages,
+    locale,
+    fallback,
+    key,
+    host,
+    interpolateMode,
+    args
+  ) {
+    var chain = this._getLocaleChain(locale, fallback);
+    var res;
+    for (var i = 0; i < chain.length; i++) {
+      var step = chain[i];
+      res =
+        this._interpolate(step, messages[step], key, host, interpolateMode, args, [key]);
+      if (!isNull(res)) {
+        if (step !== locale && "development" !== 'production' && !this._isSilentTranslationWarn(key) && !this._isSilentFallbackWarn(key)) {
+          warn(("Fall back to translate the keypath '" + key + "' with '" + step + "' locale."));
+        }
+        return res
+      }
+    }
+    return null
+  };
+
+  VueI18n.prototype._t = function _t (key, _locale, messages, host) {
+      var ref;
+
+      var values = [], len = arguments.length - 4;
+      while ( len-- > 0 ) values[ len ] = arguments[ len + 4 ];
+    if (!key) { return '' }
+
+    var parsedArgs = parseArgs.apply(void 0, values);
+    if(this._escapeParameterHtml) {
+      parsedArgs.params = escapeParams(parsedArgs.params);
+    }
+
+    var locale = parsedArgs.locale || _locale;
+
+    var ret = this._translate(
+      messages, locale, this.fallbackLocale, key,
+      host, 'string', parsedArgs.params
+    );
+    if (this._isFallbackRoot(ret)) {
+      if (!this._isSilentTranslationWarn(key) && !this._isSilentFallbackWarn(key)) {
+        warn(("Fall back to translate the keypath '" + key + "' with root locale."));
+      }
+      /* istanbul ignore if */
+      if (!this._root) { throw Error('unexpected error') }
+      return (ref = this._root).$t.apply(ref, [ key ].concat( values ))
+    } else {
+      ret = this._warnDefault(locale, key, ret, host, values, 'string');
+      if (this._postTranslation && ret !== null && ret !== undefined) {
+        ret = this._postTranslation(ret, key);
+      }
+      return ret
+    }
+  };
+
+  VueI18n.prototype.t = function t (key) {
+      var ref;
+
+      var values = [], len = arguments.length - 1;
+      while ( len-- > 0 ) values[ len ] = arguments[ len + 1 ];
+    return (ref = this)._t.apply(ref, [ key, this.locale, this._getMessages(), null ].concat( values ))
+  };
+
+  VueI18n.prototype._i = function _i (key, locale, messages, host, values) {
+    var ret =
+      this._translate(messages, locale, this.fallbackLocale, key, host, 'raw', values);
+    if (this._isFallbackRoot(ret)) {
+      if (!this._isSilentTranslationWarn(key)) {
+        warn(("Fall back to interpolate the keypath '" + key + "' with root locale."));
+      }
+      if (!this._root) { throw Error('unexpected error') }
+      return this._root.$i18n.i(key, locale, values)
+    } else {
+      return this._warnDefault(locale, key, ret, host, [values], 'raw')
+    }
+  };
+
+  VueI18n.prototype.i = function i (key, locale, values) {
+    /* istanbul ignore if */
+    if (!key) { return '' }
+
+    if (!isString(locale)) {
+      locale = this.locale;
+    }
+
+    return this._i(key, locale, this._getMessages(), null, values)
+  };
+
+  VueI18n.prototype._tc = function _tc (
+    key,
+    _locale,
+    messages,
+    host,
+    choice
+  ) {
+      var ref;
+
+      var values = [], len = arguments.length - 5;
+      while ( len-- > 0 ) values[ len ] = arguments[ len + 5 ];
+    if (!key) { return '' }
+    if (choice === undefined) {
+      choice = 1;
+    }
+
+    var predefined = { 'count': choice, 'n': choice };
+    var parsedArgs = parseArgs.apply(void 0, values);
+    parsedArgs.params = Object.assign(predefined, parsedArgs.params);
+    values = parsedArgs.locale === null ? [parsedArgs.params] : [parsedArgs.locale, parsedArgs.params];
+    return this.fetchChoice((ref = this)._t.apply(ref, [ key, _locale, messages, host ].concat( values )), choice)
+  };
+
+  VueI18n.prototype.fetchChoice = function fetchChoice (message, choice) {
+    /* istanbul ignore if */
+    if (!message || !isString(message)) { return null }
+    var choices = message.split('|');
+
+    choice = this.getChoiceIndex(choice, choices.length);
+    if (!choices[choice]) { return message }
+    return choices[choice].trim()
+  };
+
+  VueI18n.prototype.tc = function tc (key, choice) {
+      var ref;
+
+      var values = [], len = arguments.length - 2;
+      while ( len-- > 0 ) values[ len ] = arguments[ len + 2 ];
+    return (ref = this)._tc.apply(ref, [ key, this.locale, this._getMessages(), null, choice ].concat( values ))
+  };
+
+  VueI18n.prototype._te = function _te (key, locale, messages) {
+      var args = [], len = arguments.length - 3;
+      while ( len-- > 0 ) args[ len ] = arguments[ len + 3 ];
+
+    var _locale = parseArgs.apply(void 0, args).locale || locale;
+    return this._exist(messages[_locale], key)
+  };
+
+  VueI18n.prototype.te = function te (key, locale) {
+    return this._te(key, this.locale, this._getMessages(), locale)
+  };
+
+  VueI18n.prototype.getLocaleMessage = function getLocaleMessage (locale) {
+    return looseClone(this._vm.messages[locale] || {})
+  };
+
+  VueI18n.prototype.setLocaleMessage = function setLocaleMessage (locale, message) {
+    if (this._warnHtmlInMessage === 'warn' || this._warnHtmlInMessage === 'error') {
+      this._checkLocaleMessage(locale, this._warnHtmlInMessage, message);
+    }
+    this._vm.$set(this._vm.messages, locale, message);
+  };
+
+  VueI18n.prototype.mergeLocaleMessage = function mergeLocaleMessage (locale, message) {
+    if (this._warnHtmlInMessage === 'warn' || this._warnHtmlInMessage === 'error') {
+      this._checkLocaleMessage(locale, this._warnHtmlInMessage, message);
+    }
+    this._vm.$set(this._vm.messages, locale, merge(
+      typeof this._vm.messages[locale] !== 'undefined' && Object.keys(this._vm.messages[locale]).length
+        ? this._vm.messages[locale]
+        : {},
+      message
+    ));
+  };
+
+  VueI18n.prototype.getDateTimeFormat = function getDateTimeFormat (locale) {
+    return looseClone(this._vm.dateTimeFormats[locale] || {})
+  };
+
+  VueI18n.prototype.setDateTimeFormat = function setDateTimeFormat (locale, format) {
+    this._vm.$set(this._vm.dateTimeFormats, locale, format);
+    this._clearDateTimeFormat(locale, format);
+  };
+
+  VueI18n.prototype.mergeDateTimeFormat = function mergeDateTimeFormat (locale, format) {
+    this._vm.$set(this._vm.dateTimeFormats, locale, merge(this._vm.dateTimeFormats[locale] || {}, format));
+    this._clearDateTimeFormat(locale, format);
+  };
+
+  VueI18n.prototype._clearDateTimeFormat = function _clearDateTimeFormat (locale, format) {
+    for (var key in format) {
+      var id = locale + "__" + key;
+
+      if (!this._dateTimeFormatters.hasOwnProperty(id)) {
+        continue
+      }
+
+      delete this._dateTimeFormatters[id];
+    }
+  };
+
+  VueI18n.prototype._localizeDateTime = function _localizeDateTime (
+    value,
+    locale,
+    fallback,
+    dateTimeFormats,
+    key
+  ) {
+    var _locale = locale;
+    var formats = dateTimeFormats[_locale];
+
+    var chain = this._getLocaleChain(locale, fallback);
+    for (var i = 0; i < chain.length; i++) {
+      var current = _locale;
+      var step = chain[i];
+      formats = dateTimeFormats[step];
+      _locale = step;
+      // fallback locale
+      if (isNull(formats) || isNull(formats[key])) {
+        if (step !== locale && "development" !== 'production' && !this._isSilentTranslationWarn(key) && !this._isSilentFallbackWarn(key)) {
+          warn(("Fall back to '" + step + "' datetime formats from '" + current + "' datetime formats."));
+        }
+      } else {
+        break
+      }
+    }
+
+    if (isNull(formats) || isNull(formats[key])) {
+      return null
+    } else {
+      var format = formats[key];
+      var id = _locale + "__" + key;
+      var formatter = this._dateTimeFormatters[id];
+      if (!formatter) {
+        formatter = this._dateTimeFormatters[id] = new Intl.DateTimeFormat(_locale, format);
+      }
+      return formatter.format(value)
+    }
+  };
+
+  VueI18n.prototype._d = function _d (value, locale, key) {
+    /* istanbul ignore if */
+    if (!VueI18n.availabilities.dateTimeFormat) {
+      warn('Cannot format a Date value due to not supported Intl.DateTimeFormat.');
+      return ''
+    }
+
+    if (!key) {
+      return new Intl.DateTimeFormat(locale).format(value)
+    }
+
+    var ret =
+      this._localizeDateTime(value, locale, this.fallbackLocale, this._getDateTimeFormats(), key);
+    if (this._isFallbackRoot(ret)) {
+      if (!this._isSilentTranslationWarn(key) && !this._isSilentFallbackWarn(key)) {
+        warn(("Fall back to datetime localization of root: key '" + key + "'."));
+      }
+      /* istanbul ignore if */
+      if (!this._root) { throw Error('unexpected error') }
+      return this._root.$i18n.d(value, key, locale)
+    } else {
+      return ret || ''
+    }
+  };
+
+  VueI18n.prototype.d = function d (value) {
+      var args = [], len = arguments.length - 1;
+      while ( len-- > 0 ) args[ len ] = arguments[ len + 1 ];
+
+    var locale = this.locale;
+    var key = null;
+
+    if (args.length === 1) {
+      if (isString(args[0])) {
+        key = args[0];
+      } else if (isObject(args[0])) {
+        if (args[0].locale) {
+          locale = args[0].locale;
+        }
+        if (args[0].key) {
+          key = args[0].key;
+        }
+      }
+    } else if (args.length === 2) {
+      if (isString(args[0])) {
+        key = args[0];
+      }
+      if (isString(args[1])) {
+        locale = args[1];
+      }
+    }
+
+    return this._d(value, locale, key)
+  };
+
+  VueI18n.prototype.getNumberFormat = function getNumberFormat (locale) {
+    return looseClone(this._vm.numberFormats[locale] || {})
+  };
+
+  VueI18n.prototype.setNumberFormat = function setNumberFormat (locale, format) {
+    this._vm.$set(this._vm.numberFormats, locale, format);
+    this._clearNumberFormat(locale, format);
+  };
+
+  VueI18n.prototype.mergeNumberFormat = function mergeNumberFormat (locale, format) {
+    this._vm.$set(this._vm.numberFormats, locale, merge(this._vm.numberFormats[locale] || {}, format));
+    this._clearNumberFormat(locale, format);
+  };
+
+  VueI18n.prototype._clearNumberFormat = function _clearNumberFormat (locale, format) {
+    for (var key in format) {
+      var id = locale + "__" + key;
+
+      if (!this._numberFormatters.hasOwnProperty(id)) {
+        continue
+      }
+
+      delete this._numberFormatters[id];
+    }
+  };
+
+  VueI18n.prototype._getNumberFormatter = function _getNumberFormatter (
+    value,
+    locale,
+    fallback,
+    numberFormats,
+    key,
+    options
+  ) {
+    var _locale = locale;
+    var formats = numberFormats[_locale];
+
+    var chain = this._getLocaleChain(locale, fallback);
+    for (var i = 0; i < chain.length; i++) {
+      var current = _locale;
+      var step = chain[i];
+      formats = numberFormats[step];
+      _locale = step;
+      // fallback locale
+      if (isNull(formats) || isNull(formats[key])) {
+        if (step !== locale && "development" !== 'production' && !this._isSilentTranslationWarn(key) && !this._isSilentFallbackWarn(key)) {
+          warn(("Fall back to '" + step + "' number formats from '" + current + "' number formats."));
+        }
+      } else {
+        break
+      }
+    }
+
+    if (isNull(formats) || isNull(formats[key])) {
+      return null
+    } else {
+      var format = formats[key];
+
+      var formatter;
+      if (options) {
+        // If options specified - create one time number formatter
+        formatter = new Intl.NumberFormat(_locale, Object.assign({}, format, options));
+      } else {
+        var id = _locale + "__" + key;
+        formatter = this._numberFormatters[id];
+        if (!formatter) {
+          formatter = this._numberFormatters[id] = new Intl.NumberFormat(_locale, format);
+        }
+      }
+      return formatter
+    }
+  };
+
+  VueI18n.prototype._n = function _n (value, locale, key, options) {
+    /* istanbul ignore if */
+    if (!VueI18n.availabilities.numberFormat) {
+      {
+        warn('Cannot format a Number value due to not supported Intl.NumberFormat.');
+      }
+      return ''
+    }
+
+    if (!key) {
+      var nf = !options ? new Intl.NumberFormat(locale) : new Intl.NumberFormat(locale, options);
+      return nf.format(value)
+    }
+
+    var formatter = this._getNumberFormatter(value, locale, this.fallbackLocale, this._getNumberFormats(), key, options);
+    var ret = formatter && formatter.format(value);
+    if (this._isFallbackRoot(ret)) {
+      if (!this._isSilentTranslationWarn(key) && !this._isSilentFallbackWarn(key)) {
+        warn(("Fall back to number localization of root: key '" + key + "'."));
+      }
+      /* istanbul ignore if */
+      if (!this._root) { throw Error('unexpected error') }
+      return this._root.$i18n.n(value, Object.assign({}, { key: key, locale: locale }, options))
+    } else {
+      return ret || ''
+    }
+  };
+
+  VueI18n.prototype.n = function n (value) {
+      var args = [], len = arguments.length - 1;
+      while ( len-- > 0 ) args[ len ] = arguments[ len + 1 ];
+
+    var locale = this.locale;
+    var key = null;
+    var options = null;
+
+    if (args.length === 1) {
+      if (isString(args[0])) {
+        key = args[0];
+      } else if (isObject(args[0])) {
+        if (args[0].locale) {
+          locale = args[0].locale;
+        }
+        if (args[0].key) {
+          key = args[0].key;
+        }
+
+        // Filter out number format options only
+        options = Object.keys(args[0]).reduce(function (acc, key) {
+            var obj;
+
+          if (includes(numberFormatKeys, key)) {
+            return Object.assign({}, acc, ( obj = {}, obj[key] = args[0][key], obj ))
+          }
+          return acc
+        }, null);
+      }
+    } else if (args.length === 2) {
+      if (isString(args[0])) {
+        key = args[0];
+      }
+      if (isString(args[1])) {
+        locale = args[1];
+      }
+    }
+
+    return this._n(value, locale, key, options)
+  };
+
+  VueI18n.prototype._ntp = function _ntp (value, locale, key, options) {
+    /* istanbul ignore if */
+    if (!VueI18n.availabilities.numberFormat) {
+      {
+        warn('Cannot format to parts a Number value due to not supported Intl.NumberFormat.');
+      }
+      return []
+    }
+
+    if (!key) {
+      var nf = !options ? new Intl.NumberFormat(locale) : new Intl.NumberFormat(locale, options);
+      return nf.formatToParts(value)
+    }
+
+    var formatter = this._getNumberFormatter(value, locale, this.fallbackLocale, this._getNumberFormats(), key, options);
+    var ret = formatter && formatter.formatToParts(value);
+    if (this._isFallbackRoot(ret)) {
+      if (!this._isSilentTranslationWarn(key)) {
+        warn(("Fall back to format number to parts of root: key '" + key + "' ."));
+      }
+      /* istanbul ignore if */
+      if (!this._root) { throw Error('unexpected error') }
+      return this._root.$i18n._ntp(value, locale, key, options)
+    } else {
+      return ret || []
+    }
+  };
+
+  Object.defineProperties( VueI18n.prototype, prototypeAccessors );
+
+  var availabilities;
+  // $FlowFixMe
+  Object.defineProperty(VueI18n, 'availabilities', {
+    get: function get () {
+      if (!availabilities) {
+        var intlDefined = typeof Intl !== 'undefined';
+        availabilities = {
+          dateTimeFormat: intlDefined && typeof Intl.DateTimeFormat !== 'undefined',
+          numberFormat: intlDefined && typeof Intl.NumberFormat !== 'undefined'
+        };
+      }
+
+      return availabilities
+    }
+  });
+
+  VueI18n.install = install;
+  VueI18n.version = '8.24.1';
+
+  return VueI18n;
+
+})));

+ 31 - 0
lang/zh_cn.js

@@ -0,0 +1,31 @@
+module.exports =  {
+    index:{
+		gm:'游戏',
+		qwfxl:'全网发行量',
+		yxh:'已销毁',
+		ltsz:'流通市值(U)',
+		ldxwk:'流动性挖矿',
+		st:'生态',
+		hzhb:'合作伙伴',
+		sq:'社区'
+    },
+    main:{
+		dbzyl:'总单币质押量',
+		sbzyl:'总双币质押量',
+		dbzy:'单币质押',
+		sbzy:'双币质押',
+		shNLP:'收获NLP',
+		nhsy:'年化收益',
+		nhfl:'年化复利',
+		syed:'剩余可质押额度',
+		qwzy:'全网总质押',
+		ylNLP:'已领NLP',
+		wdzy:'我的质押(NLP)',
+		klNLP:'可领NLP',
+		lq:'领取',
+		js:'解锁',
+		tjldc:'添加流动池',
+		tc: '退出',
+		ljqb: '连接钱包'
+	}
+}

+ 39 - 0
libs/log.js

@@ -0,0 +1,39 @@
+
+const logLength=100;//缓存存储上限
+const name = 'log';//缓存名字
+export function addLog (data,content='') {
+	let log = uni.getStorageSync(name)||[];
+	log.unshift({
+		title:data,
+		content:content
+	});
+	uni.setStorageSync(name,log);
+	initLog(log);
+}
+
+
+export function delLog () {
+	return uni.setStorageSync(name,'');
+}
+
+export function getLog () {
+	return uni.getStorageSync(name);
+}
+
+export function initLog (log) {
+	if(log.length>logLength){
+		const newarr = log.slice(log.length-logLength);
+		uni.setStorageSync(name,newarr);
+	}
+}
+export function showLog (log) {
+	
+	let str = '';
+	uni.getStorageSync(name).forEach((e) => {
+		str+=e.title+':'+JSON.stringify(e.content)
+	})
+	uni.showModal({
+		title:"日志",
+		content:str
+	})
+}

+ 84 - 0
libs/login.js

@@ -0,0 +1,84 @@
+import store from "../store";
+import Cache from '../utils/cache';
+// #ifdef H5 || APP-PLUS
+import {
+	isWeixin
+} from "../utils";
+import auth from './wechat';
+// #endif
+
+import {
+	LOGIN_STATUS,
+	USER_INFO,
+	EXPIRES_TIME,
+	STATE_R_KEY
+} from './../config/cache';
+
+function prePage() {
+	let pages = getCurrentPages();
+	let prePage = pages[pages.length - 2];
+	// #ifdef H5
+	return prePage;
+	// #endif
+	return prePage.$vm;
+}
+
+export function toLogin(push, pathLogin) {
+	// store.commit("LOGOUT");
+	let path = prePage();
+	if (path) {
+		path = path.router;
+		if (path == undefined) {
+			path = location.pathname;
+		}
+	}
+	// #ifdef H5
+	else {
+		path = location.pathname;
+	}
+	// #endif
+
+	if (!pathLogin)
+		pathLogin = '/page/users/login/index'
+	Cache.set('login_back_url', path);
+	// #ifdef H5 || APP-PLUS
+	if (isWeixin()) {
+		auth.oAuth();
+	} else {
+		if (path !== pathLogin) {
+			push ? uni.navigateTo({
+				url: '/pages/users/login/index'
+			}) : uni.reLaunch({
+				url: '/pages/users/login/index'
+			});
+		}
+	}
+	// #endif
+
+	// #ifdef MP 
+
+
+	// #endif
+}
+
+
+export function checkLogin() {
+	let token = Cache.get(LOGIN_STATUS);
+	let expiresTime = Cache.get(EXPIRES_TIME);
+	let newTime = Math.round(new Date() / 1000);
+	if (expiresTime < newTime || !token) {
+		Cache.clear(LOGIN_STATUS);
+		Cache.clear(EXPIRES_TIME);
+		Cache.clear(USER_INFO);
+		Cache.clear(STATE_R_KEY);
+		return false;
+	} else {
+		store.commit('UPDATE_LOGIN', token);
+		let userInfo = Cache.get(USER_INFO, true);
+		if (userInfo) {
+			store.commit('UPDATE_USERINFO', userInfo);
+		}
+		return true;
+	}
+
+}

+ 253 - 0
libs/wechat.js

@@ -0,0 +1,253 @@
+// #ifdef H5
+import WechatJSSDK from "@/plugin/jweixin-module/index.js";
+// #endif
+
+import {
+	wechatConfig,
+	wechatAuth
+} from "@/api/wx.js";
+import {
+	WX_AUTH,
+	STATE_KEY,
+	LOGINTYPE,
+	BACK_URL
+} from '@/config/cache';
+import {
+	parseQuery
+} from '@/utils';
+import store from '@/store';
+import Cache from '@/utils/cache';
+
+class AuthWechat {
+	// #ifdef H5
+	constructor() {
+		//微信实例化对象
+		this.instance = WechatJSSDK;
+		//是否实例化
+		this.status = false;
+
+		this.initConfig = {};
+
+	}
+	// #endif
+	
+	isAndroid(){
+		let u = navigator.userAgent;
+		return u.indexOf('Android') > -1 || u.indexOf('Adr') > -1;
+	}
+
+	/**
+	 * 初始化wechat(分享配置)
+	 */
+	wechat() {
+		return new Promise((resolve, reject) => {
+			// if (this.status && !this.isAndroid()) return resolve(this.instance);
+			wechatConfig()
+				.then(res => {
+					this.instance.config(res.data);
+					this.initConfig = res.data;
+					this.status = true;
+					this.instance.ready(() => {
+						resolve(this.instance);
+					})
+				}).catch(err => {
+					console.log(err);
+					this.status = false;
+					reject(err);
+				});
+		});
+	}
+
+	/**
+	 * 验证是否初始化
+	 */
+	verifyInstance() {
+		let that = this;
+		return new Promise((resolve, reject) => {
+			if (that.instance === null && !that.status) {
+				that.wechat().then(res => {
+					resolve(that.instance);
+				}).catch(() => {
+					return reject();
+				})
+			} else {
+				return resolve(that.instance);
+			}
+		})
+	}
+	// 微信公众号的共享地址
+	openAddress() {
+		return new Promise((resolve, reject) => {
+			this.wechat().then(wx => {
+				this.toPromise(wx.openAddress).then(res => {
+					resolve(res);
+				}).catch(err => {
+					reject(err);
+				});
+			}).catch(err => {
+				reject(err);
+			})
+		});
+	}
+
+	/**
+	 * 微信支付
+	 * @param {Object} config
+	 */
+	pay(config) {
+		return new Promise((resolve, reject) => {
+			this.wechat().then((wx) => {
+				this.toPromise(wx.chooseWXPay, config).then(res => {
+					resolve(res);
+				}).catch(res => {
+					reject(res);
+				});
+			}).catch(res => {
+				reject(res);
+			});
+		});
+	}
+
+	toPromise(fn, config = {}) {
+		return new Promise((resolve, reject) => {
+			fn({
+				...config,
+				success(res) {
+					resolve(res);
+				},
+				fail(err) {
+					reject(err);
+				},
+				complete(err) {
+					reject(err);
+				},
+				cancel(err) {
+					reject(err);
+				}
+			});
+		});
+	}
+
+	/**
+	 * 自动去授权
+	 */
+	oAuth() {
+		if (uni.getStorageSync(WX_AUTH) && store.state.app.token) return;
+		const {
+			code
+		} = parseQuery();
+		if (!code) return this.toAuth();
+	}
+
+	clearAuthStatus() {
+
+	}
+
+	/**
+	 * 授权登陆获取token
+	 * @param {Object} code
+	 */
+	auth(code) {
+		return new Promise((resolve, reject) => {
+			let loginType = Cache.get(LOGINTYPE);
+			wechatAuth(code, parseInt(Cache.get("spread")), loginType)
+				.then(({
+					data
+				}) => {
+					let expires_time = data.expires_time.substring(0, 19);
+					expires_time = expires_time.replace(/-/g, '/');
+					expires_time = new Date(expires_time).getTime();
+					let newTime = Math.round(new Date() / 1000);
+					store.commit("LOGIN", {
+						token: data.token,
+						time: expires_time - newTime
+					});
+					Cache.set(WX_AUTH, code);
+					Cache.clear(STATE_KEY);
+					loginType && Cache.clear(LOGINTYPE);
+					resolve();
+				})
+				.catch(reject);
+		});
+	}
+
+	/**
+	 * 获取跳转授权后的地址
+	 * @param {Object} appId
+	 */
+	getAuthUrl(appId) {
+		const redirect_uri = encodeURIComponent(
+			`${location.origin}/pages/auth/index?back_url=` +
+			encodeURIComponent(
+				encodeURIComponent(
+					uni.getStorageSync(BACK_URL) ?
+					uni.getStorageSync(BACK_URL) :
+					location.pathname + location.search
+				)
+			)
+		);
+		uni.removeStorageSync(BACK_URL);
+		const state = encodeURIComponent(
+			("" + Math.random()).split(".")[1] + "authorizestate"
+		);
+		uni.setStorageSync(STATE_KEY, state);
+		return `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appId}&redirect_uri=${redirect_uri}&response_type=code&scope=snsapi_userinfo&state=${state}#wechat_redirect`;
+	}
+
+	/**
+	 * 跳转自动登陆
+	 */
+	toAuth() {
+		let that = this;
+		this.wechat().then(wx => {
+			location.href = this.getAuthUrl(that.initConfig.appId);
+		})
+	}
+
+	/**
+	 * 绑定事件
+	 * @param {Object} name 事件名
+	 * @param {Object} config 参数
+	 */
+	wechatEvevt(name, config) {
+		let that = this;
+		return new Promise((resolve, reject) => {
+			let configDefault = {
+				fail(res) {
+					console.log(res,11111);
+					if (that.instance) return reject({
+						is_ready: true,
+						wx: that.instance
+					});
+					that.verifyInstance().then(wx => {
+						return reject({
+							is_ready: true,
+							wx: wx
+						});
+					})
+				},
+				success(res) {
+					return resolve(res,2222);
+				}
+			};
+			Object.assign(configDefault, config);
+			that.wechat().then(wx => {
+				if (typeof name === 'object') {
+					name.forEach(item => {
+						wx[item] && wx[item](configDefault)
+					})
+				} else {
+					wx[name] && wx[name](configDefault)
+				}
+			})
+		});
+	}
+
+	isWeixin() {
+		return navigator.userAgent.toLowerCase().indexOf("micromessenger") !== -1;
+	}
+
+}
+
+export default new AuthWechat();
+

+ 49 - 0
main.js

@@ -0,0 +1,49 @@
+import Vue from 'vue'
+import store from './store'
+import App from './App'
+import uView from "uview-ui";
+import i18n from "./lang/i18n";
+
+Vue.use(uView);
+/**
+ *  所有测试用数据均存放于根目录json.js
+ *  
+ *  css部分使用了App.vue下的全局样式和iconfont图标,有需要图标库的可以留言。
+ *  示例使用了uni.scss下的变量, 除变量外已尽量移除特有语法,可直接替换为其他预处理器使用
+ */
+const msg = (title, duration=1500, mask=false, icon='none')=>{
+	//统一提示方便全局修改
+	if(Boolean(title) === false){
+		return;
+	}
+	uni.showToast({
+		title,
+		duration,
+		mask,
+		icon
+	});
+}
+
+const prePage = ()=>{
+	// 获取当前页面
+	let pages = getCurrentPages();
+	let prePage = pages[pages.length - 2];
+	// #ifdef H5
+	return prePage;
+	// #endif
+	return prePage.$vm;
+}
+
+
+Vue.config.productionTip = false
+Vue.prototype.$fire = new Vue();
+Vue.prototype.$store = store;
+Vue.prototype.$api = {msg, prePage};
+
+App.mpType = 'app'
+
+const app = new Vue({
+    ...App,
+	i18n,
+})
+app.$mount()

+ 129 - 0
manifest.json

@@ -0,0 +1,129 @@
+{
+    "name" : "流量王",
+    "appid" : "__UNI__F3A92A0",
+    "description" : "",
+    "versionName" : "1.0.0",
+    "versionCode" : 100,
+    "transformPx" : false,
+    "app-plus" : {
+        /* 5+App特有相关 */
+        "usingComponents" : true,
+        "splashscreen" : {
+            "alwaysShowBeforeRender" : true,
+            "waiting" : true,
+            "autoclose" : true,
+            "delay" : 0
+        },
+        "modules" : {
+            "Payment" : {}
+        },
+        /* 模块配置 */
+        "distribute" : {
+            /* 应用发布信息 */
+            "android" : {
+                /* android打包配置 */
+                "permissions" : [
+                    "<uses-permission android:name=\"android.permission.CALL_PHONE\"/>",
+                    "<uses-permission android:name=\"android.permission.MODIFY_AUDIO_SETTINGS\"/>",
+                    "<uses-permission android:name=\"android.permission.VIBRATE\"/>",
+                    "<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>"
+                ],
+                "abiFilters" : [ "armeabi-v7a", "arm64-v8a", "x86" ]
+            },
+            "ios" : {
+                "idfa" : false
+            },
+            /* ios打包配置 */
+            "sdkConfigs" : {
+                "maps" : {},
+                "oauth" : {},
+                "geolocation" : {},
+                "payment" : {
+                    "weixin" : {
+                        "__platform__" : [ "ios", "android" ],
+                        "appid" : "wxe7542891b1e27ad1",
+                        "UniversalLinks" : ""
+                    }
+                },
+                "push" : {},
+                "share" : {},
+                "speech" : {
+                    "ifly" : {}
+                },
+                "ad" : {}
+            },
+            "splashscreen" : {
+                "androidStyle" : "default",
+                "android" : {
+                    "hdpi" : "",
+                    "xhdpi" : "",
+                    "xxhdpi" : ""
+                }
+            },
+            "icons" : {
+                "android" : {
+                    "hdpi" : "",
+                    "xhdpi" : "",
+                    "xxhdpi" : "",
+                    "xxxhdpi" : ""
+                },
+                "ios" : {
+                    "appstore" : "",
+                    "ipad" : {
+                        "app" : "",
+                        "app@2x" : "",
+                        "notification" : "",
+                        "notification@2x" : "",
+                        "proapp@2x" : "",
+                        "settings" : "",
+                        "settings@2x" : "",
+                        "spotlight" : "",
+                        "spotlight@2x" : ""
+                    },
+                    "iphone" : {
+                        "app@2x" : "",
+                        "app@3x" : "",
+                        "notification@2x" : "",
+                        "notification@3x" : "",
+                        "settings@2x" : "",
+                        "settings@3x" : "",
+                        "spotlight@2x" : "",
+                        "spotlight@3x" : ""
+                    }
+                }
+            }
+        }
+    },
+    /* SDK配置 */
+    "quickapp" : {},
+    /* 快应用特有相关 */
+    "mp-weixin" : {
+        /* 小程序特有相关 */
+        "usingComponents" : true,
+        "appid" : "",
+        "setting" : {
+            "urlCheck" : true
+        }
+    },
+    "h5" : {
+        "title" : "流量王",
+        "domain" : "",
+        "router" : {
+            "base" : "/index",
+            "mode" : "hash"
+        },
+        "devServer" : {
+            "proxy" : {
+                "/api" : {
+                    "target" : "http://flow.frp.liuniu946.com/api",
+                    // "target" : "http://bowin.frp.liuniu946.com/api",
+                    // "changeOrigin": true,
+                    "pathRewrite" : {
+                        "/api" : "" // rewrite path
+                    }
+                }
+            }
+        },
+        "template" : ""
+    }
+}

+ 20 - 0
node_modules/.package-lock.json

@@ -0,0 +1,20 @@
+{
+  "name": "fox",
+  "lockfileVersion": 2,
+  "requires": true,
+  "packages": {
+    "node_modules/@metamask/onboarding": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/@metamask/onboarding/-/onboarding-1.0.1.tgz",
+      "integrity": "sha512-FqHhAsCI+Vacx2qa5mAFcWNSrTcVGMNjzxVgaX8ECSny/BJ9/vgXP9V7WF/8vb9DltPeQkxr+Fnfmm6GHfmdTQ==",
+      "dependencies": {
+        "bowser": "^2.9.0"
+      }
+    },
+    "node_modules/bowser": {
+      "version": "2.11.0",
+      "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz",
+      "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA=="
+    }
+  }
+}

+ 31 - 0
node_modules/@metamask/onboarding/CHANGELOG.md

@@ -0,0 +1,31 @@
+# Changelog
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## [Unreleased]
+
+## [1.0.1]
+### Changed
+- Update various dependencies ([#49](https://github.com/MetaMask/metamask-onboarding/pull/49), [#68](https://github.com/MetaMask/metamask-onboarding/pull/68), [#71](https://github.com/MetaMask/metamask-onboarding/pull/71))
+- Use `@lavamoat/allow-scripts` for improved security ([#67](https://github.com/MetaMask/metamask-onboarding/pull/67))
+- Remove unused dependencies ([#66](https://github.com/MetaMask/metamask-onboarding/pull/66))
+- Switch from CircleCI to GitHub Actions ([#64](https://github.com/MetaMask/metamask-onboarding/pull/64))
+- Update Node.js used for CI from v10 to v12 ([#59](https://github.com/MetaMask/metamask-onboarding/pull/59), [#73](https://github.com/MetaMask/metamask-onboarding/pull/73))
+- Refactor to improve readability ([#45](https://github.com/MetaMask/metamask-onboarding/pull/45), [#47](https://github.com/MetaMask/metamask-onboarding/pull/47), [#48](https://github.com/MetaMask/metamask-onboarding/pull/48))
+
+### Fixed
+- Fix import of `bowser` package ([#57](https://github.com/MetaMask/metamask-onboarding/pull/57), [#60](https://github.com/MetaMask/metamask-onboarding/pull/60), [#61](https://github.com/MetaMask/metamask-onboarding/pull/61), [#63](https://github.com/MetaMask/metamask-onboarding/pull/63))
+
+## [1.0.0] - 2020-07-02
+### Changed
+- **BREAKING**: Rename export to `MetaMaskOnboarding` (#32)
+- Update example in README with validated HTML (#30)
+
+### Fixed
+- Use Firefox URL without specified language (#38)
+
+[Unreleased]: https://github.com/MetaMask/metamask-onboarding/compare/v1.0.1...HEAD
+[1.0.1]: https://github.com/MetaMask/metamask-onboarding/compare/v1.0.0...v1.0.1
+[1.0.0]: https://github.com/MetaMask/metamask-onboarding/releases/tag/v1.0.0

+ 21 - 0
node_modules/@metamask/onboarding/LICENSE

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

+ 92 - 0
node_modules/@metamask/onboarding/README.md

@@ -0,0 +1,92 @@
+# MetaMask Onboarding
+
+This library is used to help onboard new MetaMask users. It allows you to ask the MetaMask extension to redirect users back to your page after onboarding has finished.
+
+This library will register the current page as having initiated onboarding, so that MetaMask knows where to redirect the user after onboarding. Note that the page will be automatically reloaded a single time once a MetaMask installation is detected, in order to facilitate this registration.
+
+## Installation
+
+`@metamask/onboarding` is made available as either a CommonJS module, and ES6 module, or an ES5 bundle.
+
+- ES6 module: `import MetaMaskOnboarding from '@metamask/onboarding'`
+- ES5 module: `const MetaMaskOnboarding = require('@metamask/onboarding')`
+- ES5 bundle: `dist/metamask-onboarding.bundle.js` (this can be included directly in a page)
+
+## Usage
+
+[See _§ Onboarding Library_ on the MetaMask Docs website for examples.](https://docs.metamask.io/guide/onboarding-library.html)
+
+## API
+
+Assuming `import MetaMaskOnboarding from '@metamask/onboarding'`, the following API is available.
+
+### Static methods
+
+#### `MetaMaskOnboarding.isMetaMaskInstalled()`
+
+Returns `true` if a MetaMask-like provider is detected, or `false` otherwise. Note that we don't provide any guarantee that this is correct, as non-MetaMask wallets can disguise themselves as MetaMask.
+
+### Static properties
+
+#### `MetaMaskOnboarding.FORWARDER_MODE`
+
+A set of constants for each of the available forwarder modes.
+
+| Constant   | Description                                                                                                                            |
+| :--------- | :------------------------------------------------------------------------------------------------------------------------------------- |
+| `INJECT`   | Inject a `iframe` to that will refresh until MetaMask has installed                                                                    |
+| `OPEN_TAB` | Open a tab to a new page that will refresh until MetaMask has installed—this is only useful if the client app has disallowed `iframes` |
+
+### Constructor
+
+#### `new MetaMaskOnboarding()`
+
+The constructor accepts an optional options bag with the following:
+
+| Option            | Description                                                                                                 |
+| :---------------- | :---------------------------------------------------------------------------------------------------------- |
+| `forwarderOrigin` | Override the forwarder URL, useful for testing. **Optional**, defaults to `'https://fwd.metamask.io'`.      |
+| `forwarderMode`   | One of the available forwarder modes. **Optional**, defaults to `MetaMaskOnboarding.FORWARDER_MODE.INJECT`. |
+
+### Instance methods
+
+#### `startOnboarding()`
+
+Starts onboarding by opening the MetaMask download page and waiting for MetaMask to be installed. Once the MetaMask extension installation is detected, a message will be sent to MetaMask to register the current site as the onboarding initiator.
+
+#### `stopOnboarding()`
+
+Stops onboarding registration, including removing the injected `iframe` (if any).
+
+## Contributing
+
+### Setup
+
+- Install [Node.js](https://nodejs.org) version 12
+  - If you are using [nvm](https://github.com/creationix/nvm#installation) (recommended) running `nvm use` will automatically choose the right node version for you.
+- Install [Yarn v1](https://yarnpkg.com/en/docs/install)
+- Run `yarn setup` to install dependencies and run any requried post-install scripts
+  - **Warning:** Do not use the `yarn` / `yarn install` command directly. Use `yarn setup` instead. The normal install command will skip required post-install scripts, leaving your development environment in an invalid state.
+
+### Linting
+
+Run `yarn lint` to run the linter.
+
+### Release & Publishing
+
+The project follows the same release process as the other libraries in the MetaMask organization:
+
+1. Create a release branch
+   - For a typical release, this would be based on `main`
+   - To update an older maintained major version, base the release branch on the major version branch (e.g. `1.x`)
+2. Update the changelog
+3. Update version in package.json file (e.g. `yarn version --minor --no-git-tag-version`)
+4. Create a pull request targeting the base branch (e.g. `main` or `1.x`)
+5. Code review and QA
+6. Once approved, the PR is squashed & merged
+7. The commit on the base branch is tagged
+8. The tag can be published as needed
+
+## License
+
+This project is available under the [MIT license](./LICENSE).

+ 38 - 0
node_modules/@metamask/onboarding/dist/index.d.ts

@@ -0,0 +1,38 @@
+export default class Onboarding {
+    static FORWARDER_MODE: {
+        INJECT: "INJECT";
+        OPEN_TAB: "OPEN_TAB";
+    };
+    private readonly forwarderOrigin;
+    private readonly downloadUrl;
+    private readonly forwarderMode;
+    private state;
+    constructor({ forwarderOrigin, forwarderMode, }?: {
+        forwarderOrigin?: string | undefined;
+        forwarderMode?: "INJECT" | undefined;
+    });
+    _onMessage(event: MessageEvent): Promise<void> | undefined;
+    _onMessageUnknownStateError(state: never): never;
+    _onMessageFromForwarder(event: MessageEvent): Promise<void>;
+    /**
+     * Starts onboarding by opening the MetaMask download page and the Onboarding forwarder
+     */
+    startOnboarding(): void;
+    /**
+     * Stops onboarding registration, including removing the injected forwarder (if any)
+     *
+     * Typically this function is not necessary, but it can be useful for cases where
+     * onboarding completes before the forwarder has registered.
+     */
+    stopOnboarding(): void;
+    _openForwarder(): void;
+    _openDownloadPage(): void;
+    /**
+     * Checks whether the MetaMask extension is installed
+     */
+    static isMetaMaskInstalled(): boolean;
+    static _register(): any;
+    static _injectForwarder(forwarderOrigin: string): void;
+    static _removeForwarder(): void;
+    static _detectBrowser(): "FIREFOX" | "CHROME" | null;
+}

+ 2462 - 0
node_modules/@metamask/onboarding/dist/metamask-onboarding.bundle.js

@@ -0,0 +1,2462 @@
+var MetaMaskOnboarding = (function () {
+    'use strict';
+
+    /*! *****************************************************************************
+    Copyright (c) Microsoft Corporation.
+
+    Permission to use, copy, modify, and/or distribute this software for any
+    purpose with or without fee is hereby granted.
+
+    THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+    REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+    AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+    INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+    LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+    OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+    PERFORMANCE OF THIS SOFTWARE.
+    ***************************************************************************** */
+
+    function __awaiter(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());
+        });
+    }
+
+    function __generator(thisArg, body) {
+        var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
+        return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
+        function verb(n) { return function (v) { return step([n, v]); }; }
+        function step(op) {
+            if (f) throw new TypeError("Generator is already executing.");
+            while (_) try {
+                if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
+                if (y = 0, t) op = [op[0] & 2, t.value];
+                switch (op[0]) {
+                    case 0: case 1: t = op; break;
+                    case 4: _.label++; return { value: op[1], done: false };
+                    case 5: _.label++; y = op[1]; op = [0]; continue;
+                    case 7: op = _.ops.pop(); _.trys.pop(); continue;
+                    default:
+                        if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
+                        if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
+                        if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
+                        if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
+                        if (t[2]) _.ops.pop();
+                        _.trys.pop(); continue;
+                }
+                op = body.call(thisArg, _);
+            } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
+            if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
+        }
+    }
+
+    // NOTE: this list must be up-to-date with browsers listed in
+    // test/acceptance/useragentstrings.yml
+    const BROWSER_ALIASES_MAP = {
+      'Amazon Silk': 'amazon_silk',
+      'Android Browser': 'android',
+      Bada: 'bada',
+      BlackBerry: 'blackberry',
+      Chrome: 'chrome',
+      Chromium: 'chromium',
+      Electron: 'electron',
+      Epiphany: 'epiphany',
+      Firefox: 'firefox',
+      Focus: 'focus',
+      Generic: 'generic',
+      'Google Search': 'google_search',
+      Googlebot: 'googlebot',
+      'Internet Explorer': 'ie',
+      'K-Meleon': 'k_meleon',
+      Maxthon: 'maxthon',
+      'Microsoft Edge': 'edge',
+      'MZ Browser': 'mz',
+      'NAVER Whale Browser': 'naver',
+      Opera: 'opera',
+      'Opera Coast': 'opera_coast',
+      PhantomJS: 'phantomjs',
+      Puffin: 'puffin',
+      QupZilla: 'qupzilla',
+      QQ: 'qq',
+      QQLite: 'qqlite',
+      Safari: 'safari',
+      Sailfish: 'sailfish',
+      'Samsung Internet for Android': 'samsung_internet',
+      SeaMonkey: 'seamonkey',
+      Sleipnir: 'sleipnir',
+      Swing: 'swing',
+      Tizen: 'tizen',
+      'UC Browser': 'uc',
+      Vivaldi: 'vivaldi',
+      'WebOS Browser': 'webos',
+      WeChat: 'wechat',
+      'Yandex Browser': 'yandex',
+      Roku: 'roku',
+    };
+
+    const BROWSER_MAP = {
+      amazon_silk: 'Amazon Silk',
+      android: 'Android Browser',
+      bada: 'Bada',
+      blackberry: 'BlackBerry',
+      chrome: 'Chrome',
+      chromium: 'Chromium',
+      electron: 'Electron',
+      epiphany: 'Epiphany',
+      firefox: 'Firefox',
+      focus: 'Focus',
+      generic: 'Generic',
+      googlebot: 'Googlebot',
+      google_search: 'Google Search',
+      ie: 'Internet Explorer',
+      k_meleon: 'K-Meleon',
+      maxthon: 'Maxthon',
+      edge: 'Microsoft Edge',
+      mz: 'MZ Browser',
+      naver: 'NAVER Whale Browser',
+      opera: 'Opera',
+      opera_coast: 'Opera Coast',
+      phantomjs: 'PhantomJS',
+      puffin: 'Puffin',
+      qupzilla: 'QupZilla',
+      qq: 'QQ Browser',
+      qqlite: 'QQ Browser Lite',
+      safari: 'Safari',
+      sailfish: 'Sailfish',
+      samsung_internet: 'Samsung Internet for Android',
+      seamonkey: 'SeaMonkey',
+      sleipnir: 'Sleipnir',
+      swing: 'Swing',
+      tizen: 'Tizen',
+      uc: 'UC Browser',
+      vivaldi: 'Vivaldi',
+      webos: 'WebOS Browser',
+      wechat: 'WeChat',
+      yandex: 'Yandex Browser',
+    };
+
+    const PLATFORMS_MAP = {
+      tablet: 'tablet',
+      mobile: 'mobile',
+      desktop: 'desktop',
+      tv: 'tv',
+    };
+
+    const OS_MAP = {
+      WindowsPhone: 'Windows Phone',
+      Windows: 'Windows',
+      MacOS: 'macOS',
+      iOS: 'iOS',
+      Android: 'Android',
+      WebOS: 'WebOS',
+      BlackBerry: 'BlackBerry',
+      Bada: 'Bada',
+      Tizen: 'Tizen',
+      Linux: 'Linux',
+      ChromeOS: 'Chrome OS',
+      PlayStation4: 'PlayStation 4',
+      Roku: 'Roku',
+    };
+
+    const ENGINE_MAP = {
+      EdgeHTML: 'EdgeHTML',
+      Blink: 'Blink',
+      Trident: 'Trident',
+      Presto: 'Presto',
+      Gecko: 'Gecko',
+      WebKit: 'WebKit',
+    };
+
+    class Utils {
+      /**
+       * Get first matched item for a string
+       * @param {RegExp} regexp
+       * @param {String} ua
+       * @return {Array|{index: number, input: string}|*|boolean|string}
+       */
+      static getFirstMatch(regexp, ua) {
+        const match = ua.match(regexp);
+        return (match && match.length > 0 && match[1]) || '';
+      }
+
+      /**
+       * Get second matched item for a string
+       * @param regexp
+       * @param {String} ua
+       * @return {Array|{index: number, input: string}|*|boolean|string}
+       */
+      static getSecondMatch(regexp, ua) {
+        const match = ua.match(regexp);
+        return (match && match.length > 1 && match[2]) || '';
+      }
+
+      /**
+       * Match a regexp and return a constant or undefined
+       * @param {RegExp} regexp
+       * @param {String} ua
+       * @param {*} _const Any const that will be returned if regexp matches the string
+       * @return {*}
+       */
+      static matchAndReturnConst(regexp, ua, _const) {
+        if (regexp.test(ua)) {
+          return _const;
+        }
+        return void (0);
+      }
+
+      static getWindowsVersionName(version) {
+        switch (version) {
+          case 'NT': return 'NT';
+          case 'XP': return 'XP';
+          case 'NT 5.0': return '2000';
+          case 'NT 5.1': return 'XP';
+          case 'NT 5.2': return '2003';
+          case 'NT 6.0': return 'Vista';
+          case 'NT 6.1': return '7';
+          case 'NT 6.2': return '8';
+          case 'NT 6.3': return '8.1';
+          case 'NT 10.0': return '10';
+          default: return undefined;
+        }
+      }
+
+      /**
+       * Get macOS version name
+       *    10.5 - Leopard
+       *    10.6 - Snow Leopard
+       *    10.7 - Lion
+       *    10.8 - Mountain Lion
+       *    10.9 - Mavericks
+       *    10.10 - Yosemite
+       *    10.11 - El Capitan
+       *    10.12 - Sierra
+       *    10.13 - High Sierra
+       *    10.14 - Mojave
+       *    10.15 - Catalina
+       *
+       * @example
+       *   getMacOSVersionName("10.14") // 'Mojave'
+       *
+       * @param  {string} version
+       * @return {string} versionName
+       */
+      static getMacOSVersionName(version) {
+        const v = version.split('.').splice(0, 2).map(s => parseInt(s, 10) || 0);
+        v.push(0);
+        if (v[0] !== 10) return undefined;
+        switch (v[1]) {
+          case 5: return 'Leopard';
+          case 6: return 'Snow Leopard';
+          case 7: return 'Lion';
+          case 8: return 'Mountain Lion';
+          case 9: return 'Mavericks';
+          case 10: return 'Yosemite';
+          case 11: return 'El Capitan';
+          case 12: return 'Sierra';
+          case 13: return 'High Sierra';
+          case 14: return 'Mojave';
+          case 15: return 'Catalina';
+          default: return undefined;
+        }
+      }
+
+      /**
+       * Get Android version name
+       *    1.5 - Cupcake
+       *    1.6 - Donut
+       *    2.0 - Eclair
+       *    2.1 - Eclair
+       *    2.2 - Froyo
+       *    2.x - Gingerbread
+       *    3.x - Honeycomb
+       *    4.0 - Ice Cream Sandwich
+       *    4.1 - Jelly Bean
+       *    4.4 - KitKat
+       *    5.x - Lollipop
+       *    6.x - Marshmallow
+       *    7.x - Nougat
+       *    8.x - Oreo
+       *    9.x - Pie
+       *
+       * @example
+       *   getAndroidVersionName("7.0") // 'Nougat'
+       *
+       * @param  {string} version
+       * @return {string} versionName
+       */
+      static getAndroidVersionName(version) {
+        const v = version.split('.').splice(0, 2).map(s => parseInt(s, 10) || 0);
+        v.push(0);
+        if (v[0] === 1 && v[1] < 5) return undefined;
+        if (v[0] === 1 && v[1] < 6) return 'Cupcake';
+        if (v[0] === 1 && v[1] >= 6) return 'Donut';
+        if (v[0] === 2 && v[1] < 2) return 'Eclair';
+        if (v[0] === 2 && v[1] === 2) return 'Froyo';
+        if (v[0] === 2 && v[1] > 2) return 'Gingerbread';
+        if (v[0] === 3) return 'Honeycomb';
+        if (v[0] === 4 && v[1] < 1) return 'Ice Cream Sandwich';
+        if (v[0] === 4 && v[1] < 4) return 'Jelly Bean';
+        if (v[0] === 4 && v[1] >= 4) return 'KitKat';
+        if (v[0] === 5) return 'Lollipop';
+        if (v[0] === 6) return 'Marshmallow';
+        if (v[0] === 7) return 'Nougat';
+        if (v[0] === 8) return 'Oreo';
+        if (v[0] === 9) return 'Pie';
+        return undefined;
+      }
+
+      /**
+       * Get version precisions count
+       *
+       * @example
+       *   getVersionPrecision("1.10.3") // 3
+       *
+       * @param  {string} version
+       * @return {number}
+       */
+      static getVersionPrecision(version) {
+        return version.split('.').length;
+      }
+
+      /**
+       * Calculate browser version weight
+       *
+       * @example
+       *   compareVersions('1.10.2.1',  '1.8.2.1.90')    // 1
+       *   compareVersions('1.010.2.1', '1.09.2.1.90');  // 1
+       *   compareVersions('1.10.2.1',  '1.10.2.1');     // 0
+       *   compareVersions('1.10.2.1',  '1.0800.2');     // -1
+       *   compareVersions('1.10.2.1',  '1.10',  true);  // 0
+       *
+       * @param {String} versionA versions versions to compare
+       * @param {String} versionB versions versions to compare
+       * @param {boolean} [isLoose] enable loose comparison
+       * @return {Number} comparison result: -1 when versionA is lower,
+       * 1 when versionA is bigger, 0 when both equal
+       */
+      /* eslint consistent-return: 1 */
+      static compareVersions(versionA, versionB, isLoose = false) {
+        // 1) get common precision for both versions, for example for "10.0" and "9" it should be 2
+        const versionAPrecision = Utils.getVersionPrecision(versionA);
+        const versionBPrecision = Utils.getVersionPrecision(versionB);
+
+        let precision = Math.max(versionAPrecision, versionBPrecision);
+        let lastPrecision = 0;
+
+        const chunks = Utils.map([versionA, versionB], (version) => {
+          const delta = precision - Utils.getVersionPrecision(version);
+
+          // 2) "9" -> "9.0" (for precision = 2)
+          const _version = version + new Array(delta + 1).join('.0');
+
+          // 3) "9.0" -> ["000000000"", "000000009"]
+          return Utils.map(_version.split('.'), chunk => new Array(20 - chunk.length).join('0') + chunk).reverse();
+        });
+
+        // adjust precision for loose comparison
+        if (isLoose) {
+          lastPrecision = precision - Math.min(versionAPrecision, versionBPrecision);
+        }
+
+        // iterate in reverse order by reversed chunks array
+        precision -= 1;
+        while (precision >= lastPrecision) {
+          // 4) compare: "000000009" > "000000010" = false (but "9" > "10" = true)
+          if (chunks[0][precision] > chunks[1][precision]) {
+            return 1;
+          }
+
+          if (chunks[0][precision] === chunks[1][precision]) {
+            if (precision === lastPrecision) {
+              // all version chunks are same
+              return 0;
+            }
+
+            precision -= 1;
+          } else if (chunks[0][precision] < chunks[1][precision]) {
+            return -1;
+          }
+        }
+
+        return undefined;
+      }
+
+      /**
+       * Array::map polyfill
+       *
+       * @param  {Array} arr
+       * @param  {Function} iterator
+       * @return {Array}
+       */
+      static map(arr, iterator) {
+        const result = [];
+        let i;
+        if (Array.prototype.map) {
+          return Array.prototype.map.call(arr, iterator);
+        }
+        for (i = 0; i < arr.length; i += 1) {
+          result.push(iterator(arr[i]));
+        }
+        return result;
+      }
+
+      /**
+       * Array::find polyfill
+       *
+       * @param  {Array} arr
+       * @param  {Function} predicate
+       * @return {Array}
+       */
+      static find(arr, predicate) {
+        let i;
+        let l;
+        if (Array.prototype.find) {
+          return Array.prototype.find.call(arr, predicate);
+        }
+        for (i = 0, l = arr.length; i < l; i += 1) {
+          const value = arr[i];
+          if (predicate(value, i)) {
+            return value;
+          }
+        }
+        return undefined;
+      }
+
+      /**
+       * Object::assign polyfill
+       *
+       * @param  {Object} obj
+       * @param  {Object} ...objs
+       * @return {Object}
+       */
+      static assign(obj, ...assigners) {
+        const result = obj;
+        let i;
+        let l;
+        if (Object.assign) {
+          return Object.assign(obj, ...assigners);
+        }
+        for (i = 0, l = assigners.length; i < l; i += 1) {
+          const assigner = assigners[i];
+          if (typeof assigner === 'object' && assigner !== null) {
+            const keys = Object.keys(assigner);
+            keys.forEach((key) => {
+              result[key] = assigner[key];
+            });
+          }
+        }
+        return obj;
+      }
+
+      /**
+       * Get short version/alias for a browser name
+       *
+       * @example
+       *   getBrowserAlias('Microsoft Edge') // edge
+       *
+       * @param  {string} browserName
+       * @return {string}
+       */
+      static getBrowserAlias(browserName) {
+        return BROWSER_ALIASES_MAP[browserName];
+      }
+
+      /**
+       * Get short version/alias for a browser name
+       *
+       * @example
+       *   getBrowserAlias('edge') // Microsoft Edge
+       *
+       * @param  {string} browserAlias
+       * @return {string}
+       */
+      static getBrowserTypeByAlias(browserAlias) {
+        return BROWSER_MAP[browserAlias] || '';
+      }
+    }
+
+    /**
+     * Browsers' descriptors
+     *
+     * The idea of descriptors is simple. You should know about them two simple things:
+     * 1. Every descriptor has a method or property called `test` and a `describe` method.
+     * 2. Order of descriptors is important.
+     *
+     * More details:
+     * 1. Method or property `test` serves as a way to detect whether the UA string
+     * matches some certain browser or not. The `describe` method helps to make a result
+     * object with params that show some browser-specific things: name, version, etc.
+     * 2. Order of descriptors is important because a Parser goes through them one by one
+     * in course. For example, if you insert Chrome's descriptor as the first one,
+     * more then a half of browsers will be described as Chrome, because they will pass
+     * the Chrome descriptor's test.
+     *
+     * Descriptor's `test` could be a property with an array of RegExps, where every RegExp
+     * will be applied to a UA string to test it whether it matches or not.
+     * If a descriptor has two or more regexps in the `test` array it tests them one by one
+     * with a logical sum operation. Parser stops if it has found any RegExp that matches the UA.
+     *
+     * Or `test` could be a method. In that case it gets a Parser instance and should
+     * return true/false to get the Parser know if this browser descriptor matches the UA or not.
+     */
+
+    const commonVersionIdentifier = /version\/(\d+(\.?_?\d+)+)/i;
+
+    const browsersList = [
+      /* Googlebot */
+      {
+        test: [/googlebot/i],
+        describe(ua) {
+          const browser = {
+            name: 'Googlebot',
+          };
+          const version = Utils.getFirstMatch(/googlebot\/(\d+(\.\d+))/i, ua) || Utils.getFirstMatch(commonVersionIdentifier, ua);
+
+          if (version) {
+            browser.version = version;
+          }
+
+          return browser;
+        },
+      },
+
+      /* Opera < 13.0 */
+      {
+        test: [/opera/i],
+        describe(ua) {
+          const browser = {
+            name: 'Opera',
+          };
+          const version = Utils.getFirstMatch(commonVersionIdentifier, ua) || Utils.getFirstMatch(/(?:opera)[\s/](\d+(\.?_?\d+)+)/i, ua);
+
+          if (version) {
+            browser.version = version;
+          }
+
+          return browser;
+        },
+      },
+
+      /* Opera > 13.0 */
+      {
+        test: [/opr\/|opios/i],
+        describe(ua) {
+          const browser = {
+            name: 'Opera',
+          };
+          const version = Utils.getFirstMatch(/(?:opr|opios)[\s/](\S+)/i, ua) || Utils.getFirstMatch(commonVersionIdentifier, ua);
+
+          if (version) {
+            browser.version = version;
+          }
+
+          return browser;
+        },
+      },
+      {
+        test: [/SamsungBrowser/i],
+        describe(ua) {
+          const browser = {
+            name: 'Samsung Internet for Android',
+          };
+          const version = Utils.getFirstMatch(commonVersionIdentifier, ua) || Utils.getFirstMatch(/(?:SamsungBrowser)[\s/](\d+(\.?_?\d+)+)/i, ua);
+
+          if (version) {
+            browser.version = version;
+          }
+
+          return browser;
+        },
+      },
+      {
+        test: [/Whale/i],
+        describe(ua) {
+          const browser = {
+            name: 'NAVER Whale Browser',
+          };
+          const version = Utils.getFirstMatch(commonVersionIdentifier, ua) || Utils.getFirstMatch(/(?:whale)[\s/](\d+(?:\.\d+)+)/i, ua);
+
+          if (version) {
+            browser.version = version;
+          }
+
+          return browser;
+        },
+      },
+      {
+        test: [/MZBrowser/i],
+        describe(ua) {
+          const browser = {
+            name: 'MZ Browser',
+          };
+          const version = Utils.getFirstMatch(/(?:MZBrowser)[\s/](\d+(?:\.\d+)+)/i, ua) || Utils.getFirstMatch(commonVersionIdentifier, ua);
+
+          if (version) {
+            browser.version = version;
+          }
+
+          return browser;
+        },
+      },
+      {
+        test: [/focus/i],
+        describe(ua) {
+          const browser = {
+            name: 'Focus',
+          };
+          const version = Utils.getFirstMatch(/(?:focus)[\s/](\d+(?:\.\d+)+)/i, ua) || Utils.getFirstMatch(commonVersionIdentifier, ua);
+
+          if (version) {
+            browser.version = version;
+          }
+
+          return browser;
+        },
+      },
+      {
+        test: [/swing/i],
+        describe(ua) {
+          const browser = {
+            name: 'Swing',
+          };
+          const version = Utils.getFirstMatch(/(?:swing)[\s/](\d+(?:\.\d+)+)/i, ua) || Utils.getFirstMatch(commonVersionIdentifier, ua);
+
+          if (version) {
+            browser.version = version;
+          }
+
+          return browser;
+        },
+      },
+      {
+        test: [/coast/i],
+        describe(ua) {
+          const browser = {
+            name: 'Opera Coast',
+          };
+          const version = Utils.getFirstMatch(commonVersionIdentifier, ua) || Utils.getFirstMatch(/(?:coast)[\s/](\d+(\.?_?\d+)+)/i, ua);
+
+          if (version) {
+            browser.version = version;
+          }
+
+          return browser;
+        },
+      },
+      {
+        test: [/yabrowser/i],
+        describe(ua) {
+          const browser = {
+            name: 'Yandex Browser',
+          };
+          const version = Utils.getFirstMatch(/(?:yabrowser)[\s/](\d+(\.?_?\d+)+)/i, ua) || Utils.getFirstMatch(commonVersionIdentifier, ua);
+
+          if (version) {
+            browser.version = version;
+          }
+
+          return browser;
+        },
+      },
+      {
+        test: [/ucbrowser/i],
+        describe(ua) {
+          const browser = {
+            name: 'UC Browser',
+          };
+          const version = Utils.getFirstMatch(commonVersionIdentifier, ua) || Utils.getFirstMatch(/(?:ucbrowser)[\s/](\d+(\.?_?\d+)+)/i, ua);
+
+          if (version) {
+            browser.version = version;
+          }
+
+          return browser;
+        },
+      },
+      {
+        test: [/Maxthon|mxios/i],
+        describe(ua) {
+          const browser = {
+            name: 'Maxthon',
+          };
+          const version = Utils.getFirstMatch(commonVersionIdentifier, ua) || Utils.getFirstMatch(/(?:Maxthon|mxios)[\s/](\d+(\.?_?\d+)+)/i, ua);
+
+          if (version) {
+            browser.version = version;
+          }
+
+          return browser;
+        },
+      },
+      {
+        test: [/epiphany/i],
+        describe(ua) {
+          const browser = {
+            name: 'Epiphany',
+          };
+          const version = Utils.getFirstMatch(commonVersionIdentifier, ua) || Utils.getFirstMatch(/(?:epiphany)[\s/](\d+(\.?_?\d+)+)/i, ua);
+
+          if (version) {
+            browser.version = version;
+          }
+
+          return browser;
+        },
+      },
+      {
+        test: [/puffin/i],
+        describe(ua) {
+          const browser = {
+            name: 'Puffin',
+          };
+          const version = Utils.getFirstMatch(commonVersionIdentifier, ua) || Utils.getFirstMatch(/(?:puffin)[\s/](\d+(\.?_?\d+)+)/i, ua);
+
+          if (version) {
+            browser.version = version;
+          }
+
+          return browser;
+        },
+      },
+      {
+        test: [/sleipnir/i],
+        describe(ua) {
+          const browser = {
+            name: 'Sleipnir',
+          };
+          const version = Utils.getFirstMatch(commonVersionIdentifier, ua) || Utils.getFirstMatch(/(?:sleipnir)[\s/](\d+(\.?_?\d+)+)/i, ua);
+
+          if (version) {
+            browser.version = version;
+          }
+
+          return browser;
+        },
+      },
+      {
+        test: [/k-meleon/i],
+        describe(ua) {
+          const browser = {
+            name: 'K-Meleon',
+          };
+          const version = Utils.getFirstMatch(commonVersionIdentifier, ua) || Utils.getFirstMatch(/(?:k-meleon)[\s/](\d+(\.?_?\d+)+)/i, ua);
+
+          if (version) {
+            browser.version = version;
+          }
+
+          return browser;
+        },
+      },
+      {
+        test: [/micromessenger/i],
+        describe(ua) {
+          const browser = {
+            name: 'WeChat',
+          };
+          const version = Utils.getFirstMatch(/(?:micromessenger)[\s/](\d+(\.?_?\d+)+)/i, ua) || Utils.getFirstMatch(commonVersionIdentifier, ua);
+
+          if (version) {
+            browser.version = version;
+          }
+
+          return browser;
+        },
+      },
+      {
+        test: [/qqbrowser/i],
+        describe(ua) {
+          const browser = {
+            name: (/qqbrowserlite/i).test(ua) ? 'QQ Browser Lite' : 'QQ Browser',
+          };
+          const version = Utils.getFirstMatch(/(?:qqbrowserlite|qqbrowser)[/](\d+(\.?_?\d+)+)/i, ua) || Utils.getFirstMatch(commonVersionIdentifier, ua);
+
+          if (version) {
+            browser.version = version;
+          }
+
+          return browser;
+        },
+      },
+      {
+        test: [/msie|trident/i],
+        describe(ua) {
+          const browser = {
+            name: 'Internet Explorer',
+          };
+          const version = Utils.getFirstMatch(/(?:msie |rv:)(\d+(\.?_?\d+)+)/i, ua);
+
+          if (version) {
+            browser.version = version;
+          }
+
+          return browser;
+        },
+      },
+      {
+        test: [/\sedg\//i],
+        describe(ua) {
+          const browser = {
+            name: 'Microsoft Edge',
+          };
+
+          const version = Utils.getFirstMatch(/\sedg\/(\d+(\.?_?\d+)+)/i, ua);
+
+          if (version) {
+            browser.version = version;
+          }
+
+          return browser;
+        },
+      },
+      {
+        test: [/edg([ea]|ios)/i],
+        describe(ua) {
+          const browser = {
+            name: 'Microsoft Edge',
+          };
+
+          const version = Utils.getSecondMatch(/edg([ea]|ios)\/(\d+(\.?_?\d+)+)/i, ua);
+
+          if (version) {
+            browser.version = version;
+          }
+
+          return browser;
+        },
+      },
+      {
+        test: [/vivaldi/i],
+        describe(ua) {
+          const browser = {
+            name: 'Vivaldi',
+          };
+          const version = Utils.getFirstMatch(/vivaldi\/(\d+(\.?_?\d+)+)/i, ua);
+
+          if (version) {
+            browser.version = version;
+          }
+
+          return browser;
+        },
+      },
+      {
+        test: [/seamonkey/i],
+        describe(ua) {
+          const browser = {
+            name: 'SeaMonkey',
+          };
+          const version = Utils.getFirstMatch(/seamonkey\/(\d+(\.?_?\d+)+)/i, ua);
+
+          if (version) {
+            browser.version = version;
+          }
+
+          return browser;
+        },
+      },
+      {
+        test: [/sailfish/i],
+        describe(ua) {
+          const browser = {
+            name: 'Sailfish',
+          };
+
+          const version = Utils.getFirstMatch(/sailfish\s?browser\/(\d+(\.\d+)?)/i, ua);
+
+          if (version) {
+            browser.version = version;
+          }
+
+          return browser;
+        },
+      },
+      {
+        test: [/silk/i],
+        describe(ua) {
+          const browser = {
+            name: 'Amazon Silk',
+          };
+          const version = Utils.getFirstMatch(/silk\/(\d+(\.?_?\d+)+)/i, ua);
+
+          if (version) {
+            browser.version = version;
+          }
+
+          return browser;
+        },
+      },
+      {
+        test: [/phantom/i],
+        describe(ua) {
+          const browser = {
+            name: 'PhantomJS',
+          };
+          const version = Utils.getFirstMatch(/phantomjs\/(\d+(\.?_?\d+)+)/i, ua);
+
+          if (version) {
+            browser.version = version;
+          }
+
+          return browser;
+        },
+      },
+      {
+        test: [/slimerjs/i],
+        describe(ua) {
+          const browser = {
+            name: 'SlimerJS',
+          };
+          const version = Utils.getFirstMatch(/slimerjs\/(\d+(\.?_?\d+)+)/i, ua);
+
+          if (version) {
+            browser.version = version;
+          }
+
+          return browser;
+        },
+      },
+      {
+        test: [/blackberry|\bbb\d+/i, /rim\stablet/i],
+        describe(ua) {
+          const browser = {
+            name: 'BlackBerry',
+          };
+          const version = Utils.getFirstMatch(commonVersionIdentifier, ua) || Utils.getFirstMatch(/blackberry[\d]+\/(\d+(\.?_?\d+)+)/i, ua);
+
+          if (version) {
+            browser.version = version;
+          }
+
+          return browser;
+        },
+      },
+      {
+        test: [/(web|hpw)[o0]s/i],
+        describe(ua) {
+          const browser = {
+            name: 'WebOS Browser',
+          };
+          const version = Utils.getFirstMatch(commonVersionIdentifier, ua) || Utils.getFirstMatch(/w(?:eb)?[o0]sbrowser\/(\d+(\.?_?\d+)+)/i, ua);
+
+          if (version) {
+            browser.version = version;
+          }
+
+          return browser;
+        },
+      },
+      {
+        test: [/bada/i],
+        describe(ua) {
+          const browser = {
+            name: 'Bada',
+          };
+          const version = Utils.getFirstMatch(/dolfin\/(\d+(\.?_?\d+)+)/i, ua);
+
+          if (version) {
+            browser.version = version;
+          }
+
+          return browser;
+        },
+      },
+      {
+        test: [/tizen/i],
+        describe(ua) {
+          const browser = {
+            name: 'Tizen',
+          };
+          const version = Utils.getFirstMatch(/(?:tizen\s?)?browser\/(\d+(\.?_?\d+)+)/i, ua) || Utils.getFirstMatch(commonVersionIdentifier, ua);
+
+          if (version) {
+            browser.version = version;
+          }
+
+          return browser;
+        },
+      },
+      {
+        test: [/qupzilla/i],
+        describe(ua) {
+          const browser = {
+            name: 'QupZilla',
+          };
+          const version = Utils.getFirstMatch(/(?:qupzilla)[\s/](\d+(\.?_?\d+)+)/i, ua) || Utils.getFirstMatch(commonVersionIdentifier, ua);
+
+          if (version) {
+            browser.version = version;
+          }
+
+          return browser;
+        },
+      },
+      {
+        test: [/firefox|iceweasel|fxios/i],
+        describe(ua) {
+          const browser = {
+            name: 'Firefox',
+          };
+          const version = Utils.getFirstMatch(/(?:firefox|iceweasel|fxios)[\s/](\d+(\.?_?\d+)+)/i, ua);
+
+          if (version) {
+            browser.version = version;
+          }
+
+          return browser;
+        },
+      },
+      {
+        test: [/electron/i],
+        describe(ua) {
+          const browser = {
+            name: 'Electron',
+          };
+          const version = Utils.getFirstMatch(/(?:electron)\/(\d+(\.?_?\d+)+)/i, ua);
+
+          if (version) {
+            browser.version = version;
+          }
+
+          return browser;
+        },
+      },
+      {
+        test: [/chromium/i],
+        describe(ua) {
+          const browser = {
+            name: 'Chromium',
+          };
+          const version = Utils.getFirstMatch(/(?:chromium)[\s/](\d+(\.?_?\d+)+)/i, ua) || Utils.getFirstMatch(commonVersionIdentifier, ua);
+
+          if (version) {
+            browser.version = version;
+          }
+
+          return browser;
+        },
+      },
+      {
+        test: [/chrome|crios|crmo/i],
+        describe(ua) {
+          const browser = {
+            name: 'Chrome',
+          };
+          const version = Utils.getFirstMatch(/(?:chrome|crios|crmo)\/(\d+(\.?_?\d+)+)/i, ua);
+
+          if (version) {
+            browser.version = version;
+          }
+
+          return browser;
+        },
+      },
+      {
+        test: [/GSA/i],
+        describe(ua) {
+          const browser = {
+            name: 'Google Search',
+          };
+          const version = Utils.getFirstMatch(/(?:GSA)\/(\d+(\.?_?\d+)+)/i, ua);
+
+          if (version) {
+            browser.version = version;
+          }
+
+          return browser;
+        },
+      },
+
+      /* Android Browser */
+      {
+        test(parser) {
+          const notLikeAndroid = !parser.test(/like android/i);
+          const butAndroid = parser.test(/android/i);
+          return notLikeAndroid && butAndroid;
+        },
+        describe(ua) {
+          const browser = {
+            name: 'Android Browser',
+          };
+          const version = Utils.getFirstMatch(commonVersionIdentifier, ua);
+
+          if (version) {
+            browser.version = version;
+          }
+
+          return browser;
+        },
+      },
+
+      /* PlayStation 4 */
+      {
+        test: [/playstation 4/i],
+        describe(ua) {
+          const browser = {
+            name: 'PlayStation 4',
+          };
+          const version = Utils.getFirstMatch(commonVersionIdentifier, ua);
+
+          if (version) {
+            browser.version = version;
+          }
+
+          return browser;
+        },
+      },
+
+      /* Safari */
+      {
+        test: [/safari|applewebkit/i],
+        describe(ua) {
+          const browser = {
+            name: 'Safari',
+          };
+          const version = Utils.getFirstMatch(commonVersionIdentifier, ua);
+
+          if (version) {
+            browser.version = version;
+          }
+
+          return browser;
+        },
+      },
+
+      /* Something else */
+      {
+        test: [/.*/i],
+        describe(ua) {
+          /* Here we try to make sure that there are explicit details about the device
+           * in order to decide what regexp exactly we want to apply
+           * (as there is a specific decision based on that conclusion)
+           */
+          const regexpWithoutDeviceSpec = /^(.*)\/(.*) /;
+          const regexpWithDeviceSpec = /^(.*)\/(.*)[ \t]\((.*)/;
+          const hasDeviceSpec = ua.search('\\(') !== -1;
+          const regexp = hasDeviceSpec ? regexpWithDeviceSpec : regexpWithoutDeviceSpec;
+          return {
+            name: Utils.getFirstMatch(regexp, ua),
+            version: Utils.getSecondMatch(regexp, ua),
+          };
+        },
+      },
+    ];
+
+    var osParsersList = [
+      /* Roku */
+      {
+        test: [/Roku\/DVP/],
+        describe(ua) {
+          const version = Utils.getFirstMatch(/Roku\/DVP-(\d+\.\d+)/i, ua);
+          return {
+            name: OS_MAP.Roku,
+            version,
+          };
+        },
+      },
+
+      /* Windows Phone */
+      {
+        test: [/windows phone/i],
+        describe(ua) {
+          const version = Utils.getFirstMatch(/windows phone (?:os)?\s?(\d+(\.\d+)*)/i, ua);
+          return {
+            name: OS_MAP.WindowsPhone,
+            version,
+          };
+        },
+      },
+
+      /* Windows */
+      {
+        test: [/windows /i],
+        describe(ua) {
+          const version = Utils.getFirstMatch(/Windows ((NT|XP)( \d\d?.\d)?)/i, ua);
+          const versionName = Utils.getWindowsVersionName(version);
+
+          return {
+            name: OS_MAP.Windows,
+            version,
+            versionName,
+          };
+        },
+      },
+
+      /* Firefox on iPad */
+      {
+        test: [/Macintosh(.*?) FxiOS(.*?) Version\//],
+        describe(ua) {
+          const version = Utils.getSecondMatch(/(Version\/)(\d[\d.]+)/, ua);
+          return {
+            name: OS_MAP.iOS,
+            version,
+          };
+        },
+      },
+
+      /* macOS */
+      {
+        test: [/macintosh/i],
+        describe(ua) {
+          const version = Utils.getFirstMatch(/mac os x (\d+(\.?_?\d+)+)/i, ua).replace(/[_\s]/g, '.');
+          const versionName = Utils.getMacOSVersionName(version);
+
+          const os = {
+            name: OS_MAP.MacOS,
+            version,
+          };
+          if (versionName) {
+            os.versionName = versionName;
+          }
+          return os;
+        },
+      },
+
+      /* iOS */
+      {
+        test: [/(ipod|iphone|ipad)/i],
+        describe(ua) {
+          const version = Utils.getFirstMatch(/os (\d+([_\s]\d+)*) like mac os x/i, ua).replace(/[_\s]/g, '.');
+
+          return {
+            name: OS_MAP.iOS,
+            version,
+          };
+        },
+      },
+
+      /* Android */
+      {
+        test(parser) {
+          const notLikeAndroid = !parser.test(/like android/i);
+          const butAndroid = parser.test(/android/i);
+          return notLikeAndroid && butAndroid;
+        },
+        describe(ua) {
+          const version = Utils.getFirstMatch(/android[\s/-](\d+(\.\d+)*)/i, ua);
+          const versionName = Utils.getAndroidVersionName(version);
+          const os = {
+            name: OS_MAP.Android,
+            version,
+          };
+          if (versionName) {
+            os.versionName = versionName;
+          }
+          return os;
+        },
+      },
+
+      /* WebOS */
+      {
+        test: [/(web|hpw)[o0]s/i],
+        describe(ua) {
+          const version = Utils.getFirstMatch(/(?:web|hpw)[o0]s\/(\d+(\.\d+)*)/i, ua);
+          const os = {
+            name: OS_MAP.WebOS,
+          };
+
+          if (version && version.length) {
+            os.version = version;
+          }
+          return os;
+        },
+      },
+
+      /* BlackBerry */
+      {
+        test: [/blackberry|\bbb\d+/i, /rim\stablet/i],
+        describe(ua) {
+          const version = Utils.getFirstMatch(/rim\stablet\sos\s(\d+(\.\d+)*)/i, ua)
+            || Utils.getFirstMatch(/blackberry\d+\/(\d+([_\s]\d+)*)/i, ua)
+            || Utils.getFirstMatch(/\bbb(\d+)/i, ua);
+
+          return {
+            name: OS_MAP.BlackBerry,
+            version,
+          };
+        },
+      },
+
+      /* Bada */
+      {
+        test: [/bada/i],
+        describe(ua) {
+          const version = Utils.getFirstMatch(/bada\/(\d+(\.\d+)*)/i, ua);
+
+          return {
+            name: OS_MAP.Bada,
+            version,
+          };
+        },
+      },
+
+      /* Tizen */
+      {
+        test: [/tizen/i],
+        describe(ua) {
+          const version = Utils.getFirstMatch(/tizen[/\s](\d+(\.\d+)*)/i, ua);
+
+          return {
+            name: OS_MAP.Tizen,
+            version,
+          };
+        },
+      },
+
+      /* Linux */
+      {
+        test: [/linux/i],
+        describe() {
+          return {
+            name: OS_MAP.Linux,
+          };
+        },
+      },
+
+      /* Chrome OS */
+      {
+        test: [/CrOS/],
+        describe() {
+          return {
+            name: OS_MAP.ChromeOS,
+          };
+        },
+      },
+
+      /* Playstation 4 */
+      {
+        test: [/PlayStation 4/],
+        describe(ua) {
+          const version = Utils.getFirstMatch(/PlayStation 4[/\s](\d+(\.\d+)*)/i, ua);
+          return {
+            name: OS_MAP.PlayStation4,
+            version,
+          };
+        },
+      },
+    ];
+
+    /*
+     * Tablets go first since usually they have more specific
+     * signs to detect.
+     */
+
+    var platformParsersList = [
+      /* Googlebot */
+      {
+        test: [/googlebot/i],
+        describe() {
+          return {
+            type: 'bot',
+            vendor: 'Google',
+          };
+        },
+      },
+
+      /* Huawei */
+      {
+        test: [/huawei/i],
+        describe(ua) {
+          const model = Utils.getFirstMatch(/(can-l01)/i, ua) && 'Nova';
+          const platform = {
+            type: PLATFORMS_MAP.mobile,
+            vendor: 'Huawei',
+          };
+          if (model) {
+            platform.model = model;
+          }
+          return platform;
+        },
+      },
+
+      /* Nexus Tablet */
+      {
+        test: [/nexus\s*(?:7|8|9|10).*/i],
+        describe() {
+          return {
+            type: PLATFORMS_MAP.tablet,
+            vendor: 'Nexus',
+          };
+        },
+      },
+
+      /* iPad */
+      {
+        test: [/ipad/i],
+        describe() {
+          return {
+            type: PLATFORMS_MAP.tablet,
+            vendor: 'Apple',
+            model: 'iPad',
+          };
+        },
+      },
+
+      /* Firefox on iPad */
+      {
+        test: [/Macintosh(.*?) FxiOS(.*?) Version\//],
+        describe() {
+          return {
+            type: PLATFORMS_MAP.tablet,
+            vendor: 'Apple',
+            model: 'iPad',
+          };
+        },
+      },
+
+      /* Amazon Kindle Fire */
+      {
+        test: [/kftt build/i],
+        describe() {
+          return {
+            type: PLATFORMS_MAP.tablet,
+            vendor: 'Amazon',
+            model: 'Kindle Fire HD 7',
+          };
+        },
+      },
+
+      /* Another Amazon Tablet with Silk */
+      {
+        test: [/silk/i],
+        describe() {
+          return {
+            type: PLATFORMS_MAP.tablet,
+            vendor: 'Amazon',
+          };
+        },
+      },
+
+      /* Tablet */
+      {
+        test: [/tablet(?! pc)/i],
+        describe() {
+          return {
+            type: PLATFORMS_MAP.tablet,
+          };
+        },
+      },
+
+      /* iPod/iPhone */
+      {
+        test(parser) {
+          const iDevice = parser.test(/ipod|iphone/i);
+          const likeIDevice = parser.test(/like (ipod|iphone)/i);
+          return iDevice && !likeIDevice;
+        },
+        describe(ua) {
+          const model = Utils.getFirstMatch(/(ipod|iphone)/i, ua);
+          return {
+            type: PLATFORMS_MAP.mobile,
+            vendor: 'Apple',
+            model,
+          };
+        },
+      },
+
+      /* Nexus Mobile */
+      {
+        test: [/nexus\s*[0-6].*/i, /galaxy nexus/i],
+        describe() {
+          return {
+            type: PLATFORMS_MAP.mobile,
+            vendor: 'Nexus',
+          };
+        },
+      },
+
+      /* Mobile */
+      {
+        test: [/[^-]mobi/i],
+        describe() {
+          return {
+            type: PLATFORMS_MAP.mobile,
+          };
+        },
+      },
+
+      /* BlackBerry */
+      {
+        test(parser) {
+          return parser.getBrowserName(true) === 'blackberry';
+        },
+        describe() {
+          return {
+            type: PLATFORMS_MAP.mobile,
+            vendor: 'BlackBerry',
+          };
+        },
+      },
+
+      /* Bada */
+      {
+        test(parser) {
+          return parser.getBrowserName(true) === 'bada';
+        },
+        describe() {
+          return {
+            type: PLATFORMS_MAP.mobile,
+          };
+        },
+      },
+
+      /* Windows Phone */
+      {
+        test(parser) {
+          return parser.getBrowserName() === 'windows phone';
+        },
+        describe() {
+          return {
+            type: PLATFORMS_MAP.mobile,
+            vendor: 'Microsoft',
+          };
+        },
+      },
+
+      /* Android Tablet */
+      {
+        test(parser) {
+          const osMajorVersion = Number(String(parser.getOSVersion()).split('.')[0]);
+          return parser.getOSName(true) === 'android' && (osMajorVersion >= 3);
+        },
+        describe() {
+          return {
+            type: PLATFORMS_MAP.tablet,
+          };
+        },
+      },
+
+      /* Android Mobile */
+      {
+        test(parser) {
+          return parser.getOSName(true) === 'android';
+        },
+        describe() {
+          return {
+            type: PLATFORMS_MAP.mobile,
+          };
+        },
+      },
+
+      /* desktop */
+      {
+        test(parser) {
+          return parser.getOSName(true) === 'macos';
+        },
+        describe() {
+          return {
+            type: PLATFORMS_MAP.desktop,
+            vendor: 'Apple',
+          };
+        },
+      },
+
+      /* Windows */
+      {
+        test(parser) {
+          return parser.getOSName(true) === 'windows';
+        },
+        describe() {
+          return {
+            type: PLATFORMS_MAP.desktop,
+          };
+        },
+      },
+
+      /* Linux */
+      {
+        test(parser) {
+          return parser.getOSName(true) === 'linux';
+        },
+        describe() {
+          return {
+            type: PLATFORMS_MAP.desktop,
+          };
+        },
+      },
+
+      /* PlayStation 4 */
+      {
+        test(parser) {
+          return parser.getOSName(true) === 'playstation 4';
+        },
+        describe() {
+          return {
+            type: PLATFORMS_MAP.tv,
+          };
+        },
+      },
+
+      /* Roku */
+      {
+        test(parser) {
+          return parser.getOSName(true) === 'roku';
+        },
+        describe() {
+          return {
+            type: PLATFORMS_MAP.tv,
+          };
+        },
+      },
+    ];
+
+    /*
+     * More specific goes first
+     */
+    var enginesParsersList = [
+      /* EdgeHTML */
+      {
+        test(parser) {
+          return parser.getBrowserName(true) === 'microsoft edge';
+        },
+        describe(ua) {
+          const isBlinkBased = /\sedg\//i.test(ua);
+
+          // return blink if it's blink-based one
+          if (isBlinkBased) {
+            return {
+              name: ENGINE_MAP.Blink,
+            };
+          }
+
+          // otherwise match the version and return EdgeHTML
+          const version = Utils.getFirstMatch(/edge\/(\d+(\.?_?\d+)+)/i, ua);
+
+          return {
+            name: ENGINE_MAP.EdgeHTML,
+            version,
+          };
+        },
+      },
+
+      /* Trident */
+      {
+        test: [/trident/i],
+        describe(ua) {
+          const engine = {
+            name: ENGINE_MAP.Trident,
+          };
+
+          const version = Utils.getFirstMatch(/trident\/(\d+(\.?_?\d+)+)/i, ua);
+
+          if (version) {
+            engine.version = version;
+          }
+
+          return engine;
+        },
+      },
+
+      /* Presto */
+      {
+        test(parser) {
+          return parser.test(/presto/i);
+        },
+        describe(ua) {
+          const engine = {
+            name: ENGINE_MAP.Presto,
+          };
+
+          const version = Utils.getFirstMatch(/presto\/(\d+(\.?_?\d+)+)/i, ua);
+
+          if (version) {
+            engine.version = version;
+          }
+
+          return engine;
+        },
+      },
+
+      /* Gecko */
+      {
+        test(parser) {
+          const isGecko = parser.test(/gecko/i);
+          const likeGecko = parser.test(/like gecko/i);
+          return isGecko && !likeGecko;
+        },
+        describe(ua) {
+          const engine = {
+            name: ENGINE_MAP.Gecko,
+          };
+
+          const version = Utils.getFirstMatch(/gecko\/(\d+(\.?_?\d+)+)/i, ua);
+
+          if (version) {
+            engine.version = version;
+          }
+
+          return engine;
+        },
+      },
+
+      /* Blink */
+      {
+        test: [/(apple)?webkit\/537\.36/i],
+        describe() {
+          return {
+            name: ENGINE_MAP.Blink,
+          };
+        },
+      },
+
+      /* WebKit */
+      {
+        test: [/(apple)?webkit/i],
+        describe(ua) {
+          const engine = {
+            name: ENGINE_MAP.WebKit,
+          };
+
+          const version = Utils.getFirstMatch(/webkit\/(\d+(\.?_?\d+)+)/i, ua);
+
+          if (version) {
+            engine.version = version;
+          }
+
+          return engine;
+        },
+      },
+    ];
+
+    /**
+     * The main class that arranges the whole parsing process.
+     */
+    class Parser {
+      /**
+       * Create instance of Parser
+       *
+       * @param {String} UA User-Agent string
+       * @param {Boolean} [skipParsing=false] parser can skip parsing in purpose of performance
+       * improvements if you need to make a more particular parsing
+       * like {@link Parser#parseBrowser} or {@link Parser#parsePlatform}
+       *
+       * @throw {Error} in case of empty UA String
+       *
+       * @constructor
+       */
+      constructor(UA, skipParsing = false) {
+        if (UA === void (0) || UA === null || UA === '') {
+          throw new Error("UserAgent parameter can't be empty");
+        }
+
+        this._ua = UA;
+
+        /**
+         * @typedef ParsedResult
+         * @property {Object} browser
+         * @property {String|undefined} [browser.name]
+         * Browser name, like `"Chrome"` or `"Internet Explorer"`
+         * @property {String|undefined} [browser.version] Browser version as a String `"12.01.45334.10"`
+         * @property {Object} os
+         * @property {String|undefined} [os.name] OS name, like `"Windows"` or `"macOS"`
+         * @property {String|undefined} [os.version] OS version, like `"NT 5.1"` or `"10.11.1"`
+         * @property {String|undefined} [os.versionName] OS name, like `"XP"` or `"High Sierra"`
+         * @property {Object} platform
+         * @property {String|undefined} [platform.type]
+         * platform type, can be either `"desktop"`, `"tablet"` or `"mobile"`
+         * @property {String|undefined} [platform.vendor] Vendor of the device,
+         * like `"Apple"` or `"Samsung"`
+         * @property {String|undefined} [platform.model] Device model,
+         * like `"iPhone"` or `"Kindle Fire HD 7"`
+         * @property {Object} engine
+         * @property {String|undefined} [engine.name]
+         * Can be any of this: `WebKit`, `Blink`, `Gecko`, `Trident`, `Presto`, `EdgeHTML`
+         * @property {String|undefined} [engine.version] String version of the engine
+         */
+        this.parsedResult = {};
+
+        if (skipParsing !== true) {
+          this.parse();
+        }
+      }
+
+      /**
+       * Get UserAgent string of current Parser instance
+       * @return {String} User-Agent String of the current <Parser> object
+       *
+       * @public
+       */
+      getUA() {
+        return this._ua;
+      }
+
+      /**
+       * Test a UA string for a regexp
+       * @param {RegExp} regex
+       * @return {Boolean}
+       */
+      test(regex) {
+        return regex.test(this._ua);
+      }
+
+      /**
+       * Get parsed browser object
+       * @return {Object}
+       */
+      parseBrowser() {
+        this.parsedResult.browser = {};
+
+        const browserDescriptor = Utils.find(browsersList, (_browser) => {
+          if (typeof _browser.test === 'function') {
+            return _browser.test(this);
+          }
+
+          if (_browser.test instanceof Array) {
+            return _browser.test.some(condition => this.test(condition));
+          }
+
+          throw new Error("Browser's test function is not valid");
+        });
+
+        if (browserDescriptor) {
+          this.parsedResult.browser = browserDescriptor.describe(this.getUA());
+        }
+
+        return this.parsedResult.browser;
+      }
+
+      /**
+       * Get parsed browser object
+       * @return {Object}
+       *
+       * @public
+       */
+      getBrowser() {
+        if (this.parsedResult.browser) {
+          return this.parsedResult.browser;
+        }
+
+        return this.parseBrowser();
+      }
+
+      /**
+       * Get browser's name
+       * @return {String} Browser's name or an empty string
+       *
+       * @public
+       */
+      getBrowserName(toLowerCase) {
+        if (toLowerCase) {
+          return String(this.getBrowser().name).toLowerCase() || '';
+        }
+        return this.getBrowser().name || '';
+      }
+
+
+      /**
+       * Get browser's version
+       * @return {String} version of browser
+       *
+       * @public
+       */
+      getBrowserVersion() {
+        return this.getBrowser().version;
+      }
+
+      /**
+       * Get OS
+       * @return {Object}
+       *
+       * @example
+       * this.getOS();
+       * {
+       *   name: 'macOS',
+       *   version: '10.11.12'
+       * }
+       */
+      getOS() {
+        if (this.parsedResult.os) {
+          return this.parsedResult.os;
+        }
+
+        return this.parseOS();
+      }
+
+      /**
+       * Parse OS and save it to this.parsedResult.os
+       * @return {*|{}}
+       */
+      parseOS() {
+        this.parsedResult.os = {};
+
+        const os = Utils.find(osParsersList, (_os) => {
+          if (typeof _os.test === 'function') {
+            return _os.test(this);
+          }
+
+          if (_os.test instanceof Array) {
+            return _os.test.some(condition => this.test(condition));
+          }
+
+          throw new Error("Browser's test function is not valid");
+        });
+
+        if (os) {
+          this.parsedResult.os = os.describe(this.getUA());
+        }
+
+        return this.parsedResult.os;
+      }
+
+      /**
+       * Get OS name
+       * @param {Boolean} [toLowerCase] return lower-cased value
+       * @return {String} name of the OS — macOS, Windows, Linux, etc.
+       */
+      getOSName(toLowerCase) {
+        const { name } = this.getOS();
+
+        if (toLowerCase) {
+          return String(name).toLowerCase() || '';
+        }
+
+        return name || '';
+      }
+
+      /**
+       * Get OS version
+       * @return {String} full version with dots ('10.11.12', '5.6', etc)
+       */
+      getOSVersion() {
+        return this.getOS().version;
+      }
+
+      /**
+       * Get parsed platform
+       * @return {{}}
+       */
+      getPlatform() {
+        if (this.parsedResult.platform) {
+          return this.parsedResult.platform;
+        }
+
+        return this.parsePlatform();
+      }
+
+      /**
+       * Get platform name
+       * @param {Boolean} [toLowerCase=false]
+       * @return {*}
+       */
+      getPlatformType(toLowerCase = false) {
+        const { type } = this.getPlatform();
+
+        if (toLowerCase) {
+          return String(type).toLowerCase() || '';
+        }
+
+        return type || '';
+      }
+
+      /**
+       * Get parsed platform
+       * @return {{}}
+       */
+      parsePlatform() {
+        this.parsedResult.platform = {};
+
+        const platform = Utils.find(platformParsersList, (_platform) => {
+          if (typeof _platform.test === 'function') {
+            return _platform.test(this);
+          }
+
+          if (_platform.test instanceof Array) {
+            return _platform.test.some(condition => this.test(condition));
+          }
+
+          throw new Error("Browser's test function is not valid");
+        });
+
+        if (platform) {
+          this.parsedResult.platform = platform.describe(this.getUA());
+        }
+
+        return this.parsedResult.platform;
+      }
+
+      /**
+       * Get parsed engine
+       * @return {{}}
+       */
+      getEngine() {
+        if (this.parsedResult.engine) {
+          return this.parsedResult.engine;
+        }
+
+        return this.parseEngine();
+      }
+
+      /**
+       * Get engines's name
+       * @return {String} Engines's name or an empty string
+       *
+       * @public
+       */
+      getEngineName(toLowerCase) {
+        if (toLowerCase) {
+          return String(this.getEngine().name).toLowerCase() || '';
+        }
+        return this.getEngine().name || '';
+      }
+
+      /**
+       * Get parsed platform
+       * @return {{}}
+       */
+      parseEngine() {
+        this.parsedResult.engine = {};
+
+        const engine = Utils.find(enginesParsersList, (_engine) => {
+          if (typeof _engine.test === 'function') {
+            return _engine.test(this);
+          }
+
+          if (_engine.test instanceof Array) {
+            return _engine.test.some(condition => this.test(condition));
+          }
+
+          throw new Error("Browser's test function is not valid");
+        });
+
+        if (engine) {
+          this.parsedResult.engine = engine.describe(this.getUA());
+        }
+
+        return this.parsedResult.engine;
+      }
+
+      /**
+       * Parse full information about the browser
+       */
+      parse() {
+        this.parseBrowser();
+        this.parseOS();
+        this.parsePlatform();
+        this.parseEngine();
+
+        return this;
+      }
+
+      /**
+       * Get parsed result
+       * @return {ParsedResult}
+       */
+      getResult() {
+        return Utils.assign({}, this.parsedResult);
+      }
+
+      /**
+       * Check if parsed browser matches certain conditions
+       *
+       * @param {Object} checkTree It's one or two layered object,
+       * which can include a platform or an OS on the first layer
+       * and should have browsers specs on the bottom-laying layer
+       *
+       * @returns {Boolean|undefined} Whether the browser satisfies the set conditions or not.
+       * Returns `undefined` when the browser is no described in the checkTree object.
+       *
+       * @example
+       * const browser = Bowser.getParser(window.navigator.userAgent);
+       * if (browser.satisfies({chrome: '>118.01.1322' }))
+       * // or with os
+       * if (browser.satisfies({windows: { chrome: '>118.01.1322' } }))
+       * // or with platforms
+       * if (browser.satisfies({desktop: { chrome: '>118.01.1322' } }))
+       */
+      satisfies(checkTree) {
+        const platformsAndOSes = {};
+        let platformsAndOSCounter = 0;
+        const browsers = {};
+        let browsersCounter = 0;
+
+        const allDefinitions = Object.keys(checkTree);
+
+        allDefinitions.forEach((key) => {
+          const currentDefinition = checkTree[key];
+          if (typeof currentDefinition === 'string') {
+            browsers[key] = currentDefinition;
+            browsersCounter += 1;
+          } else if (typeof currentDefinition === 'object') {
+            platformsAndOSes[key] = currentDefinition;
+            platformsAndOSCounter += 1;
+          }
+        });
+
+        if (platformsAndOSCounter > 0) {
+          const platformsAndOSNames = Object.keys(platformsAndOSes);
+          const OSMatchingDefinition = Utils.find(platformsAndOSNames, name => (this.isOS(name)));
+
+          if (OSMatchingDefinition) {
+            const osResult = this.satisfies(platformsAndOSes[OSMatchingDefinition]);
+
+            if (osResult !== void 0) {
+              return osResult;
+            }
+          }
+
+          const platformMatchingDefinition = Utils.find(
+            platformsAndOSNames,
+            name => (this.isPlatform(name)),
+          );
+          if (platformMatchingDefinition) {
+            const platformResult = this.satisfies(platformsAndOSes[platformMatchingDefinition]);
+
+            if (platformResult !== void 0) {
+              return platformResult;
+            }
+          }
+        }
+
+        if (browsersCounter > 0) {
+          const browserNames = Object.keys(browsers);
+          const matchingDefinition = Utils.find(browserNames, name => (this.isBrowser(name, true)));
+
+          if (matchingDefinition !== void 0) {
+            return this.compareVersion(browsers[matchingDefinition]);
+          }
+        }
+
+        return undefined;
+      }
+
+      /**
+       * Check if the browser name equals the passed string
+       * @param browserName The string to compare with the browser name
+       * @param [includingAlias=false] The flag showing whether alias will be included into comparison
+       * @returns {boolean}
+       */
+      isBrowser(browserName, includingAlias = false) {
+        const defaultBrowserName = this.getBrowserName().toLowerCase();
+        let browserNameLower = browserName.toLowerCase();
+        const alias = Utils.getBrowserTypeByAlias(browserNameLower);
+
+        if (includingAlias && alias) {
+          browserNameLower = alias.toLowerCase();
+        }
+        return browserNameLower === defaultBrowserName;
+      }
+
+      compareVersion(version) {
+        let expectedResults = [0];
+        let comparableVersion = version;
+        let isLoose = false;
+
+        const currentBrowserVersion = this.getBrowserVersion();
+
+        if (typeof currentBrowserVersion !== 'string') {
+          return void 0;
+        }
+
+        if (version[0] === '>' || version[0] === '<') {
+          comparableVersion = version.substr(1);
+          if (version[1] === '=') {
+            isLoose = true;
+            comparableVersion = version.substr(2);
+          } else {
+            expectedResults = [];
+          }
+          if (version[0] === '>') {
+            expectedResults.push(1);
+          } else {
+            expectedResults.push(-1);
+          }
+        } else if (version[0] === '=') {
+          comparableVersion = version.substr(1);
+        } else if (version[0] === '~') {
+          isLoose = true;
+          comparableVersion = version.substr(1);
+        }
+
+        return expectedResults.indexOf(
+          Utils.compareVersions(currentBrowserVersion, comparableVersion, isLoose),
+        ) > -1;
+      }
+
+      isOS(osName) {
+        return this.getOSName(true) === String(osName).toLowerCase();
+      }
+
+      isPlatform(platformType) {
+        return this.getPlatformType(true) === String(platformType).toLowerCase();
+      }
+
+      isEngine(engineName) {
+        return this.getEngineName(true) === String(engineName).toLowerCase();
+      }
+
+      /**
+       * Is anything? Check if the browser is called "anything",
+       * the OS called "anything" or the platform called "anything"
+       * @param {String} anything
+       * @returns {Boolean}
+       */
+      is(anything) {
+        return this.isBrowser(anything) || this.isOS(anything) || this.isPlatform(anything);
+      }
+
+      /**
+       * Check if any of the given values satisfies this.is(anything)
+       * @param {String[]} anythings
+       * @returns {Boolean}
+       */
+      some(anythings = []) {
+        return anythings.some(anything => this.is(anything));
+      }
+    }
+
+    /*!
+     * Bowser - a browser detector
+     * https://github.com/lancedikson/bowser
+     * MIT License | (c) Dustin Diaz 2012-2015
+     * MIT License | (c) Denis Demchenko 2015-2019
+     */
+
+    /**
+     * Bowser class.
+     * Keep it simple as much as it can be.
+     * It's supposed to work with collections of {@link Parser} instances
+     * rather then solve one-instance problems.
+     * All the one-instance stuff is located in Parser class.
+     *
+     * @class
+     * @classdesc Bowser is a static object, that provides an API to the Parsers
+     * @hideconstructor
+     */
+    class Bowser {
+      /**
+       * Creates a {@link Parser} instance
+       *
+       * @param {String} UA UserAgent string
+       * @param {Boolean} [skipParsing=false] Will make the Parser postpone parsing until you ask it
+       * explicitly. Same as `skipParsing` for {@link Parser}.
+       * @returns {Parser}
+       * @throws {Error} when UA is not a String
+       *
+       * @example
+       * const parser = Bowser.getParser(window.navigator.userAgent);
+       * const result = parser.getResult();
+       */
+      static getParser(UA, skipParsing = false) {
+        if (typeof UA !== 'string') {
+          throw new Error('UserAgent should be a string');
+        }
+        return new Parser(UA, skipParsing);
+      }
+
+      /**
+       * Creates a {@link Parser} instance and runs {@link Parser.getResult} immediately
+       *
+       * @param UA
+       * @return {ParsedResult}
+       *
+       * @example
+       * const result = Bowser.parse(window.navigator.userAgent);
+       */
+      static parse(UA) {
+        return (new Parser(UA)).getResult();
+      }
+
+      static get BROWSER_MAP() {
+        return BROWSER_MAP;
+      }
+
+      static get ENGINE_MAP() {
+        return ENGINE_MAP;
+      }
+
+      static get OS_MAP() {
+        return OS_MAP;
+      }
+
+      static get PLATFORMS_MAP() {
+        return PLATFORMS_MAP;
+      }
+    }
+
+    var ONBOARDING_STATE = {
+        INSTALLED: 'INSTALLED',
+        NOT_INSTALLED: 'NOT_INSTALLED',
+        REGISTERED: 'REGISTERED',
+        REGISTERING: 'REGISTERING',
+        RELOADING: 'RELOADING',
+    };
+    var EXTENSION_DOWNLOAD_URL = {
+        CHROME: 'https://chrome.google.com/webstore/detail/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn',
+        FIREFOX: 'https://addons.mozilla.org/firefox/addon/ether-metamask/',
+        DEFAULT: 'https://metamask.io',
+    };
+    // sessionStorage key
+    var REGISTRATION_IN_PROGRESS = 'REGISTRATION_IN_PROGRESS';
+    // forwarder iframe id
+    var FORWARDER_ID = 'FORWARDER_ID';
+    var Onboarding = /** @class */ (function () {
+        function Onboarding(_a) {
+            var _b = _a === void 0 ? {} : _a, _c = _b.forwarderOrigin, forwarderOrigin = _c === void 0 ? 'https://fwd.metamask.io' : _c, _d = _b.forwarderMode, forwarderMode = _d === void 0 ? Onboarding.FORWARDER_MODE.INJECT : _d;
+            this.forwarderOrigin = forwarderOrigin;
+            this.forwarderMode = forwarderMode;
+            this.state = Onboarding.isMetaMaskInstalled()
+                ? ONBOARDING_STATE.INSTALLED
+                : ONBOARDING_STATE.NOT_INSTALLED;
+            var browser = Onboarding._detectBrowser();
+            if (browser) {
+                this.downloadUrl = EXTENSION_DOWNLOAD_URL[browser];
+            }
+            else {
+                this.downloadUrl = EXTENSION_DOWNLOAD_URL.DEFAULT;
+            }
+            this._onMessage = this._onMessage.bind(this);
+            this._onMessageFromForwarder = this._onMessageFromForwarder.bind(this);
+            this._openForwarder = this._openForwarder.bind(this);
+            this._openDownloadPage = this._openDownloadPage.bind(this);
+            this.startOnboarding = this.startOnboarding.bind(this);
+            this.stopOnboarding = this.stopOnboarding.bind(this);
+            window.addEventListener('message', this._onMessage);
+            if (forwarderMode === Onboarding.FORWARDER_MODE.INJECT &&
+                sessionStorage.getItem(REGISTRATION_IN_PROGRESS) === 'true') {
+                Onboarding._injectForwarder(this.forwarderOrigin);
+            }
+        }
+        Onboarding.prototype._onMessage = function (event) {
+            if (event.origin !== this.forwarderOrigin) {
+                // Ignoring non-forwarder message
+                return undefined;
+            }
+            if (event.data.type === 'metamask:reload') {
+                return this._onMessageFromForwarder(event);
+            }
+            console.debug("Unknown message from '" + event.origin + "' with data " + JSON.stringify(event.data));
+            return undefined;
+        };
+        Onboarding.prototype._onMessageUnknownStateError = function (state) {
+            throw new Error("Unknown state: '" + state + "'");
+        };
+        Onboarding.prototype._onMessageFromForwarder = function (event) {
+            return __awaiter(this, void 0, void 0, function () {
+                var _a;
+                return __generator(this, function (_b) {
+                    switch (_b.label) {
+                        case 0:
+                            _a = this.state;
+                            switch (_a) {
+                                case ONBOARDING_STATE.RELOADING: return [3 /*break*/, 1];
+                                case ONBOARDING_STATE.NOT_INSTALLED: return [3 /*break*/, 2];
+                                case ONBOARDING_STATE.INSTALLED: return [3 /*break*/, 3];
+                                case ONBOARDING_STATE.REGISTERING: return [3 /*break*/, 5];
+                                case ONBOARDING_STATE.REGISTERED: return [3 /*break*/, 6];
+                            }
+                            return [3 /*break*/, 7];
+                        case 1:
+                            console.debug('Ignoring message while reloading');
+                            return [3 /*break*/, 8];
+                        case 2:
+                            console.debug('Reloading now to register with MetaMask');
+                            this.state = ONBOARDING_STATE.RELOADING;
+                            location.reload();
+                            return [3 /*break*/, 8];
+                        case 3:
+                            console.debug('Registering with MetaMask');
+                            this.state = ONBOARDING_STATE.REGISTERING;
+                            return [4 /*yield*/, Onboarding._register()];
+                        case 4:
+                            _b.sent();
+                            this.state = ONBOARDING_STATE.REGISTERED;
+                            event.source.postMessage({ type: 'metamask:registrationCompleted' }, event.origin);
+                            this.stopOnboarding();
+                            return [3 /*break*/, 8];
+                        case 5:
+                            console.debug('Already registering - ignoring reload message');
+                            return [3 /*break*/, 8];
+                        case 6:
+                            console.debug('Already registered - ignoring reload message');
+                            return [3 /*break*/, 8];
+                        case 7:
+                            this._onMessageUnknownStateError(this.state);
+                            _b.label = 8;
+                        case 8: return [2 /*return*/];
+                    }
+                });
+            });
+        };
+        /**
+         * Starts onboarding by opening the MetaMask download page and the Onboarding forwarder
+         */
+        Onboarding.prototype.startOnboarding = function () {
+            sessionStorage.setItem(REGISTRATION_IN_PROGRESS, 'true');
+            this._openDownloadPage();
+            this._openForwarder();
+        };
+        /**
+         * Stops onboarding registration, including removing the injected forwarder (if any)
+         *
+         * Typically this function is not necessary, but it can be useful for cases where
+         * onboarding completes before the forwarder has registered.
+         */
+        Onboarding.prototype.stopOnboarding = function () {
+            if (sessionStorage.getItem(REGISTRATION_IN_PROGRESS) === 'true') {
+                if (this.forwarderMode === Onboarding.FORWARDER_MODE.INJECT) {
+                    console.debug('Removing forwarder');
+                    Onboarding._removeForwarder();
+                }
+                sessionStorage.setItem(REGISTRATION_IN_PROGRESS, 'false');
+            }
+        };
+        Onboarding.prototype._openForwarder = function () {
+            if (this.forwarderMode === Onboarding.FORWARDER_MODE.OPEN_TAB) {
+                window.open(this.forwarderOrigin, '_blank');
+            }
+            else {
+                Onboarding._injectForwarder(this.forwarderOrigin);
+            }
+        };
+        Onboarding.prototype._openDownloadPage = function () {
+            window.open(this.downloadUrl, '_blank');
+        };
+        /**
+         * Checks whether the MetaMask extension is installed
+         */
+        Onboarding.isMetaMaskInstalled = function () {
+            return Boolean(window.ethereum && window.ethereum.isMetaMask);
+        };
+        Onboarding._register = function () {
+            return window.ethereum.request({
+                method: 'wallet_registerOnboarding',
+            });
+        };
+        Onboarding._injectForwarder = function (forwarderOrigin) {
+            var container = document.body;
+            var iframe = document.createElement('iframe');
+            iframe.setAttribute('height', '0');
+            iframe.setAttribute('width', '0');
+            iframe.setAttribute('style', 'display: none;');
+            iframe.setAttribute('src', forwarderOrigin);
+            iframe.setAttribute('id', FORWARDER_ID);
+            container.insertBefore(iframe, container.children[0]);
+        };
+        Onboarding._removeForwarder = function () {
+            var _a;
+            (_a = document.getElementById(FORWARDER_ID)) === null || _a === void 0 ? void 0 : _a.remove();
+        };
+        Onboarding._detectBrowser = function () {
+            var browserInfo = Bowser.parse(window.navigator.userAgent);
+            if (browserInfo.browser.name === 'Firefox') {
+                return 'FIREFOX';
+            }
+            else if (['Chrome', 'Chromium'].includes(browserInfo.browser.name || '')) {
+                return 'CHROME';
+            }
+            return null;
+        };
+        Onboarding.FORWARDER_MODE = {
+            INJECT: 'INJECT',
+            OPEN_TAB: 'OPEN_TAB',
+        };
+        return Onboarding;
+    }());
+
+    return Onboarding;
+
+}());

+ 240 - 0
node_modules/@metamask/onboarding/dist/metamask-onboarding.cjs.js

@@ -0,0 +1,240 @@
+'use strict';
+
+function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
+
+var Bowser = _interopDefault(require('bowser'));
+
+/*! *****************************************************************************
+Copyright (c) Microsoft Corporation.
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+PERFORMANCE OF THIS SOFTWARE.
+***************************************************************************** */
+
+function __awaiter(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());
+    });
+}
+
+function __generator(thisArg, body) {
+    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
+    return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
+    function verb(n) { return function (v) { return step([n, v]); }; }
+    function step(op) {
+        if (f) throw new TypeError("Generator is already executing.");
+        while (_) try {
+            if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
+            if (y = 0, t) op = [op[0] & 2, t.value];
+            switch (op[0]) {
+                case 0: case 1: t = op; break;
+                case 4: _.label++; return { value: op[1], done: false };
+                case 5: _.label++; y = op[1]; op = [0]; continue;
+                case 7: op = _.ops.pop(); _.trys.pop(); continue;
+                default:
+                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
+                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
+                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
+                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
+                    if (t[2]) _.ops.pop();
+                    _.trys.pop(); continue;
+            }
+            op = body.call(thisArg, _);
+        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
+        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
+    }
+}
+
+var ONBOARDING_STATE = {
+    INSTALLED: 'INSTALLED',
+    NOT_INSTALLED: 'NOT_INSTALLED',
+    REGISTERED: 'REGISTERED',
+    REGISTERING: 'REGISTERING',
+    RELOADING: 'RELOADING',
+};
+var EXTENSION_DOWNLOAD_URL = {
+    CHROME: 'https://chrome.google.com/webstore/detail/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn',
+    FIREFOX: 'https://addons.mozilla.org/firefox/addon/ether-metamask/',
+    DEFAULT: 'https://metamask.io',
+};
+// sessionStorage key
+var REGISTRATION_IN_PROGRESS = 'REGISTRATION_IN_PROGRESS';
+// forwarder iframe id
+var FORWARDER_ID = 'FORWARDER_ID';
+var Onboarding = /** @class */ (function () {
+    function Onboarding(_a) {
+        var _b = _a === void 0 ? {} : _a, _c = _b.forwarderOrigin, forwarderOrigin = _c === void 0 ? 'https://fwd.metamask.io' : _c, _d = _b.forwarderMode, forwarderMode = _d === void 0 ? Onboarding.FORWARDER_MODE.INJECT : _d;
+        this.forwarderOrigin = forwarderOrigin;
+        this.forwarderMode = forwarderMode;
+        this.state = Onboarding.isMetaMaskInstalled()
+            ? ONBOARDING_STATE.INSTALLED
+            : ONBOARDING_STATE.NOT_INSTALLED;
+        var browser = Onboarding._detectBrowser();
+        if (browser) {
+            this.downloadUrl = EXTENSION_DOWNLOAD_URL[browser];
+        }
+        else {
+            this.downloadUrl = EXTENSION_DOWNLOAD_URL.DEFAULT;
+        }
+        this._onMessage = this._onMessage.bind(this);
+        this._onMessageFromForwarder = this._onMessageFromForwarder.bind(this);
+        this._openForwarder = this._openForwarder.bind(this);
+        this._openDownloadPage = this._openDownloadPage.bind(this);
+        this.startOnboarding = this.startOnboarding.bind(this);
+        this.stopOnboarding = this.stopOnboarding.bind(this);
+        window.addEventListener('message', this._onMessage);
+        if (forwarderMode === Onboarding.FORWARDER_MODE.INJECT &&
+            sessionStorage.getItem(REGISTRATION_IN_PROGRESS) === 'true') {
+            Onboarding._injectForwarder(this.forwarderOrigin);
+        }
+    }
+    Onboarding.prototype._onMessage = function (event) {
+        if (event.origin !== this.forwarderOrigin) {
+            // Ignoring non-forwarder message
+            return undefined;
+        }
+        if (event.data.type === 'metamask:reload') {
+            return this._onMessageFromForwarder(event);
+        }
+        console.debug("Unknown message from '" + event.origin + "' with data " + JSON.stringify(event.data));
+        return undefined;
+    };
+    Onboarding.prototype._onMessageUnknownStateError = function (state) {
+        throw new Error("Unknown state: '" + state + "'");
+    };
+    Onboarding.prototype._onMessageFromForwarder = function (event) {
+        return __awaiter(this, void 0, void 0, function () {
+            var _a;
+            return __generator(this, function (_b) {
+                switch (_b.label) {
+                    case 0:
+                        _a = this.state;
+                        switch (_a) {
+                            case ONBOARDING_STATE.RELOADING: return [3 /*break*/, 1];
+                            case ONBOARDING_STATE.NOT_INSTALLED: return [3 /*break*/, 2];
+                            case ONBOARDING_STATE.INSTALLED: return [3 /*break*/, 3];
+                            case ONBOARDING_STATE.REGISTERING: return [3 /*break*/, 5];
+                            case ONBOARDING_STATE.REGISTERED: return [3 /*break*/, 6];
+                        }
+                        return [3 /*break*/, 7];
+                    case 1:
+                        console.debug('Ignoring message while reloading');
+                        return [3 /*break*/, 8];
+                    case 2:
+                        console.debug('Reloading now to register with MetaMask');
+                        this.state = ONBOARDING_STATE.RELOADING;
+                        location.reload();
+                        return [3 /*break*/, 8];
+                    case 3:
+                        console.debug('Registering with MetaMask');
+                        this.state = ONBOARDING_STATE.REGISTERING;
+                        return [4 /*yield*/, Onboarding._register()];
+                    case 4:
+                        _b.sent();
+                        this.state = ONBOARDING_STATE.REGISTERED;
+                        event.source.postMessage({ type: 'metamask:registrationCompleted' }, event.origin);
+                        this.stopOnboarding();
+                        return [3 /*break*/, 8];
+                    case 5:
+                        console.debug('Already registering - ignoring reload message');
+                        return [3 /*break*/, 8];
+                    case 6:
+                        console.debug('Already registered - ignoring reload message');
+                        return [3 /*break*/, 8];
+                    case 7:
+                        this._onMessageUnknownStateError(this.state);
+                        _b.label = 8;
+                    case 8: return [2 /*return*/];
+                }
+            });
+        });
+    };
+    /**
+     * Starts onboarding by opening the MetaMask download page and the Onboarding forwarder
+     */
+    Onboarding.prototype.startOnboarding = function () {
+        sessionStorage.setItem(REGISTRATION_IN_PROGRESS, 'true');
+        this._openDownloadPage();
+        this._openForwarder();
+    };
+    /**
+     * Stops onboarding registration, including removing the injected forwarder (if any)
+     *
+     * Typically this function is not necessary, but it can be useful for cases where
+     * onboarding completes before the forwarder has registered.
+     */
+    Onboarding.prototype.stopOnboarding = function () {
+        if (sessionStorage.getItem(REGISTRATION_IN_PROGRESS) === 'true') {
+            if (this.forwarderMode === Onboarding.FORWARDER_MODE.INJECT) {
+                console.debug('Removing forwarder');
+                Onboarding._removeForwarder();
+            }
+            sessionStorage.setItem(REGISTRATION_IN_PROGRESS, 'false');
+        }
+    };
+    Onboarding.prototype._openForwarder = function () {
+        if (this.forwarderMode === Onboarding.FORWARDER_MODE.OPEN_TAB) {
+            window.open(this.forwarderOrigin, '_blank');
+        }
+        else {
+            Onboarding._injectForwarder(this.forwarderOrigin);
+        }
+    };
+    Onboarding.prototype._openDownloadPage = function () {
+        window.open(this.downloadUrl, '_blank');
+    };
+    /**
+     * Checks whether the MetaMask extension is installed
+     */
+    Onboarding.isMetaMaskInstalled = function () {
+        return Boolean(window.ethereum && window.ethereum.isMetaMask);
+    };
+    Onboarding._register = function () {
+        return window.ethereum.request({
+            method: 'wallet_registerOnboarding',
+        });
+    };
+    Onboarding._injectForwarder = function (forwarderOrigin) {
+        var container = document.body;
+        var iframe = document.createElement('iframe');
+        iframe.setAttribute('height', '0');
+        iframe.setAttribute('width', '0');
+        iframe.setAttribute('style', 'display: none;');
+        iframe.setAttribute('src', forwarderOrigin);
+        iframe.setAttribute('id', FORWARDER_ID);
+        container.insertBefore(iframe, container.children[0]);
+    };
+    Onboarding._removeForwarder = function () {
+        var _a;
+        (_a = document.getElementById(FORWARDER_ID)) === null || _a === void 0 ? void 0 : _a.remove();
+    };
+    Onboarding._detectBrowser = function () {
+        var browserInfo = Bowser.parse(window.navigator.userAgent);
+        if (browserInfo.browser.name === 'Firefox') {
+            return 'FIREFOX';
+        }
+        else if (['Chrome', 'Chromium'].includes(browserInfo.browser.name || '')) {
+            return 'CHROME';
+        }
+        return null;
+    };
+    Onboarding.FORWARDER_MODE = {
+        INJECT: 'INJECT',
+        OPEN_TAB: 'OPEN_TAB',
+    };
+    return Onboarding;
+}());
+
+module.exports = Onboarding;

+ 236 - 0
node_modules/@metamask/onboarding/dist/metamask-onboarding.es.js

@@ -0,0 +1,236 @@
+import Bowser from 'bowser';
+
+/*! *****************************************************************************
+Copyright (c) Microsoft Corporation.
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+PERFORMANCE OF THIS SOFTWARE.
+***************************************************************************** */
+
+function __awaiter(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());
+    });
+}
+
+function __generator(thisArg, body) {
+    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
+    return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
+    function verb(n) { return function (v) { return step([n, v]); }; }
+    function step(op) {
+        if (f) throw new TypeError("Generator is already executing.");
+        while (_) try {
+            if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
+            if (y = 0, t) op = [op[0] & 2, t.value];
+            switch (op[0]) {
+                case 0: case 1: t = op; break;
+                case 4: _.label++; return { value: op[1], done: false };
+                case 5: _.label++; y = op[1]; op = [0]; continue;
+                case 7: op = _.ops.pop(); _.trys.pop(); continue;
+                default:
+                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
+                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
+                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
+                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
+                    if (t[2]) _.ops.pop();
+                    _.trys.pop(); continue;
+            }
+            op = body.call(thisArg, _);
+        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
+        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
+    }
+}
+
+var ONBOARDING_STATE = {
+    INSTALLED: 'INSTALLED',
+    NOT_INSTALLED: 'NOT_INSTALLED',
+    REGISTERED: 'REGISTERED',
+    REGISTERING: 'REGISTERING',
+    RELOADING: 'RELOADING',
+};
+var EXTENSION_DOWNLOAD_URL = {
+    CHROME: 'https://chrome.google.com/webstore/detail/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn',
+    FIREFOX: 'https://addons.mozilla.org/firefox/addon/ether-metamask/',
+    DEFAULT: 'https://metamask.io',
+};
+// sessionStorage key
+var REGISTRATION_IN_PROGRESS = 'REGISTRATION_IN_PROGRESS';
+// forwarder iframe id
+var FORWARDER_ID = 'FORWARDER_ID';
+var Onboarding = /** @class */ (function () {
+    function Onboarding(_a) {
+        var _b = _a === void 0 ? {} : _a, _c = _b.forwarderOrigin, forwarderOrigin = _c === void 0 ? 'https://fwd.metamask.io' : _c, _d = _b.forwarderMode, forwarderMode = _d === void 0 ? Onboarding.FORWARDER_MODE.INJECT : _d;
+        this.forwarderOrigin = forwarderOrigin;
+        this.forwarderMode = forwarderMode;
+        this.state = Onboarding.isMetaMaskInstalled()
+            ? ONBOARDING_STATE.INSTALLED
+            : ONBOARDING_STATE.NOT_INSTALLED;
+        var browser = Onboarding._detectBrowser();
+        if (browser) {
+            this.downloadUrl = EXTENSION_DOWNLOAD_URL[browser];
+        }
+        else {
+            this.downloadUrl = EXTENSION_DOWNLOAD_URL.DEFAULT;
+        }
+        this._onMessage = this._onMessage.bind(this);
+        this._onMessageFromForwarder = this._onMessageFromForwarder.bind(this);
+        this._openForwarder = this._openForwarder.bind(this);
+        this._openDownloadPage = this._openDownloadPage.bind(this);
+        this.startOnboarding = this.startOnboarding.bind(this);
+        this.stopOnboarding = this.stopOnboarding.bind(this);
+        window.addEventListener('message', this._onMessage);
+        if (forwarderMode === Onboarding.FORWARDER_MODE.INJECT &&
+            sessionStorage.getItem(REGISTRATION_IN_PROGRESS) === 'true') {
+            Onboarding._injectForwarder(this.forwarderOrigin);
+        }
+    }
+    Onboarding.prototype._onMessage = function (event) {
+        if (event.origin !== this.forwarderOrigin) {
+            // Ignoring non-forwarder message
+            return undefined;
+        }
+        if (event.data.type === 'metamask:reload') {
+            return this._onMessageFromForwarder(event);
+        }
+        console.debug("Unknown message from '" + event.origin + "' with data " + JSON.stringify(event.data));
+        return undefined;
+    };
+    Onboarding.prototype._onMessageUnknownStateError = function (state) {
+        throw new Error("Unknown state: '" + state + "'");
+    };
+    Onboarding.prototype._onMessageFromForwarder = function (event) {
+        return __awaiter(this, void 0, void 0, function () {
+            var _a;
+            return __generator(this, function (_b) {
+                switch (_b.label) {
+                    case 0:
+                        _a = this.state;
+                        switch (_a) {
+                            case ONBOARDING_STATE.RELOADING: return [3 /*break*/, 1];
+                            case ONBOARDING_STATE.NOT_INSTALLED: return [3 /*break*/, 2];
+                            case ONBOARDING_STATE.INSTALLED: return [3 /*break*/, 3];
+                            case ONBOARDING_STATE.REGISTERING: return [3 /*break*/, 5];
+                            case ONBOARDING_STATE.REGISTERED: return [3 /*break*/, 6];
+                        }
+                        return [3 /*break*/, 7];
+                    case 1:
+                        console.debug('Ignoring message while reloading');
+                        return [3 /*break*/, 8];
+                    case 2:
+                        console.debug('Reloading now to register with MetaMask');
+                        this.state = ONBOARDING_STATE.RELOADING;
+                        location.reload();
+                        return [3 /*break*/, 8];
+                    case 3:
+                        console.debug('Registering with MetaMask');
+                        this.state = ONBOARDING_STATE.REGISTERING;
+                        return [4 /*yield*/, Onboarding._register()];
+                    case 4:
+                        _b.sent();
+                        this.state = ONBOARDING_STATE.REGISTERED;
+                        event.source.postMessage({ type: 'metamask:registrationCompleted' }, event.origin);
+                        this.stopOnboarding();
+                        return [3 /*break*/, 8];
+                    case 5:
+                        console.debug('Already registering - ignoring reload message');
+                        return [3 /*break*/, 8];
+                    case 6:
+                        console.debug('Already registered - ignoring reload message');
+                        return [3 /*break*/, 8];
+                    case 7:
+                        this._onMessageUnknownStateError(this.state);
+                        _b.label = 8;
+                    case 8: return [2 /*return*/];
+                }
+            });
+        });
+    };
+    /**
+     * Starts onboarding by opening the MetaMask download page and the Onboarding forwarder
+     */
+    Onboarding.prototype.startOnboarding = function () {
+        sessionStorage.setItem(REGISTRATION_IN_PROGRESS, 'true');
+        this._openDownloadPage();
+        this._openForwarder();
+    };
+    /**
+     * Stops onboarding registration, including removing the injected forwarder (if any)
+     *
+     * Typically this function is not necessary, but it can be useful for cases where
+     * onboarding completes before the forwarder has registered.
+     */
+    Onboarding.prototype.stopOnboarding = function () {
+        if (sessionStorage.getItem(REGISTRATION_IN_PROGRESS) === 'true') {
+            if (this.forwarderMode === Onboarding.FORWARDER_MODE.INJECT) {
+                console.debug('Removing forwarder');
+                Onboarding._removeForwarder();
+            }
+            sessionStorage.setItem(REGISTRATION_IN_PROGRESS, 'false');
+        }
+    };
+    Onboarding.prototype._openForwarder = function () {
+        if (this.forwarderMode === Onboarding.FORWARDER_MODE.OPEN_TAB) {
+            window.open(this.forwarderOrigin, '_blank');
+        }
+        else {
+            Onboarding._injectForwarder(this.forwarderOrigin);
+        }
+    };
+    Onboarding.prototype._openDownloadPage = function () {
+        window.open(this.downloadUrl, '_blank');
+    };
+    /**
+     * Checks whether the MetaMask extension is installed
+     */
+    Onboarding.isMetaMaskInstalled = function () {
+        return Boolean(window.ethereum && window.ethereum.isMetaMask);
+    };
+    Onboarding._register = function () {
+        return window.ethereum.request({
+            method: 'wallet_registerOnboarding',
+        });
+    };
+    Onboarding._injectForwarder = function (forwarderOrigin) {
+        var container = document.body;
+        var iframe = document.createElement('iframe');
+        iframe.setAttribute('height', '0');
+        iframe.setAttribute('width', '0');
+        iframe.setAttribute('style', 'display: none;');
+        iframe.setAttribute('src', forwarderOrigin);
+        iframe.setAttribute('id', FORWARDER_ID);
+        container.insertBefore(iframe, container.children[0]);
+    };
+    Onboarding._removeForwarder = function () {
+        var _a;
+        (_a = document.getElementById(FORWARDER_ID)) === null || _a === void 0 ? void 0 : _a.remove();
+    };
+    Onboarding._detectBrowser = function () {
+        var browserInfo = Bowser.parse(window.navigator.userAgent);
+        if (browserInfo.browser.name === 'Firefox') {
+            return 'FIREFOX';
+        }
+        else if (['Chrome', 'Chromium'].includes(browserInfo.browser.name || '')) {
+            return 'CHROME';
+        }
+        return null;
+    };
+    Onboarding.FORWARDER_MODE = {
+        INJECT: 'INJECT',
+        OPEN_TAB: 'OPEN_TAB',
+    };
+    return Onboarding;
+}());
+
+export default Onboarding;

+ 61 - 0
node_modules/@metamask/onboarding/package.json

@@ -0,0 +1,61 @@
+{
+  "name": "@metamask/onboarding",
+  "version": "1.0.1",
+  "description": "Assists with onboarding new MetaMask users",
+  "main": "dist/metamask-onboarding.cjs.js",
+  "module": "dist/metamask-onboarding.es.js",
+  "types": "dist/index.d.ts",
+  "homepage": "https://github.com/MetaMask/metamask-onboarding#readme",
+  "bugs": {
+    "url": "https://github.com/MetaMask/metamask-onboarding/issues"
+  },
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/MetaMask/metamask-onboarding.git"
+  },
+  "publishConfig": {
+    "registry": "https://registry.npmjs.org/",
+    "access": "public"
+  },
+  "license": "MIT",
+  "files": [
+    "/src",
+    "/dist"
+  ],
+  "scripts": {
+    "setup": "yarn install && yarn allow-scripts",
+    "prepublishOnly": "yarn build",
+    "lint:eslint": "eslint . --cache --ext js,ts",
+    "lint:misc": "prettier '**/*.json' '**/*.md' '!CHANGELOG.md' '**/*.yml' --ignore-path .gitignore",
+    "lint": "yarn lint:eslint && yarn lint:misc --check",
+    "lint:fix": "yarn lint:eslint --fix && yarn lint:misc --write",
+    "build": "rollup --config"
+  },
+  "dependencies": {
+    "bowser": "^2.9.0"
+  },
+  "devDependencies": {
+    "@lavamoat/allow-scripts": "^1.0.6",
+    "@metamask/auto-changelog": "^2.3.0",
+    "@metamask/eslint-config": "^6.0.0",
+    "@metamask/eslint-config-nodejs": "^6.0.0",
+    "@metamask/eslint-config-typescript": "^6.0.0",
+    "@rollup/plugin-node-resolve": "^7.1.1",
+    "@typescript-eslint/eslint-plugin": "^4.26.0",
+    "@typescript-eslint/parser": "^4.26.0",
+    "eslint": "^7.27.0",
+    "eslint-config-prettier": "^8.3.0",
+    "eslint-plugin-import": "^2.23.4",
+    "eslint-plugin-node": "^11.1.0",
+    "eslint-plugin-prettier": "^3.4.0",
+    "prettier": "^2.3.0",
+    "rollup": "^2.18.0",
+    "rollup-plugin-typescript2": "^0.30.0",
+    "typescript": "^4.3.2"
+  },
+  "lavamoat": {
+    "allowScripts": {
+      "@lavamoat/preinstall-always-fail": false
+    }
+  }
+}

+ 205 - 0
node_modules/@metamask/onboarding/src/index.ts

@@ -0,0 +1,205 @@
+import Bowser from 'bowser';
+
+const ONBOARDING_STATE = {
+  INSTALLED: 'INSTALLED' as const,
+  NOT_INSTALLED: 'NOT_INSTALLED' as const,
+  REGISTERED: 'REGISTERED' as const,
+  REGISTERING: 'REGISTERING' as const,
+  RELOADING: 'RELOADING' as const,
+};
+
+const EXTENSION_DOWNLOAD_URL = {
+  CHROME:
+    'https://chrome.google.com/webstore/detail/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn',
+  FIREFOX: 'https://addons.mozilla.org/firefox/addon/ether-metamask/',
+  DEFAULT: 'https://metamask.io',
+};
+
+// sessionStorage key
+const REGISTRATION_IN_PROGRESS = 'REGISTRATION_IN_PROGRESS';
+
+// forwarder iframe id
+const FORWARDER_ID = 'FORWARDER_ID';
+
+export default class Onboarding {
+  static FORWARDER_MODE = {
+    INJECT: 'INJECT' as const,
+    OPEN_TAB: 'OPEN_TAB' as const,
+  };
+
+  private readonly forwarderOrigin: string;
+
+  private readonly downloadUrl: string;
+
+  private readonly forwarderMode: keyof typeof Onboarding.FORWARDER_MODE;
+
+  private state: keyof typeof ONBOARDING_STATE;
+
+  constructor({
+    forwarderOrigin = 'https://fwd.metamask.io',
+    forwarderMode = Onboarding.FORWARDER_MODE.INJECT,
+  } = {}) {
+    this.forwarderOrigin = forwarderOrigin;
+    this.forwarderMode = forwarderMode;
+    this.state = Onboarding.isMetaMaskInstalled()
+      ? ONBOARDING_STATE.INSTALLED
+      : ONBOARDING_STATE.NOT_INSTALLED;
+
+    const browser = Onboarding._detectBrowser();
+    if (browser) {
+      this.downloadUrl = EXTENSION_DOWNLOAD_URL[browser];
+    } else {
+      this.downloadUrl = EXTENSION_DOWNLOAD_URL.DEFAULT;
+    }
+
+    this._onMessage = this._onMessage.bind(this);
+    this._onMessageFromForwarder = this._onMessageFromForwarder.bind(this);
+    this._openForwarder = this._openForwarder.bind(this);
+    this._openDownloadPage = this._openDownloadPage.bind(this);
+    this.startOnboarding = this.startOnboarding.bind(this);
+    this.stopOnboarding = this.stopOnboarding.bind(this);
+
+    window.addEventListener('message', this._onMessage);
+
+    if (
+      forwarderMode === Onboarding.FORWARDER_MODE.INJECT &&
+      sessionStorage.getItem(REGISTRATION_IN_PROGRESS) === 'true'
+    ) {
+      Onboarding._injectForwarder(this.forwarderOrigin);
+    }
+  }
+
+  _onMessage(event: MessageEvent) {
+    if (event.origin !== this.forwarderOrigin) {
+      // Ignoring non-forwarder message
+      return undefined;
+    }
+
+    if (event.data.type === 'metamask:reload') {
+      return this._onMessageFromForwarder(event);
+    }
+
+    console.debug(
+      `Unknown message from '${event.origin}' with data ${JSON.stringify(
+        event.data,
+      )}`,
+    );
+    return undefined;
+  }
+
+  _onMessageUnknownStateError(state: never): never {
+    throw new Error(`Unknown state: '${state}'`);
+  }
+
+  async _onMessageFromForwarder(event: MessageEvent) {
+    switch (this.state) {
+      case ONBOARDING_STATE.RELOADING:
+        console.debug('Ignoring message while reloading');
+        break;
+      case ONBOARDING_STATE.NOT_INSTALLED:
+        console.debug('Reloading now to register with MetaMask');
+        this.state = ONBOARDING_STATE.RELOADING;
+        location.reload();
+        break;
+
+      case ONBOARDING_STATE.INSTALLED:
+        console.debug('Registering with MetaMask');
+        this.state = ONBOARDING_STATE.REGISTERING;
+        await Onboarding._register();
+        this.state = ONBOARDING_STATE.REGISTERED;
+        (event.source as Window).postMessage(
+          { type: 'metamask:registrationCompleted' },
+          event.origin,
+        );
+        this.stopOnboarding();
+        break;
+      case ONBOARDING_STATE.REGISTERING:
+        console.debug('Already registering - ignoring reload message');
+        break;
+      case ONBOARDING_STATE.REGISTERED:
+        console.debug('Already registered - ignoring reload message');
+        break;
+      default:
+        this._onMessageUnknownStateError(this.state);
+    }
+  }
+
+  /**
+   * Starts onboarding by opening the MetaMask download page and the Onboarding forwarder
+   */
+  startOnboarding() {
+    sessionStorage.setItem(REGISTRATION_IN_PROGRESS, 'true');
+    this._openDownloadPage();
+    this._openForwarder();
+  }
+
+  /**
+   * Stops onboarding registration, including removing the injected forwarder (if any)
+   *
+   * Typically this function is not necessary, but it can be useful for cases where
+   * onboarding completes before the forwarder has registered.
+   */
+  stopOnboarding() {
+    if (sessionStorage.getItem(REGISTRATION_IN_PROGRESS) === 'true') {
+      if (this.forwarderMode === Onboarding.FORWARDER_MODE.INJECT) {
+        console.debug('Removing forwarder');
+        Onboarding._removeForwarder();
+      }
+      sessionStorage.setItem(REGISTRATION_IN_PROGRESS, 'false');
+    }
+  }
+
+  _openForwarder() {
+    if (this.forwarderMode === Onboarding.FORWARDER_MODE.OPEN_TAB) {
+      window.open(this.forwarderOrigin, '_blank');
+    } else {
+      Onboarding._injectForwarder(this.forwarderOrigin);
+    }
+  }
+
+  _openDownloadPage() {
+    window.open(this.downloadUrl, '_blank');
+  }
+
+  /**
+   * Checks whether the MetaMask extension is installed
+   */
+  static isMetaMaskInstalled() {
+    return Boolean(
+      (window as any).ethereum && (window as any).ethereum.isMetaMask,
+    );
+  }
+
+  static _register() {
+    return (window as any).ethereum.request({
+      method: 'wallet_registerOnboarding',
+    });
+  }
+
+  static _injectForwarder(forwarderOrigin: string) {
+    const container = document.body;
+    const iframe = document.createElement('iframe');
+    iframe.setAttribute('height', '0');
+    iframe.setAttribute('width', '0');
+    iframe.setAttribute('style', 'display: none;');
+    iframe.setAttribute('src', forwarderOrigin);
+    iframe.setAttribute('id', FORWARDER_ID);
+    container.insertBefore(iframe, container.children[0]);
+  }
+
+  static _removeForwarder() {
+    document.getElementById(FORWARDER_ID)?.remove();
+  }
+
+  static _detectBrowser() {
+    const browserInfo = Bowser.parse(window.navigator.userAgent);
+    if (browserInfo.browser.name === 'Firefox') {
+      return 'FIREFOX';
+    } else if (
+      ['Chrome', 'Chromium'].includes(browserInfo.browser.name || '')
+    ) {
+      return 'CHROME';
+    }
+    return null;
+  }
+}

+ 218 - 0
node_modules/bowser/CHANGELOG.md

@@ -0,0 +1,218 @@
+# Bowser Changelog
+
+### 2.11.0 (Sep 12, 2020)
+- [ADD] Added support for aliases in `Parser#is` method (#437)
+- [ADD] Added more typings (#438, #427)
+- [ADD] Added support for MIUI Browserr (#436)
+
+### 2.10.0 (Jul 9, 2020)
+- [FIX] Fix for Firefox detection on iOS 13 [#415]
+- [FIX] Fixes for typings.d.ts [#409]
+- [FIX] Updated development dependencies
+
+### 2.9.0 (Jan 28, 2020)
+- [ADD] Export more methods and constants via .d.ts [#388], [#390]
+
+### 2.8.1 (Dec 26, 2019)
+- [FIX] Reverted [#382] as it broke build
+
+### 2.8.0 (Dec 26, 2019)
+- [ADD] Add polyfills for Array.find & Object.assign [#383]
+- [ADD] Export constants with types.d.ts [#382]
+- [FIX] Add support for WeChat on Windows [#381]
+- [FIX] Fix detection of Firefox on iPad [#379]
+- [FIX] Add detection of Electron [#375]
+- [FIX] Updated dev-dependencies
+
+### 2.7.0 (Oct 2, 2019)
+- [FIX] Add support for QQ Browser [#362]
+- [FIX] Add support for GSA [#364]
+- [FIX] Updated dependencies
+
+### 2.6.0 (Sep 6, 2019)
+- [ADD] Define "module" export in package.json [#354]
+- [FIX] Fix Tablet PC detection [#334]
+
+### 2.5.4 (Sep 2, 2019)
+- [FIX] Exclude docs from the npm package [#349]
+
+### 2.5.3 (Aug 4, 2019)
+- [FIX] Add MacOS names support [#338]
+- [FIX] Point typings.d.ts from package.json [#341]
+- [FIX] Upgrade dependencies
+
+### 2.5.2 (July 17, 2019)
+- [FIX] Fixes the bug undefined method because of failed build (#335)
+
+### 2.5.1 (July 17, 2019)
+- [FIX] Fixes the bug with a custom Error class (#335)
+- [FIX] Fixes the settings for Babel to reduce the bundle size (#259)
+
+### 2.5.0 (July 16, 2019)
+- [ADD] Add constant output so that users can quickly get all types (#325)
+- [FIX] Add support for Roku OS (#332)
+- [FIX] Update devDependencies
+- [FIX] Fix docs, README and added funding information
+
+### 2.4.0 (May 3, 2019)
+- [FIX] Update regexp for generic browsers (#310)
+- [FIX] Fix issues with module.exports (#318)
+- [FIX] Update devDependencies (#316, #321, #322)
+- [FIX] Fix docs (#320)
+
+### 2.3.0 (April 14, 2019)
+- [ADD] Add support for Blink-based MS Edge (#311)
+- [ADD] Add more types for TS (#289)
+- [FIX] Update dev-dependencies
+- [FIX] Update docs
+
+### 2.2.1 (April 12, 2019)
+- [ADD] Add an alias for Samsung Internet
+- [FIX] Fix browser name detection for browsers without an alias (#313)
+
+### 2.2.0 (April 7, 2019)
+- [ADD] Add short aliases for browser names (#295)
+- [FIX] Fix Yandex Browser version detection (#308)
+
+### 2.1.2 (March 6, 2019)
+- [FIX] Fix buggy `getFirstMatch` reference
+
+### 2.1.1 (March 6, 2019)
+- [ADD] Add detection of PlayStation 4 (#291)
+- [ADD] Deploy docs on GH Pages (#293)
+- [FIX] Fix files extensions for importing (#294)
+- [FIX] Fix docs (#295)
+
+### 2.1.0 (January 24, 2019)
+- [ADD] Add new `Parser.getEngineName()` method (#288)
+- [ADD] Add detection of ChromeOS (#287)
+- [FIX] Fix README
+
+### 2.0.0 (January 19, 2019)
+- [ADD] Support a non strict equality in `Parser.satisfies()` (#275)
+- [ADD] Add Android versions names (#276)
+- [ADD] Add a typings file (#277)
+- [ADD] Added support for Googlebot recognition (#278)
+- [FIX] Update building tools, avoid security issues
+
+### 2.0.0-beta.3 (September 15, 2018)
+- [FIX] Fix Chrome Mobile detection (#253)
+- [FIX] Use built bowser for CI (#252)
+- [FIX] Update babel-plugin-add-module-exports (#251)
+
+### 2.0.0-beta.2 (September 9, 2018)
+- [FIX] Fix failing comparing version through `Parser.satisfies` (#243)
+- [FIX] Fix travis testing, include eslint into CI testing
+- [FIX] Add support for Maxthon desktop browser (#246)
+- [FIX] Add support for Swing browser (#248)
+- [DOCS] Regenerate docs
+
+### 2.0.0-beta.1 (August 18, 2018)
+- [ADD] Add loose version comparison to `Parser.compareVersion()` and `Parser.satisfies()`
+- [CHORE] Add CONTRIBUTING.md
+- [DOCS] Regenerate docs
+
+### 2.0.0-alpha.4 (August 2, 2018)
+- [DOCS] Fix usage docs (#238)
+- [CHANGE] Make `./es5.js` the main file of the package (#239)
+
+### 2.0.0-alpha.3 (July 22, 2018)
+- [CHANGE] Rename split and rename `compiled.js` to `es5.js` and `bundled.js` (#231, #236, #237)
+- [ADD] Add `Parser.some` (#235)
+
+### 2.0.0-alpha.2 (July 17, 2018)
+- [CHANGE] Make `src/bowser` main file instead of the bundled one
+- [CHANGE] Move the bundled file to the root of the package to make it possible to `require('bowser/compiled')` (#231)
+- [REMOVE] Remove `typings.d.ts` before stable release (#232)
+- [FIX] Improve Nexus devices detection (#233)
+
+### 2.0.0-alpha.1 (July 9, 2018)
+- [ADD] `Bowser.getParser()`
+- [ADD] `Bowser.parse`
+- [ADD] `Parser` class which describes parsing process
+- [CHANGE] Change bowser's returning object
+- [REMOVE] Remove bower support
+
+### 1.9.4 (June 28, 2018)
+- [FIX] Fix NAVER Whale browser detection (#220)
+- [FIX] Fix MZ Browser browser detection (#219)
+- [FIX] Fix Firefox Focus browser detection (#191)
+- [FIX] Fix webOS browser detection (#186)
+
+### 1.9.3 (March 12, 2018)
+- [FIX] Fix `typings.d.ts` — add `ipad`, `iphone`, `ipod` flags to the interface
+
+### 1.9.2 (February 5, 2018)
+- [FIX] Fix `typings.d.ts` — add `osname` flag to the interface
+
+### 1.9.1 (December 22, 2017)
+- [FIX] Fix `typings.d.ts` — add `chromium` flag to the interface
+
+### 1.9.0 (December 20, 2017)
+- [ADD] Add a public method `.detect()` (#205)
+- [DOCS] Fix description of `chromium` flag in docs (#206)
+
+### 1.8.1 (October 7, 2017)
+- [FIX] Fix detection of MS Edge on Android and iOS (#201)
+
+### 1.8.0 (October 7, 2017)
+- [ADD] Add `osname` into result object (#200)
+
+### 1.7.3 (August 30, 2017)
+- [FIX] Fix detection of Chrome on Android 8 OPR6 (#193)
+
+### 1.7.2 (August 17, 2017)
+- [FIX] Fix typings.d.ts according to #185
+
+### 1.7.1 (July 13, 2017)
+- [ADD] Fix detecting of Tablet PC as tablet (#183)
+
+### 1.7.0 (May 18, 2017)
+- [ADD] Add OS version support for Windows and macOS (#178)
+
+### 1.6.0 (December 5, 2016)
+- [ADD] Add some tests for Windows devices (#89)
+- [ADD] Add `root` to initialization process (#170)
+- [FIX] Upgrade .travis.yml config
+
+### 1.5.0 (October 31, 2016)
+- [ADD] Throw an error when `minVersion` map has not a string as a version and fix readme (#165)
+- [FIX] Fix truly detection of Windows Phones (#167)
+
+### 1.4.6 (September 19, 2016)
+- [FIX] Fix mobile Opera's version detection on Android
+- [FIX] Fix typescript typings — add `mobile` and `tablet` flags
+- [DOC] Fix description of `bowser.check`
+
+### 1.4.5 (August 30, 2016)
+
+- [FIX] Add support of Samsung Internet for Android
+- [FIX] Fix case when `navigator.userAgent` is `undefined`
+- [DOC] Add information about `strictMode` in `check` function
+- [DOC] Consistent use of `bowser` variable in the README
+
+### 1.4.4 (August 10, 2016)
+
+- [FIX] Fix AMD `define` call — pass name to the function
+
+### 1.4.3 (July 27, 2016)
+
+- [FIX] Fix error `Object doesn't support this property or method` on IE8
+
+### 1.4.2 (July 26, 2016)
+
+- [FIX] Fix missing `isUnsupportedBrowser` in typings description
+- [DOC] Fix `check`'s declaration in README
+
+### 1.4.1 (July 7, 2016)
+
+- [FIX] Fix `strictMode` logic for `isUnsupportedBrowser`
+
+### 1.4.0 (June 28, 2016)
+
+- [FEATURE] Add `bowser.compareVersions` method
+- [FEATURE] Add `bowser.isUnsupportedBrowser` method
+- [FEATURE] Add `bowser.check` method
+- [DOC] Changelog started
+- [DOC] Add API section to README
+- [FIX] Fix detection of browser type (A/C/X) for Chromium

+ 39 - 0
node_modules/bowser/LICENSE

@@ -0,0 +1,39 @@
+Copyright 2015, Dustin Diaz (the "Original Author")
+All rights reserved.
+
+MIT License
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+Distributions of all or part of the Software intended to be used
+by the recipients as they would use the unmodified Software,
+containing modifications that substantially alter, remove, or
+disable functionality of the Software, outside of the documented
+configuration mechanisms provided by the Software, shall be
+modified such that the Original Author's bug reporting email
+addresses and urls are either replaced with the contact information
+of the parties responsible for the changes, or removed entirely.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+
+Except where noted, this license applies to any and all software
+programs and associated documentation files created by the
+Original Author, when distributed with the Software.

+ 179 - 0
node_modules/bowser/README.md

@@ -0,0 +1,179 @@
+## Bowser
+A small, fast and rich-API browser/platform/engine detector for both browser and node.
+- **Small.** Use plain ES5-version which is ~4.8kB gzipped.
+- **Optimized.** Use only those parsers you need — it doesn't do useless work.
+- **Multi-platform.** It's browser- and node-ready, so you can use it in any environment.
+
+Don't hesitate to support the project on Github or [OpenCollective](https://opencollective.com/bowser) if you like it ❤️ Also, contributors are always welcome!
+
+[![Financial Contributors on Open Collective](https://opencollective.com/bowser/all/badge.svg?label=financial+contributors)](https://opencollective.com/bowser) [![Build Status](https://travis-ci.org/lancedikson/bowser.svg?branch=master)](https://travis-ci.org/lancedikson/bowser/)  [![Greenkeeper badge](https://badges.greenkeeper.io/lancedikson/bowser.svg)](https://greenkeeper.io/)  [![Coverage Status](https://coveralls.io/repos/github/lancedikson/bowser/badge.svg?branch=master)](https://coveralls.io/github/lancedikson/bowser?branch=master) ![Downloads](https://img.shields.io/npm/dm/bowser)
+
+# Contents
+- [Overview](#overview)
+- [Use cases](#use-cases)
+- [Advanced usage](#advanced-usage)
+- [How can I help?](#contributing)
+
+# Overview
+
+The library is made to help to detect what browser your user has and gives you a convenient API to filter the users somehow depending on their browsers. Check it out on this page: https://bowser-js.github.io/bowser-online/.
+
+### ⚠️ Version 2.0 breaking changes ⚠️
+
+Version 2.0 has drastically changed the API. All available methods are on the [docs page](https://lancedikson.github.io/bowser/docs).
+
+_For legacy code, check out the [1.x](https://github.com/lancedikson/bowser/tree/v1.x) branch and install it through `npm install bowser@1.9.4`._
+
+# Use cases
+
+First of all, require the library. This is a UMD Module, so it will work for AMD, TypeScript, ES6, and CommonJS module systems.
+
+```javascript
+const Bowser = require("bowser"); // CommonJS
+
+import * as Bowser from "bowser"; // TypeScript
+
+import Bowser from "bowser"; // ES6 (and TypeScript with --esModuleInterop enabled)
+```
+
+By default, the exported version is the *ES5 transpiled version*, which **do not** include any polyfills.
+
+In case you don't use your own `babel-polyfill` you may need to have pre-built bundle with all needed polyfills.
+So, for you it's suitable to require bowser like this: `require('bowser/bundled')`.
+As the result, you get a ES5 version of bowser with `babel-polyfill` bundled together.
+
+You may need to use the source files, so they will be available in the package as well.
+
+## Browser props detection
+
+Often we need to pick users' browser properties such as the name, the version, the rendering engine and so on. Here is an example how to do it with Bowser:
+
+```javascript
+const browser = Bowser.getParser(window.navigator.userAgent);
+
+console.log(`The current browser name is "${browser.getBrowserName()}"`);
+// The current browser name is "Internet Explorer"
+```
+
+or
+
+```javascript
+const browser = Bowser.getParser(window.navigator.userAgent);
+console.log(browser.getBrowser());
+
+// outputs
+{
+  name: "Internet Explorer"
+  version: "11.0"
+}
+```
+
+or
+
+```javascript
+console.log(Bowser.parse(window.navigator.userAgent));
+
+// outputs
+{
+  browser: {
+    name: "Internet Explorer"
+    version: "11.0"
+  },
+  os: {
+    name: "Windows"
+    version: "NT 6.3"
+    versionName: "8.1"
+  },
+  platform: {
+    type: "desktop"
+  },
+  engine: {
+    name: "Trident"
+    version: "7.0"
+  }
+}
+```
+
+
+## Filtering browsers
+
+You could want to filter some particular browsers to provide any special support for them or make any workarounds.
+It could look like this:
+
+```javascript
+const browser = Bowser.getParser(window.navigator.userAgent);
+const isValidBrowser = browser.satisfies({
+  // declare browsers per OS
+  windows: {
+    "internet explorer": ">10",
+  },
+  macos: {
+    safari: ">10.1"
+  },
+
+  // per platform (mobile, desktop or tablet)
+  mobile: {
+    safari: '>=9',
+    'android browser': '>3.10'
+  },
+
+  // or in general
+  chrome: "~20.1.1432",
+  firefox: ">31",
+  opera: ">=22",
+
+  // also supports equality operator
+  chrome: "=20.1.1432", // will match particular build only
+
+  // and loose-equality operator
+  chrome: "~20",        // will match any 20.* sub-version
+  chrome: "~20.1"       // will match any 20.1.* sub-version (20.1.19 as well as 20.1.12.42-alpha.1)
+});
+```
+
+Settings for any particular OS or platform has more priority and redefines settings of standalone browsers.
+Thus, you can define OS or platform specific rules and they will have more priority in the end.
+
+More of API and possibilities you will find in the `docs` folder.
+
+### Browser names for `.satisfies()`
+
+By default you are supposed to use the full browser name for `.satisfies`.
+But, there's a short way to define a browser using short aliases. The full
+list of aliases can be found in [the file](src/constants.js).
+
+## Similar Projects
+* [Kong](https://github.com/BigBadBleuCheese/Kong) - A C# port of Bowser.
+
+## Contributors
+
+### Code Contributors
+
+This project exists thanks to all the people who contribute. [[Contribute](CONTRIBUTING.md)].
+<a href="https://github.com/lancedikson/bowser/graphs/contributors"><img src="https://opencollective.com/bowser/contributors.svg?width=890&button=false" /></a>
+
+### Financial Contributors
+
+Become a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/bowser/contribute)]
+
+#### Individuals
+
+<a href="https://opencollective.com/bowser"><img src="https://opencollective.com/bowser/individuals.svg?width=890"></a>
+
+#### Organizations
+
+Support this project with your organization. Your logo will show up here with a link to your website. [[Contribute](https://opencollective.com/bowser/contribute)]
+
+<a href="https://opencollective.com/bowser/organization/0/website"><img src="https://opencollective.com/bowser/organization/0/avatar.svg"></a>
+<a href="https://opencollective.com/bowser/organization/1/website"><img src="https://opencollective.com/bowser/organization/1/avatar.svg"></a>
+<a href="https://opencollective.com/bowser/organization/2/website"><img src="https://opencollective.com/bowser/organization/2/avatar.svg"></a>
+<a href="https://opencollective.com/bowser/organization/3/website"><img src="https://opencollective.com/bowser/organization/3/avatar.svg"></a>
+<a href="https://opencollective.com/bowser/organization/4/website"><img src="https://opencollective.com/bowser/organization/4/avatar.svg"></a>
+<a href="https://opencollective.com/bowser/organization/5/website"><img src="https://opencollective.com/bowser/organization/5/avatar.svg"></a>
+<a href="https://opencollective.com/bowser/organization/6/website"><img src="https://opencollective.com/bowser/organization/6/avatar.svg"></a>
+<a href="https://opencollective.com/bowser/organization/7/website"><img src="https://opencollective.com/bowser/organization/7/avatar.svg"></a>
+<a href="https://opencollective.com/bowser/organization/8/website"><img src="https://opencollective.com/bowser/organization/8/avatar.svg"></a>
+<a href="https://opencollective.com/bowser/organization/9/website"><img src="https://opencollective.com/bowser/organization/9/avatar.svg"></a>
+
+## License
+Licensed as MIT. All rights not explicitly granted in the MIT license are reserved. See the included LICENSE file for more details.

Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
node_modules/bowser/bundled.js


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
node_modules/bowser/es5.js


+ 250 - 0
node_modules/bowser/index.d.ts

@@ -0,0 +1,250 @@
+// Type definitions for Bowser v2
+// Project: https://github.com/lancedikson/bowser
+// Definitions by: Alexander P. Cerutti <https://github.com/alexandercerutti>,
+
+export = Bowser;
+export as namespace Bowser;
+
+declare namespace Bowser {
+  /**
+   * Creates a Parser instance
+   * @param {string} UA - User agent string
+   * @param {boolean} skipParsing
+   */
+
+  function getParser(UA: string, skipParsing?: boolean): Parser.Parser;
+
+  /**
+   * Creates a Parser instance and runs Parser.getResult immediately
+   * @param UA - User agent string
+   * @returns {Parser.ParsedResult}
+   */
+
+  function parse(UA: string): Parser.ParsedResult;
+
+  /**
+   * Constants exposed via bowser getters
+   */
+  const BROWSER_MAP: Record<string, string>;
+  const ENGINE_MAP: Record<string, string>;
+  const OS_MAP: Record<string, string>;
+  const PLATFORMS_MAP: Record<string, string>;
+
+  namespace Parser {
+    interface Parser {
+      constructor(UA: string, skipParsing?: boolean): Parser.Parser;
+
+      /**
+       * Get parsed browser object
+       * @return {BrowserDetails} Browser's details
+       */
+
+      getBrowser(): BrowserDetails;
+
+      /**
+       * Get browser's name
+       * @param {Boolean} [toLowerCase] return lower-cased value
+       * @return {String} Browser's name or an empty string
+       */
+
+      getBrowserName(toLowerCase?: boolean): string;
+
+      /**
+       * Get browser's version
+       * @return {String} version of browser
+       */
+
+      getBrowserVersion(): string;
+
+      /**
+       * Get OS
+       * @return {OSDetails} - OS Details
+       *
+       * @example
+       * this.getOS(); // {
+       * //   name: 'macOS',
+       * //   version: '10.11.12',
+       * // }
+       */
+
+      getOS(): OSDetails;
+
+      /**
+       * Get OS name
+       * @param {Boolean} [toLowerCase] return lower-cased value
+       * @return {String} name of the OS — macOS, Windows, Linux, etc.
+       */
+
+      getOSName(toLowerCase?: boolean): string;
+
+      /**
+       * Get OS version
+       * @return {String} full version with dots ('10.11.12', '5.6', etc)
+       */
+
+      getOSVersion(): string;
+
+      /**
+       * Get parsed platform
+       * @returns {PlatformDetails}
+       */
+
+      getPlatform(): PlatformDetails;
+
+      /**
+       * Get platform name
+       * @param {boolean} toLowerCase
+       */
+
+      getPlatformType(toLowerCase?: boolean): string;
+
+      /**
+       * Get parsed engine
+       * @returns {EngineDetails}
+       */
+
+      getEngine(): EngineDetails;
+
+      /**
+       * Get parsed engine's name
+       * @returns {String} Engine's name or an empty string
+       */
+
+      getEngineName(): string;
+
+      /**
+       * Get parsed result
+       * @return {ParsedResult}
+       */
+
+      getResult(): ParsedResult;
+
+      /**
+       * Get UserAgent string of current Parser instance
+       * @return {String} User-Agent String of the current <Parser> object
+       */
+
+      getUA(): string;
+
+      /**
+       * Is anything? Check if the browser is called "anything",
+       * the OS called "anything" or the platform called "anything"
+       * @param {String} anything
+       * @returns {Boolean}
+       */
+
+      is(anything: any): boolean;
+
+      /**
+       * Parse full information about the browser
+       * @returns {Parser.Parser}
+       */
+
+      parse(): Parser.Parser;
+
+      /**
+       * Get parsed browser object
+       * @returns {BrowserDetails}
+       */
+
+      parseBrowser(): BrowserDetails;
+
+      /**
+       * Get parsed engine
+       * @returns {EngineDetails}
+       */
+
+      parseEngine(): EngineDetails;
+
+      /**
+       * Parse OS and save it to this.parsedResult.os
+       * @returns {OSDetails}
+       */
+
+      parseOS(): OSDetails;
+
+      /**
+       * Get parsed platform
+       * @returns {PlatformDetails}
+       */
+
+      parsePlatform(): PlatformDetails;
+
+      /**
+       * Check if parsed browser matches certain conditions
+       *
+       * @param {checkTree} checkTree It's one or two layered object,
+       * which can include a platform or an OS on the first layer
+       * and should have browsers specs on the bottom-laying layer
+       *
+       * @returns {Boolean|undefined} Whether the browser satisfies the set conditions or not.
+       * Returns `undefined` when the browser is no described in the checkTree object.
+       *
+       * @example
+       * const browser = new Bowser(UA);
+       * if (browser.check({chrome: '>118.01.1322' }))
+       * // or with os
+       * if (browser.check({windows: { chrome: '>118.01.1322' } }))
+       * // or with platforms
+       * if (browser.check({desktop: { chrome: '>118.01.1322' } }))
+       */
+
+      satisfies(checkTree: checkTree): boolean | undefined;
+
+       /**
+       * Check if the browser name equals the passed string
+       * @param browserName The string to compare with the browser name
+       * @param [includingAlias=false] The flag showing whether alias will be included into comparison
+       * @returns {boolean}
+       */
+
+
+      isBrowser(browserName: string, includingAlias?: boolean): boolean;
+
+      /**
+       * Check if any of the given values satifies `.is(anything)`
+       * @param {string[]} anythings
+       * @returns {boolean} true if at least one condition is satisfied, false otherwise.
+       */
+
+      some(anythings: string[]): boolean | undefined;
+
+      /**
+       * Test a UA string for a regexp
+       * @param regex
+       * @returns {boolean} true if the regex matches the UA, false otherwise.
+       */
+
+      test(regex: RegExp): boolean;
+    }
+
+    interface ParsedResult {
+      browser: BrowserDetails;
+      os: OSDetails;
+      platform: PlatformDetails;
+      engine: EngineDetails;
+    }
+
+    interface Details {
+      name?: string;
+      version?: string;
+    }
+
+    interface OSDetails extends Details {
+      versionName?: string;
+    }
+
+    interface PlatformDetails {
+      type?: string;
+      vendor?: string;
+      model?: string;
+    }
+
+    type BrowserDetails = Details;
+    type EngineDetails = Details;
+
+    interface checkTree {
+      [key: string]: any;
+    }
+  }
+}

+ 83 - 0
node_modules/bowser/package.json

@@ -0,0 +1,83 @@
+{
+  "name": "bowser",
+  "version": "2.11.0",
+  "description": "Lightweight browser detector",
+  "keywords": [
+    "browser",
+    "useragent",
+    "user-agent",
+    "parser",
+    "ua",
+    "detection",
+    "ender",
+    "sniff"
+  ],
+  "homepage": "https://github.com/lancedikson/bowser",
+  "author": "Dustin Diaz <dustin@dustindiaz.com> (http://dustindiaz.com)",
+  "contributors": [
+    {
+      "name": "Denis Demchenko",
+      "url": "http://twitter.com/lancedikson"
+    }
+  ],
+  "main": "es5.js",
+  "browser": "es5.js",
+  "module": "src/bowser.js",
+  "types": "index.d.ts",
+  "repository": {
+    "type": "git",
+    "url": "git+https://github.com/lancedikson/bowser.git"
+  },
+  "devDependencies": {
+    "@babel/cli": "^7.11.6",
+    "@babel/core": "^7.8.0",
+    "@babel/polyfill": "^7.8.3",
+    "@babel/preset-env": "^7.8.2",
+    "@babel/register": "^7.8.3",
+    "ava": "^3.0.0",
+    "babel-eslint": "^10.0.3",
+    "babel-loader": "^8.0.6",
+    "babel-plugin-add-module-exports": "^1.0.2",
+    "babel-plugin-istanbul": "^6.0.0",
+    "compression-webpack-plugin": "^4.0.0",
+    "coveralls": "^3.0.6",
+    "docdash": "^1.1.1",
+    "eslint": "^6.5.1",
+    "eslint-config-airbnb-base": "^13.2.0",
+    "eslint-plugin-ava": "^10.0.0",
+    "eslint-plugin-import": "^2.18.2",
+    "gh-pages": "^3.0.0",
+    "jsdoc": "^3.6.3",
+    "nyc": "^15.0.0",
+    "sinon": "^9.0.0",
+    "testem": "^3.0.0",
+    "webpack": "^4.41.0",
+    "webpack-bundle-analyzer": "^3.5.2",
+    "webpack-cli": "^3.3.9",
+    "yamljs": "^0.3.0"
+  },
+  "ava": {
+    "require": [
+      "@babel/register"
+    ]
+  },
+  "bugs": {
+    "url": "https://github.com/lancedikson/bowser/issues"
+  },
+  "directories": {
+    "test": "test"
+  },
+  "scripts": {
+    "build": "webpack --config webpack.config.js",
+    "generate-and-deploy-docs": "npm run generate-docs && gh-pages --dist docs --dest docs",
+    "watch": "webpack --watch --config webpack.config.js",
+    "prepublishOnly": "npm run build",
+    "lint": "eslint ./src",
+    "testem": "testem",
+    "test": "nyc --reporter=html --reporter=text ava",
+    "test:watch": "ava --watch",
+    "coverage": "nyc report --reporter=text-lcov | coveralls",
+    "generate-docs": "jsdoc -c jsdoc.json"
+  },
+  "license": "MIT"
+}

+ 77 - 0
node_modules/bowser/src/bowser.js

@@ -0,0 +1,77 @@
+/*!
+ * Bowser - a browser detector
+ * https://github.com/lancedikson/bowser
+ * MIT License | (c) Dustin Diaz 2012-2015
+ * MIT License | (c) Denis Demchenko 2015-2019
+ */
+import Parser from './parser.js';
+import {
+  BROWSER_MAP,
+  ENGINE_MAP,
+  OS_MAP,
+  PLATFORMS_MAP,
+} from './constants.js';
+
+/**
+ * Bowser class.
+ * Keep it simple as much as it can be.
+ * It's supposed to work with collections of {@link Parser} instances
+ * rather then solve one-instance problems.
+ * All the one-instance stuff is located in Parser class.
+ *
+ * @class
+ * @classdesc Bowser is a static object, that provides an API to the Parsers
+ * @hideconstructor
+ */
+class Bowser {
+  /**
+   * Creates a {@link Parser} instance
+   *
+   * @param {String} UA UserAgent string
+   * @param {Boolean} [skipParsing=false] Will make the Parser postpone parsing until you ask it
+   * explicitly. Same as `skipParsing` for {@link Parser}.
+   * @returns {Parser}
+   * @throws {Error} when UA is not a String
+   *
+   * @example
+   * const parser = Bowser.getParser(window.navigator.userAgent);
+   * const result = parser.getResult();
+   */
+  static getParser(UA, skipParsing = false) {
+    if (typeof UA !== 'string') {
+      throw new Error('UserAgent should be a string');
+    }
+    return new Parser(UA, skipParsing);
+  }
+
+  /**
+   * Creates a {@link Parser} instance and runs {@link Parser.getResult} immediately
+   *
+   * @param UA
+   * @return {ParsedResult}
+   *
+   * @example
+   * const result = Bowser.parse(window.navigator.userAgent);
+   */
+  static parse(UA) {
+    return (new Parser(UA)).getResult();
+  }
+
+  static get BROWSER_MAP() {
+    return BROWSER_MAP;
+  }
+
+  static get ENGINE_MAP() {
+    return ENGINE_MAP;
+  }
+
+  static get OS_MAP() {
+    return OS_MAP;
+  }
+
+  static get PLATFORMS_MAP() {
+    return PLATFORMS_MAP;
+  }
+}
+
+export default Bowser;

+ 116 - 0
node_modules/bowser/src/constants.js

@@ -0,0 +1,116 @@
+// NOTE: this list must be up-to-date with browsers listed in
+// test/acceptance/useragentstrings.yml
+export const BROWSER_ALIASES_MAP = {
+  'Amazon Silk': 'amazon_silk',
+  'Android Browser': 'android',
+  Bada: 'bada',
+  BlackBerry: 'blackberry',
+  Chrome: 'chrome',
+  Chromium: 'chromium',
+  Electron: 'electron',
+  Epiphany: 'epiphany',
+  Firefox: 'firefox',
+  Focus: 'focus',
+  Generic: 'generic',
+  'Google Search': 'google_search',
+  Googlebot: 'googlebot',
+  'Internet Explorer': 'ie',
+  'K-Meleon': 'k_meleon',
+  Maxthon: 'maxthon',
+  'Microsoft Edge': 'edge',
+  'MZ Browser': 'mz',
+  'NAVER Whale Browser': 'naver',
+  Opera: 'opera',
+  'Opera Coast': 'opera_coast',
+  PhantomJS: 'phantomjs',
+  Puffin: 'puffin',
+  QupZilla: 'qupzilla',
+  QQ: 'qq',
+  QQLite: 'qqlite',
+  Safari: 'safari',
+  Sailfish: 'sailfish',
+  'Samsung Internet for Android': 'samsung_internet',
+  SeaMonkey: 'seamonkey',
+  Sleipnir: 'sleipnir',
+  Swing: 'swing',
+  Tizen: 'tizen',
+  'UC Browser': 'uc',
+  Vivaldi: 'vivaldi',
+  'WebOS Browser': 'webos',
+  WeChat: 'wechat',
+  'Yandex Browser': 'yandex',
+  Roku: 'roku',
+};
+
+export const BROWSER_MAP = {
+  amazon_silk: 'Amazon Silk',
+  android: 'Android Browser',
+  bada: 'Bada',
+  blackberry: 'BlackBerry',
+  chrome: 'Chrome',
+  chromium: 'Chromium',
+  electron: 'Electron',
+  epiphany: 'Epiphany',
+  firefox: 'Firefox',
+  focus: 'Focus',
+  generic: 'Generic',
+  googlebot: 'Googlebot',
+  google_search: 'Google Search',
+  ie: 'Internet Explorer',
+  k_meleon: 'K-Meleon',
+  maxthon: 'Maxthon',
+  edge: 'Microsoft Edge',
+  mz: 'MZ Browser',
+  naver: 'NAVER Whale Browser',
+  opera: 'Opera',
+  opera_coast: 'Opera Coast',
+  phantomjs: 'PhantomJS',
+  puffin: 'Puffin',
+  qupzilla: 'QupZilla',
+  qq: 'QQ Browser',
+  qqlite: 'QQ Browser Lite',
+  safari: 'Safari',
+  sailfish: 'Sailfish',
+  samsung_internet: 'Samsung Internet for Android',
+  seamonkey: 'SeaMonkey',
+  sleipnir: 'Sleipnir',
+  swing: 'Swing',
+  tizen: 'Tizen',
+  uc: 'UC Browser',
+  vivaldi: 'Vivaldi',
+  webos: 'WebOS Browser',
+  wechat: 'WeChat',
+  yandex: 'Yandex Browser',
+};
+
+export const PLATFORMS_MAP = {
+  tablet: 'tablet',
+  mobile: 'mobile',
+  desktop: 'desktop',
+  tv: 'tv',
+};
+
+export const OS_MAP = {
+  WindowsPhone: 'Windows Phone',
+  Windows: 'Windows',
+  MacOS: 'macOS',
+  iOS: 'iOS',
+  Android: 'Android',
+  WebOS: 'WebOS',
+  BlackBerry: 'BlackBerry',
+  Bada: 'Bada',
+  Tizen: 'Tizen',
+  Linux: 'Linux',
+  ChromeOS: 'Chrome OS',
+  PlayStation4: 'PlayStation 4',
+  Roku: 'Roku',
+};
+
+export const ENGINE_MAP = {
+  EdgeHTML: 'EdgeHTML',
+  Blink: 'Blink',
+  Trident: 'Trident',
+  Presto: 'Presto',
+  Gecko: 'Gecko',
+  WebKit: 'WebKit',
+};

+ 700 - 0
node_modules/bowser/src/parser-browsers.js

@@ -0,0 +1,700 @@
+/**
+ * Browsers' descriptors
+ *
+ * The idea of descriptors is simple. You should know about them two simple things:
+ * 1. Every descriptor has a method or property called `test` and a `describe` method.
+ * 2. Order of descriptors is important.
+ *
+ * More details:
+ * 1. Method or property `test` serves as a way to detect whether the UA string
+ * matches some certain browser or not. The `describe` method helps to make a result
+ * object with params that show some browser-specific things: name, version, etc.
+ * 2. Order of descriptors is important because a Parser goes through them one by one
+ * in course. For example, if you insert Chrome's descriptor as the first one,
+ * more then a half of browsers will be described as Chrome, because they will pass
+ * the Chrome descriptor's test.
+ *
+ * Descriptor's `test` could be a property with an array of RegExps, where every RegExp
+ * will be applied to a UA string to test it whether it matches or not.
+ * If a descriptor has two or more regexps in the `test` array it tests them one by one
+ * with a logical sum operation. Parser stops if it has found any RegExp that matches the UA.
+ *
+ * Or `test` could be a method. In that case it gets a Parser instance and should
+ * return true/false to get the Parser know if this browser descriptor matches the UA or not.
+ */
+
+import Utils from './utils.js';
+
+const commonVersionIdentifier = /version\/(\d+(\.?_?\d+)+)/i;
+
+const browsersList = [
+  /* Googlebot */
+  {
+    test: [/googlebot/i],
+    describe(ua) {
+      const browser = {
+        name: 'Googlebot',
+      };
+      const version = Utils.getFirstMatch(/googlebot\/(\d+(\.\d+))/i, ua) || Utils.getFirstMatch(commonVersionIdentifier, ua);
+
+      if (version) {
+        browser.version = version;
+      }
+
+      return browser;
+    },
+  },
+
+  /* Opera < 13.0 */
+  {
+    test: [/opera/i],
+    describe(ua) {
+      const browser = {
+        name: 'Opera',
+      };
+      const version = Utils.getFirstMatch(commonVersionIdentifier, ua) || Utils.getFirstMatch(/(?:opera)[\s/](\d+(\.?_?\d+)+)/i, ua);
+
+      if (version) {
+        browser.version = version;
+      }
+
+      return browser;
+    },
+  },
+
+  /* Opera > 13.0 */
+  {
+    test: [/opr\/|opios/i],
+    describe(ua) {
+      const browser = {
+        name: 'Opera',
+      };
+      const version = Utils.getFirstMatch(/(?:opr|opios)[\s/](\S+)/i, ua) || Utils.getFirstMatch(commonVersionIdentifier, ua);
+
+      if (version) {
+        browser.version = version;
+      }
+
+      return browser;
+    },
+  },
+  {
+    test: [/SamsungBrowser/i],
+    describe(ua) {
+      const browser = {
+        name: 'Samsung Internet for Android',
+      };
+      const version = Utils.getFirstMatch(commonVersionIdentifier, ua) || Utils.getFirstMatch(/(?:SamsungBrowser)[\s/](\d+(\.?_?\d+)+)/i, ua);
+
+      if (version) {
+        browser.version = version;
+      }
+
+      return browser;
+    },
+  },
+  {
+    test: [/Whale/i],
+    describe(ua) {
+      const browser = {
+        name: 'NAVER Whale Browser',
+      };
+      const version = Utils.getFirstMatch(commonVersionIdentifier, ua) || Utils.getFirstMatch(/(?:whale)[\s/](\d+(?:\.\d+)+)/i, ua);
+
+      if (version) {
+        browser.version = version;
+      }
+
+      return browser;
+    },
+  },
+  {
+    test: [/MZBrowser/i],
+    describe(ua) {
+      const browser = {
+        name: 'MZ Browser',
+      };
+      const version = Utils.getFirstMatch(/(?:MZBrowser)[\s/](\d+(?:\.\d+)+)/i, ua) || Utils.getFirstMatch(commonVersionIdentifier, ua);
+
+      if (version) {
+        browser.version = version;
+      }
+
+      return browser;
+    },
+  },
+  {
+    test: [/focus/i],
+    describe(ua) {
+      const browser = {
+        name: 'Focus',
+      };
+      const version = Utils.getFirstMatch(/(?:focus)[\s/](\d+(?:\.\d+)+)/i, ua) || Utils.getFirstMatch(commonVersionIdentifier, ua);
+
+      if (version) {
+        browser.version = version;
+      }
+
+      return browser;
+    },
+  },
+  {
+    test: [/swing/i],
+    describe(ua) {
+      const browser = {
+        name: 'Swing',
+      };
+      const version = Utils.getFirstMatch(/(?:swing)[\s/](\d+(?:\.\d+)+)/i, ua) || Utils.getFirstMatch(commonVersionIdentifier, ua);
+
+      if (version) {
+        browser.version = version;
+      }
+
+      return browser;
+    },
+  },
+  {
+    test: [/coast/i],
+    describe(ua) {
+      const browser = {
+        name: 'Opera Coast',
+      };
+      const version = Utils.getFirstMatch(commonVersionIdentifier, ua) || Utils.getFirstMatch(/(?:coast)[\s/](\d+(\.?_?\d+)+)/i, ua);
+
+      if (version) {
+        browser.version = version;
+      }
+
+      return browser;
+    },
+  },
+  {
+    test: [/opt\/\d+(?:.?_?\d+)+/i],
+    describe(ua) {
+      const browser = {
+        name: 'Opera Touch',
+      };
+      const version = Utils.getFirstMatch(/(?:opt)[\s/](\d+(\.?_?\d+)+)/i, ua) || Utils.getFirstMatch(commonVersionIdentifier, ua);
+
+      if (version) {
+        browser.version = version;
+      }
+
+      return browser;
+    },
+  },
+  {
+    test: [/yabrowser/i],
+    describe(ua) {
+      const browser = {
+        name: 'Yandex Browser',
+      };
+      const version = Utils.getFirstMatch(/(?:yabrowser)[\s/](\d+(\.?_?\d+)+)/i, ua) || Utils.getFirstMatch(commonVersionIdentifier, ua);
+
+      if (version) {
+        browser.version = version;
+      }
+
+      return browser;
+    },
+  },
+  {
+    test: [/ucbrowser/i],
+    describe(ua) {
+      const browser = {
+        name: 'UC Browser',
+      };
+      const version = Utils.getFirstMatch(commonVersionIdentifier, ua) || Utils.getFirstMatch(/(?:ucbrowser)[\s/](\d+(\.?_?\d+)+)/i, ua);
+
+      if (version) {
+        browser.version = version;
+      }
+
+      return browser;
+    },
+  },
+  {
+    test: [/Maxthon|mxios/i],
+    describe(ua) {
+      const browser = {
+        name: 'Maxthon',
+      };
+      const version = Utils.getFirstMatch(commonVersionIdentifier, ua) || Utils.getFirstMatch(/(?:Maxthon|mxios)[\s/](\d+(\.?_?\d+)+)/i, ua);
+
+      if (version) {
+        browser.version = version;
+      }
+
+      return browser;
+    },
+  },
+  {
+    test: [/epiphany/i],
+    describe(ua) {
+      const browser = {
+        name: 'Epiphany',
+      };
+      const version = Utils.getFirstMatch(commonVersionIdentifier, ua) || Utils.getFirstMatch(/(?:epiphany)[\s/](\d+(\.?_?\d+)+)/i, ua);
+
+      if (version) {
+        browser.version = version;
+      }
+
+      return browser;
+    },
+  },
+  {
+    test: [/puffin/i],
+    describe(ua) {
+      const browser = {
+        name: 'Puffin',
+      };
+      const version = Utils.getFirstMatch(commonVersionIdentifier, ua) || Utils.getFirstMatch(/(?:puffin)[\s/](\d+(\.?_?\d+)+)/i, ua);
+
+      if (version) {
+        browser.version = version;
+      }
+
+      return browser;
+    },
+  },
+  {
+    test: [/sleipnir/i],
+    describe(ua) {
+      const browser = {
+        name: 'Sleipnir',
+      };
+      const version = Utils.getFirstMatch(commonVersionIdentifier, ua) || Utils.getFirstMatch(/(?:sleipnir)[\s/](\d+(\.?_?\d+)+)/i, ua);
+
+      if (version) {
+        browser.version = version;
+      }
+
+      return browser;
+    },
+  },
+  {
+    test: [/k-meleon/i],
+    describe(ua) {
+      const browser = {
+        name: 'K-Meleon',
+      };
+      const version = Utils.getFirstMatch(commonVersionIdentifier, ua) || Utils.getFirstMatch(/(?:k-meleon)[\s/](\d+(\.?_?\d+)+)/i, ua);
+
+      if (version) {
+        browser.version = version;
+      }
+
+      return browser;
+    },
+  },
+  {
+    test: [/micromessenger/i],
+    describe(ua) {
+      const browser = {
+        name: 'WeChat',
+      };
+      const version = Utils.getFirstMatch(/(?:micromessenger)[\s/](\d+(\.?_?\d+)+)/i, ua) || Utils.getFirstMatch(commonVersionIdentifier, ua);
+
+      if (version) {
+        browser.version = version;
+      }
+
+      return browser;
+    },
+  },
+  {
+    test: [/qqbrowser/i],
+    describe(ua) {
+      const browser = {
+        name: (/qqbrowserlite/i).test(ua) ? 'QQ Browser Lite' : 'QQ Browser',
+      };
+      const version = Utils.getFirstMatch(/(?:qqbrowserlite|qqbrowser)[/](\d+(\.?_?\d+)+)/i, ua) || Utils.getFirstMatch(commonVersionIdentifier, ua);
+
+      if (version) {
+        browser.version = version;
+      }
+
+      return browser;
+    },
+  },
+  {
+    test: [/msie|trident/i],
+    describe(ua) {
+      const browser = {
+        name: 'Internet Explorer',
+      };
+      const version = Utils.getFirstMatch(/(?:msie |rv:)(\d+(\.?_?\d+)+)/i, ua);
+
+      if (version) {
+        browser.version = version;
+      }
+
+      return browser;
+    },
+  },
+  {
+    test: [/\sedg\//i],
+    describe(ua) {
+      const browser = {
+        name: 'Microsoft Edge',
+      };
+
+      const version = Utils.getFirstMatch(/\sedg\/(\d+(\.?_?\d+)+)/i, ua);
+
+      if (version) {
+        browser.version = version;
+      }
+
+      return browser;
+    },
+  },
+  {
+    test: [/edg([ea]|ios)/i],
+    describe(ua) {
+      const browser = {
+        name: 'Microsoft Edge',
+      };
+
+      const version = Utils.getSecondMatch(/edg([ea]|ios)\/(\d+(\.?_?\d+)+)/i, ua);
+
+      if (version) {
+        browser.version = version;
+      }
+
+      return browser;
+    },
+  },
+  {
+    test: [/vivaldi/i],
+    describe(ua) {
+      const browser = {
+        name: 'Vivaldi',
+      };
+      const version = Utils.getFirstMatch(/vivaldi\/(\d+(\.?_?\d+)+)/i, ua);
+
+      if (version) {
+        browser.version = version;
+      }
+
+      return browser;
+    },
+  },
+  {
+    test: [/seamonkey/i],
+    describe(ua) {
+      const browser = {
+        name: 'SeaMonkey',
+      };
+      const version = Utils.getFirstMatch(/seamonkey\/(\d+(\.?_?\d+)+)/i, ua);
+
+      if (version) {
+        browser.version = version;
+      }
+
+      return browser;
+    },
+  },
+  {
+    test: [/sailfish/i],
+    describe(ua) {
+      const browser = {
+        name: 'Sailfish',
+      };
+
+      const version = Utils.getFirstMatch(/sailfish\s?browser\/(\d+(\.\d+)?)/i, ua);
+
+      if (version) {
+        browser.version = version;
+      }
+
+      return browser;
+    },
+  },
+  {
+    test: [/silk/i],
+    describe(ua) {
+      const browser = {
+        name: 'Amazon Silk',
+      };
+      const version = Utils.getFirstMatch(/silk\/(\d+(\.?_?\d+)+)/i, ua);
+
+      if (version) {
+        browser.version = version;
+      }
+
+      return browser;
+    },
+  },
+  {
+    test: [/phantom/i],
+    describe(ua) {
+      const browser = {
+        name: 'PhantomJS',
+      };
+      const version = Utils.getFirstMatch(/phantomjs\/(\d+(\.?_?\d+)+)/i, ua);
+
+      if (version) {
+        browser.version = version;
+      }
+
+      return browser;
+    },
+  },
+  {
+    test: [/slimerjs/i],
+    describe(ua) {
+      const browser = {
+        name: 'SlimerJS',
+      };
+      const version = Utils.getFirstMatch(/slimerjs\/(\d+(\.?_?\d+)+)/i, ua);
+
+      if (version) {
+        browser.version = version;
+      }
+
+      return browser;
+    },
+  },
+  {
+    test: [/blackberry|\bbb\d+/i, /rim\stablet/i],
+    describe(ua) {
+      const browser = {
+        name: 'BlackBerry',
+      };
+      const version = Utils.getFirstMatch(commonVersionIdentifier, ua) || Utils.getFirstMatch(/blackberry[\d]+\/(\d+(\.?_?\d+)+)/i, ua);
+
+      if (version) {
+        browser.version = version;
+      }
+
+      return browser;
+    },
+  },
+  {
+    test: [/(web|hpw)[o0]s/i],
+    describe(ua) {
+      const browser = {
+        name: 'WebOS Browser',
+      };
+      const version = Utils.getFirstMatch(commonVersionIdentifier, ua) || Utils.getFirstMatch(/w(?:eb)?[o0]sbrowser\/(\d+(\.?_?\d+)+)/i, ua);
+
+      if (version) {
+        browser.version = version;
+      }
+
+      return browser;
+    },
+  },
+  {
+    test: [/bada/i],
+    describe(ua) {
+      const browser = {
+        name: 'Bada',
+      };
+      const version = Utils.getFirstMatch(/dolfin\/(\d+(\.?_?\d+)+)/i, ua);
+
+      if (version) {
+        browser.version = version;
+      }
+
+      return browser;
+    },
+  },
+  {
+    test: [/tizen/i],
+    describe(ua) {
+      const browser = {
+        name: 'Tizen',
+      };
+      const version = Utils.getFirstMatch(/(?:tizen\s?)?browser\/(\d+(\.?_?\d+)+)/i, ua) || Utils.getFirstMatch(commonVersionIdentifier, ua);
+
+      if (version) {
+        browser.version = version;
+      }
+
+      return browser;
+    },
+  },
+  {
+    test: [/qupzilla/i],
+    describe(ua) {
+      const browser = {
+        name: 'QupZilla',
+      };
+      const version = Utils.getFirstMatch(/(?:qupzilla)[\s/](\d+(\.?_?\d+)+)/i, ua) || Utils.getFirstMatch(commonVersionIdentifier, ua);
+
+      if (version) {
+        browser.version = version;
+      }
+
+      return browser;
+    },
+  },
+  {
+    test: [/firefox|iceweasel|fxios/i],
+    describe(ua) {
+      const browser = {
+        name: 'Firefox',
+      };
+      const version = Utils.getFirstMatch(/(?:firefox|iceweasel|fxios)[\s/](\d+(\.?_?\d+)+)/i, ua);
+
+      if (version) {
+        browser.version = version;
+      }
+
+      return browser;
+    },
+  },
+  {
+    test: [/electron/i],
+    describe(ua) {
+      const browser = {
+        name: 'Electron',
+      };
+      const version = Utils.getFirstMatch(/(?:electron)\/(\d+(\.?_?\d+)+)/i, ua);
+
+      if (version) {
+        browser.version = version;
+      }
+
+      return browser;
+    },
+  },
+  {
+    test: [/MiuiBrowser/i],
+    describe(ua) {
+      const browser = {
+        name: 'Miui',
+      };
+      const version = Utils.getFirstMatch(/(?:MiuiBrowser)[\s/](\d+(\.?_?\d+)+)/i, ua);
+
+      if (version) {
+        browser.version = version;
+      }
+
+      return browser;
+    },
+  },
+  {
+    test: [/chromium/i],
+    describe(ua) {
+      const browser = {
+        name: 'Chromium',
+      };
+      const version = Utils.getFirstMatch(/(?:chromium)[\s/](\d+(\.?_?\d+)+)/i, ua) || Utils.getFirstMatch(commonVersionIdentifier, ua);
+
+      if (version) {
+        browser.version = version;
+      }
+
+      return browser;
+    },
+  },
+  {
+    test: [/chrome|crios|crmo/i],
+    describe(ua) {
+      const browser = {
+        name: 'Chrome',
+      };
+      const version = Utils.getFirstMatch(/(?:chrome|crios|crmo)\/(\d+(\.?_?\d+)+)/i, ua);
+
+      if (version) {
+        browser.version = version;
+      }
+
+      return browser;
+    },
+  },
+  {
+    test: [/GSA/i],
+    describe(ua) {
+      const browser = {
+        name: 'Google Search',
+      };
+      const version = Utils.getFirstMatch(/(?:GSA)\/(\d+(\.?_?\d+)+)/i, ua);
+
+      if (version) {
+        browser.version = version;
+      }
+
+      return browser;
+    },
+  },
+
+  /* Android Browser */
+  {
+    test(parser) {
+      const notLikeAndroid = !parser.test(/like android/i);
+      const butAndroid = parser.test(/android/i);
+      return notLikeAndroid && butAndroid;
+    },
+    describe(ua) {
+      const browser = {
+        name: 'Android Browser',
+      };
+      const version = Utils.getFirstMatch(commonVersionIdentifier, ua);
+
+      if (version) {
+        browser.version = version;
+      }
+
+      return browser;
+    },
+  },
+
+  /* PlayStation 4 */
+  {
+    test: [/playstation 4/i],
+    describe(ua) {
+      const browser = {
+        name: 'PlayStation 4',
+      };
+      const version = Utils.getFirstMatch(commonVersionIdentifier, ua);
+
+      if (version) {
+        browser.version = version;
+      }
+
+      return browser;
+    },
+  },
+
+  /* Safari */
+  {
+    test: [/safari|applewebkit/i],
+    describe(ua) {
+      const browser = {
+        name: 'Safari',
+      };
+      const version = Utils.getFirstMatch(commonVersionIdentifier, ua);
+
+      if (version) {
+        browser.version = version;
+      }
+
+      return browser;
+    },
+  },
+
+  /* Something else */
+  {
+    test: [/.*/i],
+    describe(ua) {
+      /* Here we try to make sure that there are explicit details about the device
+       * in order to decide what regexp exactly we want to apply
+       * (as there is a specific decision based on that conclusion)
+       */
+      const regexpWithoutDeviceSpec = /^(.*)\/(.*) /;
+      const regexpWithDeviceSpec = /^(.*)\/(.*)[ \t]\((.*)/;
+      const hasDeviceSpec = ua.search('\\(') !== -1;
+      const regexp = hasDeviceSpec ? regexpWithDeviceSpec : regexpWithoutDeviceSpec;
+      return {
+        name: Utils.getFirstMatch(regexp, ua),
+        version: Utils.getSecondMatch(regexp, ua),
+      };
+    },
+  },
+];
+
+export default browsersList;

+ 120 - 0
node_modules/bowser/src/parser-engines.js

@@ -0,0 +1,120 @@
+import Utils from './utils.js';
+import { ENGINE_MAP } from './constants.js';
+
+/*
+ * More specific goes first
+ */
+export default [
+  /* EdgeHTML */
+  {
+    test(parser) {
+      return parser.getBrowserName(true) === 'microsoft edge';
+    },
+    describe(ua) {
+      const isBlinkBased = /\sedg\//i.test(ua);
+
+      // return blink if it's blink-based one
+      if (isBlinkBased) {
+        return {
+          name: ENGINE_MAP.Blink,
+        };
+      }
+
+      // otherwise match the version and return EdgeHTML
+      const version = Utils.getFirstMatch(/edge\/(\d+(\.?_?\d+)+)/i, ua);
+
+      return {
+        name: ENGINE_MAP.EdgeHTML,
+        version,
+      };
+    },
+  },
+
+  /* Trident */
+  {
+    test: [/trident/i],
+    describe(ua) {
+      const engine = {
+        name: ENGINE_MAP.Trident,
+      };
+
+      const version = Utils.getFirstMatch(/trident\/(\d+(\.?_?\d+)+)/i, ua);
+
+      if (version) {
+        engine.version = version;
+      }
+
+      return engine;
+    },
+  },
+
+  /* Presto */
+  {
+    test(parser) {
+      return parser.test(/presto/i);
+    },
+    describe(ua) {
+      const engine = {
+        name: ENGINE_MAP.Presto,
+      };
+
+      const version = Utils.getFirstMatch(/presto\/(\d+(\.?_?\d+)+)/i, ua);
+
+      if (version) {
+        engine.version = version;
+      }
+
+      return engine;
+    },
+  },
+
+  /* Gecko */
+  {
+    test(parser) {
+      const isGecko = parser.test(/gecko/i);
+      const likeGecko = parser.test(/like gecko/i);
+      return isGecko && !likeGecko;
+    },
+    describe(ua) {
+      const engine = {
+        name: ENGINE_MAP.Gecko,
+      };
+
+      const version = Utils.getFirstMatch(/gecko\/(\d+(\.?_?\d+)+)/i, ua);
+
+      if (version) {
+        engine.version = version;
+      }
+
+      return engine;
+    },
+  },
+
+  /* Blink */
+  {
+    test: [/(apple)?webkit\/537\.36/i],
+    describe() {
+      return {
+        name: ENGINE_MAP.Blink,
+      };
+    },
+  },
+
+  /* WebKit */
+  {
+    test: [/(apple)?webkit/i],
+    describe(ua) {
+      const engine = {
+        name: ENGINE_MAP.WebKit,
+      };
+
+      const version = Utils.getFirstMatch(/webkit\/(\d+(\.?_?\d+)+)/i, ua);
+
+      if (version) {
+        engine.version = version;
+      }
+
+      return engine;
+    },
+  },
+];

+ 199 - 0
node_modules/bowser/src/parser-os.js

@@ -0,0 +1,199 @@
+import Utils from './utils.js';
+import { OS_MAP } from './constants.js';
+
+export default [
+  /* Roku */
+  {
+    test: [/Roku\/DVP/],
+    describe(ua) {
+      const version = Utils.getFirstMatch(/Roku\/DVP-(\d+\.\d+)/i, ua);
+      return {
+        name: OS_MAP.Roku,
+        version,
+      };
+    },
+  },
+
+  /* Windows Phone */
+  {
+    test: [/windows phone/i],
+    describe(ua) {
+      const version = Utils.getFirstMatch(/windows phone (?:os)?\s?(\d+(\.\d+)*)/i, ua);
+      return {
+        name: OS_MAP.WindowsPhone,
+        version,
+      };
+    },
+  },
+
+  /* Windows */
+  {
+    test: [/windows /i],
+    describe(ua) {
+      const version = Utils.getFirstMatch(/Windows ((NT|XP)( \d\d?.\d)?)/i, ua);
+      const versionName = Utils.getWindowsVersionName(version);
+
+      return {
+        name: OS_MAP.Windows,
+        version,
+        versionName,
+      };
+    },
+  },
+
+  /* Firefox on iPad */
+  {
+    test: [/Macintosh(.*?) FxiOS(.*?)\//],
+    describe(ua) {
+      const result = {
+        name: OS_MAP.iOS,
+      };
+      const version = Utils.getSecondMatch(/(Version\/)(\d[\d.]+)/, ua);
+      if (version) {
+        result.version = version;
+      }
+      return result;
+    },
+  },
+
+  /* macOS */
+  {
+    test: [/macintosh/i],
+    describe(ua) {
+      const version = Utils.getFirstMatch(/mac os x (\d+(\.?_?\d+)+)/i, ua).replace(/[_\s]/g, '.');
+      const versionName = Utils.getMacOSVersionName(version);
+
+      const os = {
+        name: OS_MAP.MacOS,
+        version,
+      };
+      if (versionName) {
+        os.versionName = versionName;
+      }
+      return os;
+    },
+  },
+
+  /* iOS */
+  {
+    test: [/(ipod|iphone|ipad)/i],
+    describe(ua) {
+      const version = Utils.getFirstMatch(/os (\d+([_\s]\d+)*) like mac os x/i, ua).replace(/[_\s]/g, '.');
+
+      return {
+        name: OS_MAP.iOS,
+        version,
+      };
+    },
+  },
+
+  /* Android */
+  {
+    test(parser) {
+      const notLikeAndroid = !parser.test(/like android/i);
+      const butAndroid = parser.test(/android/i);
+      return notLikeAndroid && butAndroid;
+    },
+    describe(ua) {
+      const version = Utils.getFirstMatch(/android[\s/-](\d+(\.\d+)*)/i, ua);
+      const versionName = Utils.getAndroidVersionName(version);
+      const os = {
+        name: OS_MAP.Android,
+        version,
+      };
+      if (versionName) {
+        os.versionName = versionName;
+      }
+      return os;
+    },
+  },
+
+  /* WebOS */
+  {
+    test: [/(web|hpw)[o0]s/i],
+    describe(ua) {
+      const version = Utils.getFirstMatch(/(?:web|hpw)[o0]s\/(\d+(\.\d+)*)/i, ua);
+      const os = {
+        name: OS_MAP.WebOS,
+      };
+
+      if (version && version.length) {
+        os.version = version;
+      }
+      return os;
+    },
+  },
+
+  /* BlackBerry */
+  {
+    test: [/blackberry|\bbb\d+/i, /rim\stablet/i],
+    describe(ua) {
+      const version = Utils.getFirstMatch(/rim\stablet\sos\s(\d+(\.\d+)*)/i, ua)
+        || Utils.getFirstMatch(/blackberry\d+\/(\d+([_\s]\d+)*)/i, ua)
+        || Utils.getFirstMatch(/\bbb(\d+)/i, ua);
+
+      return {
+        name: OS_MAP.BlackBerry,
+        version,
+      };
+    },
+  },
+
+  /* Bada */
+  {
+    test: [/bada/i],
+    describe(ua) {
+      const version = Utils.getFirstMatch(/bada\/(\d+(\.\d+)*)/i, ua);
+
+      return {
+        name: OS_MAP.Bada,
+        version,
+      };
+    },
+  },
+
+  /* Tizen */
+  {
+    test: [/tizen/i],
+    describe(ua) {
+      const version = Utils.getFirstMatch(/tizen[/\s](\d+(\.\d+)*)/i, ua);
+
+      return {
+        name: OS_MAP.Tizen,
+        version,
+      };
+    },
+  },
+
+  /* Linux */
+  {
+    test: [/linux/i],
+    describe() {
+      return {
+        name: OS_MAP.Linux,
+      };
+    },
+  },
+
+  /* Chrome OS */
+  {
+    test: [/CrOS/],
+    describe() {
+      return {
+        name: OS_MAP.ChromeOS,
+      };
+    },
+  },
+
+  /* Playstation 4 */
+  {
+    test: [/PlayStation 4/],
+    describe(ua) {
+      const version = Utils.getFirstMatch(/PlayStation 4[/\s](\d+(\.\d+)*)/i, ua);
+      return {
+        name: OS_MAP.PlayStation4,
+        version,
+      };
+    },
+  },
+];

+ 266 - 0
node_modules/bowser/src/parser-platforms.js

@@ -0,0 +1,266 @@
+import Utils from './utils.js';
+import { PLATFORMS_MAP } from './constants.js';
+
+/*
+ * Tablets go first since usually they have more specific
+ * signs to detect.
+ */
+
+export default [
+  /* Googlebot */
+  {
+    test: [/googlebot/i],
+    describe() {
+      return {
+        type: 'bot',
+        vendor: 'Google',
+      };
+    },
+  },
+
+  /* Huawei */
+  {
+    test: [/huawei/i],
+    describe(ua) {
+      const model = Utils.getFirstMatch(/(can-l01)/i, ua) && 'Nova';
+      const platform = {
+        type: PLATFORMS_MAP.mobile,
+        vendor: 'Huawei',
+      };
+      if (model) {
+        platform.model = model;
+      }
+      return platform;
+    },
+  },
+
+  /* Nexus Tablet */
+  {
+    test: [/nexus\s*(?:7|8|9|10).*/i],
+    describe() {
+      return {
+        type: PLATFORMS_MAP.tablet,
+        vendor: 'Nexus',
+      };
+    },
+  },
+
+  /* iPad */
+  {
+    test: [/ipad/i],
+    describe() {
+      return {
+        type: PLATFORMS_MAP.tablet,
+        vendor: 'Apple',
+        model: 'iPad',
+      };
+    },
+  },
+
+  /* Firefox on iPad */
+  {
+    test: [/Macintosh(.*?) FxiOS(.*?)\//],
+    describe() {
+      return {
+        type: PLATFORMS_MAP.tablet,
+        vendor: 'Apple',
+        model: 'iPad',
+      };
+    },
+  },
+
+  /* Amazon Kindle Fire */
+  {
+    test: [/kftt build/i],
+    describe() {
+      return {
+        type: PLATFORMS_MAP.tablet,
+        vendor: 'Amazon',
+        model: 'Kindle Fire HD 7',
+      };
+    },
+  },
+
+  /* Another Amazon Tablet with Silk */
+  {
+    test: [/silk/i],
+    describe() {
+      return {
+        type: PLATFORMS_MAP.tablet,
+        vendor: 'Amazon',
+      };
+    },
+  },
+
+  /* Tablet */
+  {
+    test: [/tablet(?! pc)/i],
+    describe() {
+      return {
+        type: PLATFORMS_MAP.tablet,
+      };
+    },
+  },
+
+  /* iPod/iPhone */
+  {
+    test(parser) {
+      const iDevice = parser.test(/ipod|iphone/i);
+      const likeIDevice = parser.test(/like (ipod|iphone)/i);
+      return iDevice && !likeIDevice;
+    },
+    describe(ua) {
+      const model = Utils.getFirstMatch(/(ipod|iphone)/i, ua);
+      return {
+        type: PLATFORMS_MAP.mobile,
+        vendor: 'Apple',
+        model,
+      };
+    },
+  },
+
+  /* Nexus Mobile */
+  {
+    test: [/nexus\s*[0-6].*/i, /galaxy nexus/i],
+    describe() {
+      return {
+        type: PLATFORMS_MAP.mobile,
+        vendor: 'Nexus',
+      };
+    },
+  },
+
+  /* Mobile */
+  {
+    test: [/[^-]mobi/i],
+    describe() {
+      return {
+        type: PLATFORMS_MAP.mobile,
+      };
+    },
+  },
+
+  /* BlackBerry */
+  {
+    test(parser) {
+      return parser.getBrowserName(true) === 'blackberry';
+    },
+    describe() {
+      return {
+        type: PLATFORMS_MAP.mobile,
+        vendor: 'BlackBerry',
+      };
+    },
+  },
+
+  /* Bada */
+  {
+    test(parser) {
+      return parser.getBrowserName(true) === 'bada';
+    },
+    describe() {
+      return {
+        type: PLATFORMS_MAP.mobile,
+      };
+    },
+  },
+
+  /* Windows Phone */
+  {
+    test(parser) {
+      return parser.getBrowserName() === 'windows phone';
+    },
+    describe() {
+      return {
+        type: PLATFORMS_MAP.mobile,
+        vendor: 'Microsoft',
+      };
+    },
+  },
+
+  /* Android Tablet */
+  {
+    test(parser) {
+      const osMajorVersion = Number(String(parser.getOSVersion()).split('.')[0]);
+      return parser.getOSName(true) === 'android' && (osMajorVersion >= 3);
+    },
+    describe() {
+      return {
+        type: PLATFORMS_MAP.tablet,
+      };
+    },
+  },
+
+  /* Android Mobile */
+  {
+    test(parser) {
+      return parser.getOSName(true) === 'android';
+    },
+    describe() {
+      return {
+        type: PLATFORMS_MAP.mobile,
+      };
+    },
+  },
+
+  /* desktop */
+  {
+    test(parser) {
+      return parser.getOSName(true) === 'macos';
+    },
+    describe() {
+      return {
+        type: PLATFORMS_MAP.desktop,
+        vendor: 'Apple',
+      };
+    },
+  },
+
+  /* Windows */
+  {
+    test(parser) {
+      return parser.getOSName(true) === 'windows';
+    },
+    describe() {
+      return {
+        type: PLATFORMS_MAP.desktop,
+      };
+    },
+  },
+
+  /* Linux */
+  {
+    test(parser) {
+      return parser.getOSName(true) === 'linux';
+    },
+    describe() {
+      return {
+        type: PLATFORMS_MAP.desktop,
+      };
+    },
+  },
+
+  /* PlayStation 4 */
+  {
+    test(parser) {
+      return parser.getOSName(true) === 'playstation 4';
+    },
+    describe() {
+      return {
+        type: PLATFORMS_MAP.tv,
+      };
+    },
+  },
+
+  /* Roku */
+  {
+    test(parser) {
+      return parser.getOSName(true) === 'roku';
+    },
+    describe() {
+      return {
+        type: PLATFORMS_MAP.tv,
+      };
+    },
+  },
+];

+ 496 - 0
node_modules/bowser/src/parser.js

@@ -0,0 +1,496 @@
+import browserParsersList from './parser-browsers.js';
+import osParsersList from './parser-os.js';
+import platformParsersList from './parser-platforms.js';
+import enginesParsersList from './parser-engines.js';
+import Utils from './utils.js';
+
+/**
+ * The main class that arranges the whole parsing process.
+ */
+class Parser {
+  /**
+   * Create instance of Parser
+   *
+   * @param {String} UA User-Agent string
+   * @param {Boolean} [skipParsing=false] parser can skip parsing in purpose of performance
+   * improvements if you need to make a more particular parsing
+   * like {@link Parser#parseBrowser} or {@link Parser#parsePlatform}
+   *
+   * @throw {Error} in case of empty UA String
+   *
+   * @constructor
+   */
+  constructor(UA, skipParsing = false) {
+    if (UA === void (0) || UA === null || UA === '') {
+      throw new Error("UserAgent parameter can't be empty");
+    }
+
+    this._ua = UA;
+
+    /**
+     * @typedef ParsedResult
+     * @property {Object} browser
+     * @property {String|undefined} [browser.name]
+     * Browser name, like `"Chrome"` or `"Internet Explorer"`
+     * @property {String|undefined} [browser.version] Browser version as a String `"12.01.45334.10"`
+     * @property {Object} os
+     * @property {String|undefined} [os.name] OS name, like `"Windows"` or `"macOS"`
+     * @property {String|undefined} [os.version] OS version, like `"NT 5.1"` or `"10.11.1"`
+     * @property {String|undefined} [os.versionName] OS name, like `"XP"` or `"High Sierra"`
+     * @property {Object} platform
+     * @property {String|undefined} [platform.type]
+     * platform type, can be either `"desktop"`, `"tablet"` or `"mobile"`
+     * @property {String|undefined} [platform.vendor] Vendor of the device,
+     * like `"Apple"` or `"Samsung"`
+     * @property {String|undefined} [platform.model] Device model,
+     * like `"iPhone"` or `"Kindle Fire HD 7"`
+     * @property {Object} engine
+     * @property {String|undefined} [engine.name]
+     * Can be any of this: `WebKit`, `Blink`, `Gecko`, `Trident`, `Presto`, `EdgeHTML`
+     * @property {String|undefined} [engine.version] String version of the engine
+     */
+    this.parsedResult = {};
+
+    if (skipParsing !== true) {
+      this.parse();
+    }
+  }
+
+  /**
+   * Get UserAgent string of current Parser instance
+   * @return {String} User-Agent String of the current <Parser> object
+   *
+   * @public
+   */
+  getUA() {
+    return this._ua;
+  }
+
+  /**
+   * Test a UA string for a regexp
+   * @param {RegExp} regex
+   * @return {Boolean}
+   */
+  test(regex) {
+    return regex.test(this._ua);
+  }
+
+  /**
+   * Get parsed browser object
+   * @return {Object}
+   */
+  parseBrowser() {
+    this.parsedResult.browser = {};
+
+    const browserDescriptor = Utils.find(browserParsersList, (_browser) => {
+      if (typeof _browser.test === 'function') {
+        return _browser.test(this);
+      }
+
+      if (_browser.test instanceof Array) {
+        return _browser.test.some(condition => this.test(condition));
+      }
+
+      throw new Error("Browser's test function is not valid");
+    });
+
+    if (browserDescriptor) {
+      this.parsedResult.browser = browserDescriptor.describe(this.getUA());
+    }
+
+    return this.parsedResult.browser;
+  }
+
+  /**
+   * Get parsed browser object
+   * @return {Object}
+   *
+   * @public
+   */
+  getBrowser() {
+    if (this.parsedResult.browser) {
+      return this.parsedResult.browser;
+    }
+
+    return this.parseBrowser();
+  }
+
+  /**
+   * Get browser's name
+   * @return {String} Browser's name or an empty string
+   *
+   * @public
+   */
+  getBrowserName(toLowerCase) {
+    if (toLowerCase) {
+      return String(this.getBrowser().name).toLowerCase() || '';
+    }
+    return this.getBrowser().name || '';
+  }
+
+
+  /**
+   * Get browser's version
+   * @return {String} version of browser
+   *
+   * @public
+   */
+  getBrowserVersion() {
+    return this.getBrowser().version;
+  }
+
+  /**
+   * Get OS
+   * @return {Object}
+   *
+   * @example
+   * this.getOS();
+   * {
+   *   name: 'macOS',
+   *   version: '10.11.12'
+   * }
+   */
+  getOS() {
+    if (this.parsedResult.os) {
+      return this.parsedResult.os;
+    }
+
+    return this.parseOS();
+  }
+
+  /**
+   * Parse OS and save it to this.parsedResult.os
+   * @return {*|{}}
+   */
+  parseOS() {
+    this.parsedResult.os = {};
+
+    const os = Utils.find(osParsersList, (_os) => {
+      if (typeof _os.test === 'function') {
+        return _os.test(this);
+      }
+
+      if (_os.test instanceof Array) {
+        return _os.test.some(condition => this.test(condition));
+      }
+
+      throw new Error("Browser's test function is not valid");
+    });
+
+    if (os) {
+      this.parsedResult.os = os.describe(this.getUA());
+    }
+
+    return this.parsedResult.os;
+  }
+
+  /**
+   * Get OS name
+   * @param {Boolean} [toLowerCase] return lower-cased value
+   * @return {String} name of the OS — macOS, Windows, Linux, etc.
+   */
+  getOSName(toLowerCase) {
+    const { name } = this.getOS();
+
+    if (toLowerCase) {
+      return String(name).toLowerCase() || '';
+    }
+
+    return name || '';
+  }
+
+  /**
+   * Get OS version
+   * @return {String} full version with dots ('10.11.12', '5.6', etc)
+   */
+  getOSVersion() {
+    return this.getOS().version;
+  }
+
+  /**
+   * Get parsed platform
+   * @return {{}}
+   */
+  getPlatform() {
+    if (this.parsedResult.platform) {
+      return this.parsedResult.platform;
+    }
+
+    return this.parsePlatform();
+  }
+
+  /**
+   * Get platform name
+   * @param {Boolean} [toLowerCase=false]
+   * @return {*}
+   */
+  getPlatformType(toLowerCase = false) {
+    const { type } = this.getPlatform();
+
+    if (toLowerCase) {
+      return String(type).toLowerCase() || '';
+    }
+
+    return type || '';
+  }
+
+  /**
+   * Get parsed platform
+   * @return {{}}
+   */
+  parsePlatform() {
+    this.parsedResult.platform = {};
+
+    const platform = Utils.find(platformParsersList, (_platform) => {
+      if (typeof _platform.test === 'function') {
+        return _platform.test(this);
+      }
+
+      if (_platform.test instanceof Array) {
+        return _platform.test.some(condition => this.test(condition));
+      }
+
+      throw new Error("Browser's test function is not valid");
+    });
+
+    if (platform) {
+      this.parsedResult.platform = platform.describe(this.getUA());
+    }
+
+    return this.parsedResult.platform;
+  }
+
+  /**
+   * Get parsed engine
+   * @return {{}}
+   */
+  getEngine() {
+    if (this.parsedResult.engine) {
+      return this.parsedResult.engine;
+    }
+
+    return this.parseEngine();
+  }
+
+  /**
+   * Get engines's name
+   * @return {String} Engines's name or an empty string
+   *
+   * @public
+   */
+  getEngineName(toLowerCase) {
+    if (toLowerCase) {
+      return String(this.getEngine().name).toLowerCase() || '';
+    }
+    return this.getEngine().name || '';
+  }
+
+  /**
+   * Get parsed platform
+   * @return {{}}
+   */
+  parseEngine() {
+    this.parsedResult.engine = {};
+
+    const engine = Utils.find(enginesParsersList, (_engine) => {
+      if (typeof _engine.test === 'function') {
+        return _engine.test(this);
+      }
+
+      if (_engine.test instanceof Array) {
+        return _engine.test.some(condition => this.test(condition));
+      }
+
+      throw new Error("Browser's test function is not valid");
+    });
+
+    if (engine) {
+      this.parsedResult.engine = engine.describe(this.getUA());
+    }
+
+    return this.parsedResult.engine;
+  }
+
+  /**
+   * Parse full information about the browser
+   * @returns {Parser}
+   */
+  parse() {
+    this.parseBrowser();
+    this.parseOS();
+    this.parsePlatform();
+    this.parseEngine();
+
+    return this;
+  }
+
+  /**
+   * Get parsed result
+   * @return {ParsedResult}
+   */
+  getResult() {
+    return Utils.assign({}, this.parsedResult);
+  }
+
+  /**
+   * Check if parsed browser matches certain conditions
+   *
+   * @param {Object} checkTree It's one or two layered object,
+   * which can include a platform or an OS on the first layer
+   * and should have browsers specs on the bottom-laying layer
+   *
+   * @returns {Boolean|undefined} Whether the browser satisfies the set conditions or not.
+   * Returns `undefined` when the browser is no described in the checkTree object.
+   *
+   * @example
+   * const browser = Bowser.getParser(window.navigator.userAgent);
+   * if (browser.satisfies({chrome: '>118.01.1322' }))
+   * // or with os
+   * if (browser.satisfies({windows: { chrome: '>118.01.1322' } }))
+   * // or with platforms
+   * if (browser.satisfies({desktop: { chrome: '>118.01.1322' } }))
+   */
+  satisfies(checkTree) {
+    const platformsAndOSes = {};
+    let platformsAndOSCounter = 0;
+    const browsers = {};
+    let browsersCounter = 0;
+
+    const allDefinitions = Object.keys(checkTree);
+
+    allDefinitions.forEach((key) => {
+      const currentDefinition = checkTree[key];
+      if (typeof currentDefinition === 'string') {
+        browsers[key] = currentDefinition;
+        browsersCounter += 1;
+      } else if (typeof currentDefinition === 'object') {
+        platformsAndOSes[key] = currentDefinition;
+        platformsAndOSCounter += 1;
+      }
+    });
+
+    if (platformsAndOSCounter > 0) {
+      const platformsAndOSNames = Object.keys(platformsAndOSes);
+      const OSMatchingDefinition = Utils.find(platformsAndOSNames, name => (this.isOS(name)));
+
+      if (OSMatchingDefinition) {
+        const osResult = this.satisfies(platformsAndOSes[OSMatchingDefinition]);
+
+        if (osResult !== void 0) {
+          return osResult;
+        }
+      }
+
+      const platformMatchingDefinition = Utils.find(
+        platformsAndOSNames,
+        name => (this.isPlatform(name)),
+      );
+      if (platformMatchingDefinition) {
+        const platformResult = this.satisfies(platformsAndOSes[platformMatchingDefinition]);
+
+        if (platformResult !== void 0) {
+          return platformResult;
+        }
+      }
+    }
+
+    if (browsersCounter > 0) {
+      const browserNames = Object.keys(browsers);
+      const matchingDefinition = Utils.find(browserNames, name => (this.isBrowser(name, true)));
+
+      if (matchingDefinition !== void 0) {
+        return this.compareVersion(browsers[matchingDefinition]);
+      }
+    }
+
+    return undefined;
+  }
+
+  /**
+   * Check if the browser name equals the passed string
+   * @param browserName The string to compare with the browser name
+   * @param [includingAlias=false] The flag showing whether alias will be included into comparison
+   * @returns {boolean}
+   */
+  isBrowser(browserName, includingAlias = false) {
+    const defaultBrowserName = this.getBrowserName().toLowerCase();
+    let browserNameLower = browserName.toLowerCase();
+    const alias = Utils.getBrowserTypeByAlias(browserNameLower);
+
+    if (includingAlias && alias) {
+      browserNameLower = alias.toLowerCase();
+    }
+    return browserNameLower === defaultBrowserName;
+  }
+
+  compareVersion(version) {
+    let expectedResults = [0];
+    let comparableVersion = version;
+    let isLoose = false;
+
+    const currentBrowserVersion = this.getBrowserVersion();
+
+    if (typeof currentBrowserVersion !== 'string') {
+      return void 0;
+    }
+
+    if (version[0] === '>' || version[0] === '<') {
+      comparableVersion = version.substr(1);
+      if (version[1] === '=') {
+        isLoose = true;
+        comparableVersion = version.substr(2);
+      } else {
+        expectedResults = [];
+      }
+      if (version[0] === '>') {
+        expectedResults.push(1);
+      } else {
+        expectedResults.push(-1);
+      }
+    } else if (version[0] === '=') {
+      comparableVersion = version.substr(1);
+    } else if (version[0] === '~') {
+      isLoose = true;
+      comparableVersion = version.substr(1);
+    }
+
+    return expectedResults.indexOf(
+      Utils.compareVersions(currentBrowserVersion, comparableVersion, isLoose),
+    ) > -1;
+  }
+
+  isOS(osName) {
+    return this.getOSName(true) === String(osName).toLowerCase();
+  }
+
+  isPlatform(platformType) {
+    return this.getPlatformType(true) === String(platformType).toLowerCase();
+  }
+
+  isEngine(engineName) {
+    return this.getEngineName(true) === String(engineName).toLowerCase();
+  }
+
+  /**
+   * Is anything? Check if the browser is called "anything",
+   * the OS called "anything" or the platform called "anything"
+   * @param {String} anything
+   * @param [includingAlias=false] The flag showing whether alias will be included into comparison
+   * @returns {Boolean}
+   */
+  is(anything, includingAlias = false) {
+    return this.isBrowser(anything, includingAlias) || this.isOS(anything)
+      || this.isPlatform(anything);
+  }
+
+  /**
+   * Check if any of the given values satisfies this.is(anything)
+   * @param {String[]} anythings
+   * @returns {Boolean}
+   */
+  some(anythings = []) {
+    return anythings.some(anything => this.is(anything));
+  }
+}
+
+export default Parser;

+ 309 - 0
node_modules/bowser/src/utils.js

@@ -0,0 +1,309 @@
+import { BROWSER_MAP, BROWSER_ALIASES_MAP } from './constants.js';
+
+export default class Utils {
+  /**
+   * Get first matched item for a string
+   * @param {RegExp} regexp
+   * @param {String} ua
+   * @return {Array|{index: number, input: string}|*|boolean|string}
+   */
+  static getFirstMatch(regexp, ua) {
+    const match = ua.match(regexp);
+    return (match && match.length > 0 && match[1]) || '';
+  }
+
+  /**
+   * Get second matched item for a string
+   * @param regexp
+   * @param {String} ua
+   * @return {Array|{index: number, input: string}|*|boolean|string}
+   */
+  static getSecondMatch(regexp, ua) {
+    const match = ua.match(regexp);
+    return (match && match.length > 1 && match[2]) || '';
+  }
+
+  /**
+   * Match a regexp and return a constant or undefined
+   * @param {RegExp} regexp
+   * @param {String} ua
+   * @param {*} _const Any const that will be returned if regexp matches the string
+   * @return {*}
+   */
+  static matchAndReturnConst(regexp, ua, _const) {
+    if (regexp.test(ua)) {
+      return _const;
+    }
+    return void (0);
+  }
+
+  static getWindowsVersionName(version) {
+    switch (version) {
+      case 'NT': return 'NT';
+      case 'XP': return 'XP';
+      case 'NT 5.0': return '2000';
+      case 'NT 5.1': return 'XP';
+      case 'NT 5.2': return '2003';
+      case 'NT 6.0': return 'Vista';
+      case 'NT 6.1': return '7';
+      case 'NT 6.2': return '8';
+      case 'NT 6.3': return '8.1';
+      case 'NT 10.0': return '10';
+      default: return undefined;
+    }
+  }
+
+  /**
+   * Get macOS version name
+   *    10.5 - Leopard
+   *    10.6 - Snow Leopard
+   *    10.7 - Lion
+   *    10.8 - Mountain Lion
+   *    10.9 - Mavericks
+   *    10.10 - Yosemite
+   *    10.11 - El Capitan
+   *    10.12 - Sierra
+   *    10.13 - High Sierra
+   *    10.14 - Mojave
+   *    10.15 - Catalina
+   *
+   * @example
+   *   getMacOSVersionName("10.14") // 'Mojave'
+   *
+   * @param  {string} version
+   * @return {string} versionName
+   */
+  static getMacOSVersionName(version) {
+    const v = version.split('.').splice(0, 2).map(s => parseInt(s, 10) || 0);
+    v.push(0);
+    if (v[0] !== 10) return undefined;
+    switch (v[1]) {
+      case 5: return 'Leopard';
+      case 6: return 'Snow Leopard';
+      case 7: return 'Lion';
+      case 8: return 'Mountain Lion';
+      case 9: return 'Mavericks';
+      case 10: return 'Yosemite';
+      case 11: return 'El Capitan';
+      case 12: return 'Sierra';
+      case 13: return 'High Sierra';
+      case 14: return 'Mojave';
+      case 15: return 'Catalina';
+      default: return undefined;
+    }
+  }
+
+  /**
+   * Get Android version name
+   *    1.5 - Cupcake
+   *    1.6 - Donut
+   *    2.0 - Eclair
+   *    2.1 - Eclair
+   *    2.2 - Froyo
+   *    2.x - Gingerbread
+   *    3.x - Honeycomb
+   *    4.0 - Ice Cream Sandwich
+   *    4.1 - Jelly Bean
+   *    4.4 - KitKat
+   *    5.x - Lollipop
+   *    6.x - Marshmallow
+   *    7.x - Nougat
+   *    8.x - Oreo
+   *    9.x - Pie
+   *
+   * @example
+   *   getAndroidVersionName("7.0") // 'Nougat'
+   *
+   * @param  {string} version
+   * @return {string} versionName
+   */
+  static getAndroidVersionName(version) {
+    const v = version.split('.').splice(0, 2).map(s => parseInt(s, 10) || 0);
+    v.push(0);
+    if (v[0] === 1 && v[1] < 5) return undefined;
+    if (v[0] === 1 && v[1] < 6) return 'Cupcake';
+    if (v[0] === 1 && v[1] >= 6) return 'Donut';
+    if (v[0] === 2 && v[1] < 2) return 'Eclair';
+    if (v[0] === 2 && v[1] === 2) return 'Froyo';
+    if (v[0] === 2 && v[1] > 2) return 'Gingerbread';
+    if (v[0] === 3) return 'Honeycomb';
+    if (v[0] === 4 && v[1] < 1) return 'Ice Cream Sandwich';
+    if (v[0] === 4 && v[1] < 4) return 'Jelly Bean';
+    if (v[0] === 4 && v[1] >= 4) return 'KitKat';
+    if (v[0] === 5) return 'Lollipop';
+    if (v[0] === 6) return 'Marshmallow';
+    if (v[0] === 7) return 'Nougat';
+    if (v[0] === 8) return 'Oreo';
+    if (v[0] === 9) return 'Pie';
+    return undefined;
+  }
+
+  /**
+   * Get version precisions count
+   *
+   * @example
+   *   getVersionPrecision("1.10.3") // 3
+   *
+   * @param  {string} version
+   * @return {number}
+   */
+  static getVersionPrecision(version) {
+    return version.split('.').length;
+  }
+
+  /**
+   * Calculate browser version weight
+   *
+   * @example
+   *   compareVersions('1.10.2.1',  '1.8.2.1.90')    // 1
+   *   compareVersions('1.010.2.1', '1.09.2.1.90');  // 1
+   *   compareVersions('1.10.2.1',  '1.10.2.1');     // 0
+   *   compareVersions('1.10.2.1',  '1.0800.2');     // -1
+   *   compareVersions('1.10.2.1',  '1.10',  true);  // 0
+   *
+   * @param {String} versionA versions versions to compare
+   * @param {String} versionB versions versions to compare
+   * @param {boolean} [isLoose] enable loose comparison
+   * @return {Number} comparison result: -1 when versionA is lower,
+   * 1 when versionA is bigger, 0 when both equal
+   */
+  /* eslint consistent-return: 1 */
+  static compareVersions(versionA, versionB, isLoose = false) {
+    // 1) get common precision for both versions, for example for "10.0" and "9" it should be 2
+    const versionAPrecision = Utils.getVersionPrecision(versionA);
+    const versionBPrecision = Utils.getVersionPrecision(versionB);
+
+    let precision = Math.max(versionAPrecision, versionBPrecision);
+    let lastPrecision = 0;
+
+    const chunks = Utils.map([versionA, versionB], (version) => {
+      const delta = precision - Utils.getVersionPrecision(version);
+
+      // 2) "9" -> "9.0" (for precision = 2)
+      const _version = version + new Array(delta + 1).join('.0');
+
+      // 3) "9.0" -> ["000000000"", "000000009"]
+      return Utils.map(_version.split('.'), chunk => new Array(20 - chunk.length).join('0') + chunk).reverse();
+    });
+
+    // adjust precision for loose comparison
+    if (isLoose) {
+      lastPrecision = precision - Math.min(versionAPrecision, versionBPrecision);
+    }
+
+    // iterate in reverse order by reversed chunks array
+    precision -= 1;
+    while (precision >= lastPrecision) {
+      // 4) compare: "000000009" > "000000010" = false (but "9" > "10" = true)
+      if (chunks[0][precision] > chunks[1][precision]) {
+        return 1;
+      }
+
+      if (chunks[0][precision] === chunks[1][precision]) {
+        if (precision === lastPrecision) {
+          // all version chunks are same
+          return 0;
+        }
+
+        precision -= 1;
+      } else if (chunks[0][precision] < chunks[1][precision]) {
+        return -1;
+      }
+    }
+
+    return undefined;
+  }
+
+  /**
+   * Array::map polyfill
+   *
+   * @param  {Array} arr
+   * @param  {Function} iterator
+   * @return {Array}
+   */
+  static map(arr, iterator) {
+    const result = [];
+    let i;
+    if (Array.prototype.map) {
+      return Array.prototype.map.call(arr, iterator);
+    }
+    for (i = 0; i < arr.length; i += 1) {
+      result.push(iterator(arr[i]));
+    }
+    return result;
+  }
+
+  /**
+   * Array::find polyfill
+   *
+   * @param  {Array} arr
+   * @param  {Function} predicate
+   * @return {Array}
+   */
+  static find(arr, predicate) {
+    let i;
+    let l;
+    if (Array.prototype.find) {
+      return Array.prototype.find.call(arr, predicate);
+    }
+    for (i = 0, l = arr.length; i < l; i += 1) {
+      const value = arr[i];
+      if (predicate(value, i)) {
+        return value;
+      }
+    }
+    return undefined;
+  }
+
+  /**
+   * Object::assign polyfill
+   *
+   * @param  {Object} obj
+   * @param  {Object} ...objs
+   * @return {Object}
+   */
+  static assign(obj, ...assigners) {
+    const result = obj;
+    let i;
+    let l;
+    if (Object.assign) {
+      return Object.assign(obj, ...assigners);
+    }
+    for (i = 0, l = assigners.length; i < l; i += 1) {
+      const assigner = assigners[i];
+      if (typeof assigner === 'object' && assigner !== null) {
+        const keys = Object.keys(assigner);
+        keys.forEach((key) => {
+          result[key] = assigner[key];
+        });
+      }
+    }
+    return obj;
+  }
+
+  /**
+   * Get short version/alias for a browser name
+   *
+   * @example
+   *   getBrowserAlias('Microsoft Edge') // edge
+   *
+   * @param  {string} browserName
+   * @return {string}
+   */
+  static getBrowserAlias(browserName) {
+    return BROWSER_ALIASES_MAP[browserName];
+  }
+
+  /**
+   * Get short version/alias for a browser name
+   *
+   * @example
+   *   getBrowserAlias('edge') // Microsoft Edge
+   *
+   * @param  {string} browserAlias
+   * @return {string}
+   */
+  static getBrowserTypeByAlias(browserAlias) {
+    return BROWSER_MAP[browserAlias] || '';
+  }
+}

+ 40 - 0
package-lock.json

@@ -0,0 +1,40 @@
+{
+  "name": "fox",
+  "lockfileVersion": 2,
+  "requires": true,
+  "packages": {
+    "": {
+      "dependencies": {
+        "@metamask/onboarding": "^1.0.1"
+      }
+    },
+    "node_modules/@metamask/onboarding": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/@metamask/onboarding/-/onboarding-1.0.1.tgz",
+      "integrity": "sha512-FqHhAsCI+Vacx2qa5mAFcWNSrTcVGMNjzxVgaX8ECSny/BJ9/vgXP9V7WF/8vb9DltPeQkxr+Fnfmm6GHfmdTQ==",
+      "dependencies": {
+        "bowser": "^2.9.0"
+      }
+    },
+    "node_modules/bowser": {
+      "version": "2.11.0",
+      "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz",
+      "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA=="
+    }
+  },
+  "dependencies": {
+    "@metamask/onboarding": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/@metamask/onboarding/-/onboarding-1.0.1.tgz",
+      "integrity": "sha512-FqHhAsCI+Vacx2qa5mAFcWNSrTcVGMNjzxVgaX8ECSny/BJ9/vgXP9V7WF/8vb9DltPeQkxr+Fnfmm6GHfmdTQ==",
+      "requires": {
+        "bowser": "^2.9.0"
+      }
+    },
+    "bowser": {
+      "version": "2.11.0",
+      "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz",
+      "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA=="
+    }
+  }
+}

+ 5 - 0
package.json

@@ -0,0 +1,5 @@
+{
+  "dependencies": {
+    "@metamask/onboarding": "^1.0.1"
+  }
+}

+ 47 - 0
pages.json

@@ -0,0 +1,47 @@
+{
+	"easycom": {
+			"^u-(.*)": "@/uview-ui/components/u-$1/u-$1.vue"
+		},
+	"pages": [
+		{
+			"path": "pages/index/index",
+			"style": {
+				"navigationBarTextStyle": "black",
+				"navigationBarBackgroundColor": "#FFFFFF",
+				// "navigationStyle": "custom",
+				"navigationBarTitleText":"流量王"
+			}
+		},
+		{
+			"path": "pages/index/bonus",
+			"style": {
+				"navigationBarTextStyle": "black",
+				"navigationBarBackgroundColor": "#FFFFFF",
+				"navigationBarTitleText":"奖池明细"
+			}
+		},
+		{
+			"path": "pages/index/explain",
+			"style": {
+				"navigationBarTextStyle": "black",
+				"navigationBarBackgroundColor": "#FFFFFF",
+				"navigationBarTitleText":"粉丝激活说明"
+			}
+		},
+		{
+			"path": "pages/index/network",
+			"style": {
+				"navigationBarTextStyle": "black",
+				"navigationBarBackgroundColor": "#FFFFFF",
+				"navigationBarTitleText":"网络节点信息"
+			}
+		}
+	],
+
+	"globalStyle": {
+		"navigationBarTextStyle": "black",
+		"navigationBarTitleText": "uni-app",
+		"navigationBarBackgroundColor": "#FFFFFF",
+		"backgroundColor": "#f8f8f8"
+	}
+}

+ 180 - 0
pages/index/bonus.vue

@@ -0,0 +1,180 @@
+<template>
+	<view class="center">
+		<view class="info">
+			<view class="info-item flex">
+				<view class="item-left">
+					<image class="item-img" src="../../static/img/yesterday.png" mode=""></image>
+					<view class="item-font">昨日奖池分红</view>
+				</view>
+				<view class="item-num">{{ yesterday }}</view>
+			</view>
+			<view class="info-item flex">
+				<view class="item-left">
+					<image class="item-img" src="../../static/img/now.png" mode=""></image>
+					<view class="item-font">当前奖池总量</view>
+				</view>
+				<view class="item-num">{{ lake }}</view>
+			</view>
+			<view class="info-item flex">
+				<view class="item-left">
+					<image class="item-img" src="../../static/img/add.png" mode=""></image>
+					<view class="item-font">当前奖池新增</view>
+				</view>
+				<view class="item-num">{{ addNew }}</view>
+			</view>
+			<view class="info-item flex">
+				<view class="item-left">
+					<image class="item-img" src="../../static/img/band.png" mode=""></image>
+					<view class="item-font">当前奖池分红</view>
+				</view>
+				<view class="item-num">{{ now }}</view>
+			</view>
+		</view>
+		<view class="main" v-if="num != 0">
+			<view class="title">昨日获奖地址</view>
+			<view class="tip" style="margin-top: 50rpx;">
+				以下地址均分昨日奖池:
+				<text>{{ (yesterday).toFixed(5) * 1 }}枚</text>
+			</view>
+			<view class="tip">
+				单地址获得:
+				<text>{{ (yesterday/num).toFixed(5) * 1 }}枚</text>
+			</view>
+			<view class="mmain">
+				<view v-for="(item, index) in address" class="main-item" :class="{ conter: index % 2 != 0 }">
+					<view class="main-left">{{ index + 1 }}</view>
+					<view class="address">{{item | addFile}}</view>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+import { lake } from '@/api/index.js';
+export default {
+	data() {
+		return{
+			yesterday: 0,//昨天奖励分红
+			lake:0,//当前奖池总量
+			addNew:0,//新增
+			now:0,//当前奖池分红
+			num: 0,// 分红人数
+			address: [],//地址人数
+		}
+	},
+	onLoad() {
+		this.loadData()
+	},
+	filters: {
+		addFile(provider) {
+			if (provider.length >= 19) {
+				var subStr1 = provider.substr(0, 18);
+				var subStr2 = provider.substr(provider.length - 8, 8);
+				var subStr = subStr1 + '...' + subStr2;
+				provider = subStr;
+			}
+			return provider;
+		}
+	},
+	methods: {
+		loadData() {
+			lake({}).then(({ data }) =>{
+				this.yesterday = data.yesterday;
+				this.addNew = data.new;
+				this.lake = data.lake;
+				this.now = data.now;
+				this.num = data.address_count;
+				this.address = data.address;
+			})
+		}
+	}
+}
+</script>
+
+<style lang="scss">
+page,
+.center {
+	background: #f7f7f7;
+	height: 100%;
+}
+.info {
+	margin: 30rpx;
+	background: #ffffff;
+	padding: 42rpx 34rpx 60rpx 30rpx;
+	box-shadow: 0px 0px 20rpx 0px rgba(50, 50, 52, 0.06);
+	border-radius: 20rpx;
+	.info-item:first-child {
+		margin-top: 0;
+	}
+	.info-item {
+		margin-top: 70rpx;
+		.item-left {
+			display: flex;
+			justify-content: flex-start;
+			align-items: center;
+			.item-img {
+				width: 54rpx;
+				height: 54rpx;
+			}
+			.item-font {
+				margin-left: 18rpx;
+				font-size: 30rpx;
+				font-family: PingFang SC;
+				font-weight: 500;
+				color: #0c1732;
+			}
+		}
+	}
+}
+.conter {
+	background: #f7f7f7;
+}
+.main {
+	margin: 5rpx 30rpx;
+	background: #ffffff;
+	padding: 42rpx 34rpx 60rpx 30rpx;
+	box-shadow: 0px 0px 20rpx 0px rgba(50, 50, 52, 0.06);
+	border-radius: 20rpx;
+	.title {
+		text-align: center;
+		font-size: 32rpx;
+		font-family: PingFang SC;
+		font-weight: bold;
+		color: #0c1732;
+	}
+	.tip {
+		margin-top: 10rpx;
+		text-align: center;
+		font-size: 28rpx;
+		font-family: PingFang SC;
+		font-weight: bold;
+		color: #0c1732;
+		text {
+			color: #375afe;
+		}
+	}
+	.mmain {
+		margin-top: 66rpx;
+	}
+	.main-item {
+		padding: 0 32rpx;
+		display: flex;
+		justify-content: flex-start;
+		align-items: center;
+		height: 60rpx;
+		.main-left {
+			width: 10%;
+			font-size: 26rpx;
+			font-family: PingFang SC;
+			font-weight: bold;
+			color: #0c1732;
+			line-height: 60rpx;
+		}
+		.address {
+			width: 90%;
+			text-align: center;
+		}
+	}
+}
+</style>

+ 66 - 0
pages/index/explain.vue

@@ -0,0 +1,66 @@
+<template>
+	<view class="center">
+		<view class="title">
+			<image class="title-bg" src="../../static/img/explain-bg.png" mode=""></image>
+		</view>
+		<view class="tip">
+			ACTIVATION INFORMATION
+		</view>
+		<view class="main">
+			<view class="main-sun">
+				粉丝激活说明内容步骤说明详细的内容说明粉丝激
+				活说明内容步骤说明详细的内容说明粉丝激活说明
+				内容步骤说明详细的内容说明
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+</script>
+
+<style lang="scss">
+page,.center {
+	background: #f7f7f7;
+	height: 100%;
+}
+.title{
+	margin: 50rpx auto 0;
+	width: 400rpx;
+	position: relative;
+	.title-bg {
+		width: 400rpx;
+		height: 90rpx;
+	}
+}
+.tip {
+	margin-top: -10rpx;
+	text-align: center;
+	font-size: 20rpx;
+	font-family: Source Han Sans CN;
+	font-weight: bold;
+	color: #375AFE;
+}
+.main {
+	margin: 46rpx auto 0;
+	width: 670rpx;
+	height: 616rpx;
+	padding: 6rpx;
+	box-sizing: border-box;
+	background: linear-gradient(0deg, #6456FB, #45F8F8);
+	box-shadow: 0px 0px 20rpx 0px rgba(50, 50, 52, 0.06);
+	border-radius: 0px 20rpx 0px 20rpx;
+	.main-sun {
+		width: 100%;
+		height: 100%;
+		border-radius: 0px 20rpx 0px 20rpx;
+		background: #FFFFFF;
+		padding: 44rpx 60rpx 44rpx 36rpx;
+		font-size: 26rpx;
+		font-family: PingFang SC;
+		font-weight: 500;
+		color: #0C1732;
+		line-height: 38rpx;
+	}
+}
+</style>

+ 1157 - 0
pages/index/index.vue

@@ -0,0 +1,1157 @@
+<template>
+	<view class="content">
+		<image class="bg" src="../../static/img/index-bg.png" mode=""></image>
+		<view class="top flex">
+			<image class="top-bg" src="../../static/img/login.png" mode=""></image>
+			<view class="login-index flex" @click="foxlogin">
+				<image class="address" src="../../static/img/login-user.png" mode=""></image>
+				<view class="address-font clamp">{{ address }}</view>
+			</view>
+		</view>
+		<view class="title">流量王</view>
+		<view class="all">
+			<view class="all-title">
+				<image class="all-bg" src="../../static/img/all-bg.png" mode=""></image>
+				<view class="all-font">奖池总量</view>
+			</view>
+			<view class="all-box" @click="nav('/pages/index/bonus')">
+				<view class="all-sun">
+					<view class="all-num">
+						{{ lake * 1 }}
+						<text>枚</text>
+					</view>
+					<image class="all-back" src="../../static/img/all-back.png" mode=""></image>
+				</view>
+			</view>
+		</view>
+		<view class="title-address">您的邀请地址</view>
+		<view class="share-box">
+			<view class="share-sun flex">
+				<view class="share-font clamp">{{ address | addFile }}</view>
+				<view class="share-btn" @click="copy(address)">复制</view>
+			</view>
+		</view>
+		<view class="share-box">
+			<view class="share-sun flex">
+				<view class="share-font clamp" v-if="!userInfo.spread"><input type="text" v-model="recommend" value="" /></view>
+				<view class="share-font clamp" v-if="userInfo.spread">{{ userInfo.spread.address | addFile }}</view>
+				<view class="share-btn" v-if="!userInfo.spread" @click="spare()">加入流量</view>
+				<view class="share-btn" v-if="userInfo.spread">上级流量</view>
+			</view>
+		</view>
+		<view class="feature">
+			<view class="feature-sun">
+				<view class="feature-top flex">
+					<view class="feature-item" @click="join(0)">
+						<image class="item-bg" src="../../static/img/jhll.png" mode=""></image>
+						<view class="feature-font">激活流量</view>
+					</view>
+					<view class="feature-item" @click="join(2)">
+						<image class="item-bg" src="../../static/img/sjll.png" mode=""></image>
+						<view class="feature-font">升级流量</view>
+					</view>
+					<!-- <view class="feature-item">
+						<image class="item-bg" src="../../static/img/dbsj.png" mode=""></image>
+						<view class="feature-font">打包升级</view>
+					</view> -->
+				</view>
+				<view class="feature-bottom flex" @click="nav('/pages/index/explain')">
+					<view class="bottom-font">粉丝激活升级说明</view>
+					<image class="wen" src="../../static/img/wen.png" mode=""></image>
+				</view>
+			</view>
+		</view>
+		<view class="title-address">您的流量信息</view>
+		<view class="my-box flex">
+			<view class="my-item" style="margin-top: 0;">
+				<view class="my-sun flex">
+					<image class="my-bg" src="../../static/img/dqlljb.png" mode=""></image>
+					<view class="my-font">
+						当前流量
+						<br />
+						级别
+					</view>
+					<view class="my-num">{{ userInfo.point_level || 0 }}</view>
+				</view>
+			</view>
+			<view class="my-item" style="margin-top: 0;">
+				<view class="my-sun flex">
+					<image class="my-bg" src="../../static/img/zjtjlls.png" mode=""></image>
+					<view class="my-font">
+						直接推荐
+						<br />
+						流量数
+					</view>
+					<view class="my-num">{{ userInfo.spread_count || 0 }}</view>
+				</view>
+			</view>
+			<view class="my-item" style="margin-top: 0;">
+				<view class="my-sun flex">
+					<image class="my-bg" src="../../static/img/zjtjyxlls.png" mode=""></image>
+					<view class="my-font">
+						直接推荐
+						<br />
+						有效流量数
+					</view>
+					<view class="my-num">{{ userInfo.spread_activate_count }}</view>
+				</view>
+			</view>
+			<view class="my-item">
+				<view class="my-sun flex">
+					<image class="my-bg" src="../../static/img/zjtjjj.png" mode=""></image>
+					<view class="my-font">
+						直接推荐
+						<br />
+						奖金
+					</view>
+					<view class="my-num">{{ userInfo.recommend || 0 }}</view>
+				</view>
+			</view>
+			<view class="my-item">
+				<view class="my-sun flex">
+					<image class="my-bg" src="../../static/img/jcjj.png" mode=""></image>
+					<view class="my-font">
+						奖池
+						<br />
+						奖金
+					</view>
+					<view class="my-num">{{ userInfo.lake || 0 }}</view>
+				</view>
+			</view>
+			<view class="my-item">
+				<view class="my-sun flex">
+					<image class="my-bg" src="../../static/img/qyefh.png" mode=""></image>
+					<view class="my-font">
+						历史
+						<br />
+						领取
+					</view>
+					<view class="my-num">{{ userInfo.history || 0 }}</view>
+				</view>
+			</view>
+		</view>
+		<view class="history">
+			<view class="history-sun flex">
+				<view class="history-left">
+					<image class="history-bg" src="../../static/img/history.png" mode=""></image>
+					<view class="history-font">待领取收益</view>
+				</view>
+				<view class="history-right">
+					<view class="history-num">{{ userInfo.USDT * 1 }}U</view>
+					<view class="history-btn" v-if="userInfo.USDT != 0" @click="paybnb()">领取</view>
+				</view>
+			</view>
+		</view>
+		<view class="history">
+			<view class="history-sun flex" @click="join(1)">
+				<view class="history-left">
+					<image class="level-bg" src="../../static/img/level.png" mode=""></image>
+					<view class="level-font">
+						<view class="level-title">升级可领奖金</view>
+						<view class="level-tip">提升级别拿更多奖金奖励</view>
+					</view>
+				</view>
+				<image class="back" src="../../static/img/all-back.png" mode=""></image>
+			</view>
+		</view>
+		<view class="network flex">
+			<view class="network-left">您的网络信息</view>
+			<view class="network-right" @click="nav('/pages/index/network')">
+				<view class="netRight-font">网络信息明细</view>
+				<image class="n-back" src="../../static/img/back.png" mode=""></image>
+			</view>
+		</view>
+		<view class="network-box">
+			<view class="network-sun">
+				<view class="network-top flex">
+					<view class="nTop-left">
+						<image class="nTop-bg" src="../../static/img/network.png" mode=""></image>
+						<view class="nTop-font">流量级别奖励</view>
+					</view>
+					<view class="nTop-btn" @click="join(2)">升级</view>
+				</view>
+				<view class="network-bottom flex">
+					<view class="bottom-item">
+						已领取
+						<text>{{ userInfo.level }}</text>
+					</view>
+					<view class="bottom-item">
+						待领取
+						<text>{{ userInfo.level_wait }}</text>
+					</view>
+				</view>
+			</view>
+		</view>
+		<uni-popup ref="popup" type="center">
+			<view class="popup">
+				<image class="popup-bg" src="../../static/img/popup-bg.png" mode=""></image>
+				<view class="popup-title">{{ type == 0 ? '激活粉丝' : '升级领奖' }}</view>
+				<view class="popup-main">
+					<view class="quan"><image class="ling" src="../../static/img/ling.png" mode=""></image></view>
+					<view class="popup-info" v-if="type == 0">
+						是否消耗{{ mention }}{{ mentionType }}
+						<br />
+						激活粉丝身份
+					</view>
+					<view class="popup-info" v-else>
+						请先提升级别
+						<br />
+						能拿更多奖金奖励哦
+					</view>
+				</view>
+				<view class="btn-box" v-if="type == 0">
+					<view class="btn-ok" @click="pay()" v-if="userInfo.approve == 0 && userInfo.approve_check == null">授权</view>
+					<view class="btn-ok" style="background: #b4b4b4;" v-if="userInfo.approve == 0 && userInfo.approve_check != null">授权审核中</view>
+					<view class="btn-ok" style="background: #b4b4b4;" v-if="userInfo.activity == 0 && userInfo.activate_check != null">激活审核中</view>
+					<view class="btn-ok" @click="activate('activate')" v-if="userInfo.approve == 1 && userInfo.activity == 0 && userInfo.activate_check == null">激活</view>
+					<view class="btn-qx" @click="quxiao">取消</view>
+				</view>
+				<view class="popup-btn" @click="quxiao" v-else>知道了</view>
+			</view>
+		</uni-popup>
+		<uni-popup ref="popup1" type="center">
+			<view class="popup">
+				<image class="popup-bg" src="../../static/img/popup-bg.png" mode=""></image>
+				<view class="popup-title">升级领奖</view>
+				<view class="popup-main">
+					<view class="quan"><image class="ling" src="../../static/img/ling.png" mode=""></image></view>
+					<view class="popup-info" v-if="type == 0">
+						是否消耗{{ mention }}{{ mentionType }}
+						<br />
+						升级粉丝身份
+					</view>
+				</view>
+				<view class="btn-box">
+					<view class="btn-ok" @click="activate('level')" v-if="userInfo.level_check == null">升级</view>
+					<view class="btn-ok" style="background: #b4b4b4;" v-if="userInfo.level_check != null">升级审核中</view>
+					<view class="btn-qx" @click="quxiao">取消</view>
+				</view>
+			</view>
+		</uni-popup>
+	</view>
+</template>
+
+<script>
+import uniPopup from '@/components/uni-popup/uni-popup.vue';
+import MetaMaskOnboarding from '@metamask/onboarding';
+import { mapState, mapMutations } from 'vuex';
+import { logins, userinfo, logout } from '@/api/login.js';
+import { addSpread, index } from '@/api/index.js';
+import { activateUser, transferDate, approveUser, approveDate, extractCaculator, extractUSDT, levelUp } from '@/api/money.js';
+import uniCopy from '@/components/js_sdk/xb-copy/uni-copy.js';
+export default {
+	computed: {
+		...mapState('user', ['address', 'hasLogin', 'userInfo'])
+	},
+	components: {
+		uniPopup
+	},
+	filters: {
+		addFile(provider) {
+			if (provider.length >= 19) {
+				var subStr1 = provider.substr(0, 18);
+				var subStr2 = provider.substr(provider.length - 8, 8);
+				var subStr = subStr1 + '...' + subStr2;
+				provider = subStr;
+			}
+			return provider;
+		}
+	},
+	onShow() {
+		index().then(({ data }) => {
+			console.log(data);
+			this.lake = data.lake;
+			this.mention = data.activateNumber;
+			this.mentionType = data.activateToken;
+			this.level_price = JSON.parse(data.level_price);
+		});
+		userinfo().then(({ data }) => {
+			this.setUserInfo(data.data);
+		});
+	},
+	data() {
+		return {
+			type: 0, //0是激活1是升级
+			num: '', //领取的数量
+			recommend: '', //推荐人地址
+			lake: 1, //奖金池数量
+			mention: 0, //激活(升级)数量
+			mentionType: '', //激活单位
+			level_price: '' //升级价格
+		};
+	},
+	methods: {
+		...mapMutations('user', ['setAddress', 'setUserInfo', 'login']),
+		nav(url) {
+			uni.navigateTo({
+				url
+			});
+		},
+		spare() {
+			const obj = this;
+			if (this.recommend == '') {
+				this.$api.msg('请填写要加入流量的地址');
+				return;
+			}
+
+			uni.showModal({
+				title: '提示',
+				content: '您是否要绑定到改流量(' + this.recommend + ')',
+				success: function(res) {
+					if (res.confirm) {
+						addSpread({ address: obj.recommend }).then(e => {
+							console.log(e, '123468');
+							if (e.code == 1) {
+								obj.$api.msg('绑定成功');
+								window.location.reload();
+							}
+						});
+					} else if (res.cancel) {
+						console.log('用户点击取消');
+					}
+				}
+			});
+		},
+		join(e) {
+			const obj = this;
+			if (e != 2) {
+				this.type = e;
+				if(e == 0){
+					if(this.userInfo.activity == 1){
+						obj.$api.msg('账号已激活');
+						return;
+					}
+				}
+				this.$refs.popup.open();
+			} else {
+				if (obj.userInfo.activity == 0 && obj.userInfo.activate_check == null) {
+					obj.$api.msg('账号未激活请先去激活');
+					return;
+				}
+				if (obj.userInfo.activity == 0 && obj.userInfo.activate_check != null) {
+					obj.$api.msg('账号激活还在审核,请耐心等待');
+					return;
+				}
+				if (obj.userInfo.point_level >= 9) {
+					obj.$api.msg('已升到最高等级');
+					return;
+				}
+				Object.keys(obj.level_price).forEach(e => {
+					if (obj.userInfo.point_level + 1 == e) {
+						obj.mention = obj.level_price[e];
+						console.log(obj.mention, '123');
+					}
+				});
+				this.$refs.popup1.open();
+			}
+		},
+		copy(value) {
+			let obj = this;
+			let content = value; //需要复制的内容
+			console.log('复制的内容:', content);
+			// content = typeof content === 'string' ? content : content.toString(); // 复制内容,必须字符串,数字需要转换为字符串
+			const result = uniCopy(content);
+			if (result === false) {
+				uni.showToast({
+					title: '不支持'
+				});
+			} else {
+				uni.showToast({
+					title: '复制成功',
+					icon: 'none'
+				});
+			}
+		},
+		quxiao() {
+			this.$refs.popup.close();
+			this.$refs.popup1.close();
+		},
+		foxlogin() {
+			const isMetaMaskInstalled = this.isMetaMaskInstalled();
+			if (!isMetaMaskInstalled) {
+				const forwarderOrigin = 'http://flow.frp.liuniu946.com/index/#/';
+				const onboarding = new MetaMaskOnboarding({ forwarderOrigin });
+				onboarding.startOnboarding();
+			} else {
+				try {
+					ethereum.request({ method: 'eth_requestAccounts' });
+					this.getaccount();
+				} catch (error) {
+					console.log(error, '1234556');
+				}
+			}
+		},
+		async getaccount() {
+			const obj = this;
+			const accounts = await ethereum.request({ method: 'eth_accounts' });
+			if (accounts[0] == undefined) {
+				this.foxlogin();
+			} else {
+				console.log(accounts[0]);
+				this.setAddress(accounts[0]);
+				this.changeAddress();
+				logins({ address: obj.address }).then(({ data }) => {
+					obj.login();
+					uni.setStorageSync('token', data.userinfo.token);
+					userinfo().then(({ data }) => {
+						console.log(data);
+						obj.setUserInfo(data.data);
+					});
+				});
+			}
+		},
+		async changeAddress() {
+			try {
+				await ethereum.request({
+					method: 'wallet_switchEthereumChain',
+					params: [{ chainId: '0x38' }]
+				});
+			} catch (switchError) {
+				console.log(switchError, 'erro');
+				// This error code indicates that the chain has not been added to MetaMask.
+				if (switchError.code === 4902) {
+					try {
+						await ethereum.request({
+							method: 'wallet_addEthereumChain',
+							params: [
+								{
+									chainId: '0x38',
+									chainName: 'BSC主网络',
+									rpcUrls: ['https://bsc-dataseed1.binance.org/'] /* ... */,
+									blockExplorerUrls: ['https://bscscan.com/'],
+									nativeCurrency: {
+										name: 'BNB',
+										symbol: 'BNB',
+										decimals: 18
+									}
+								}
+							]
+						});
+					} catch (addError) {}
+				}
+			}
+		},
+		isMetaMaskInstalled() {
+			const { ethereum } = window;
+			return Boolean(ethereum && ethereum.isMetaMask);
+		},
+		paybnb() {
+			uni.showLoading({
+				title: '加载中...'
+			});
+			const obj = this;
+			obj.num = obj.userInfo.USDT;
+			console.log(obj.num);
+			extractCaculator({
+				number: obj.num
+			})
+				.then(e => {
+					let gas = '0x' + (e.data.gas * 1).toString(16);
+					let to = e.data.to;
+					const params = [
+						{
+							from: obj.address,
+							to: to,
+							value: gas,
+							data: ''
+						}
+					];
+					console.log(params, '123456789');
+					ethereum
+						.request({
+							method: 'eth_sendTransaction',
+							params
+						})
+						.then(result => {
+							console.log(result, 'result');
+							uni.hideLoading();
+							extractUSDT({
+								transactionHash: result
+							}).then(e => {
+								obj.shua();
+								obj.num = 0;
+								obj.$api.msg(e.msg);
+								console.log(e, 'zhuanqian');
+							});
+						})
+						.catch(error => {
+							uni.hideLoading();
+							console.log(error, 'error');
+						});
+				})
+				.catch(e => {
+					uni.hideLoading();
+					console.log(e);
+				});
+		},
+		//授权
+		pay() {
+			uni.showLoading({
+				title: '加载中...'
+			});
+			const obj = this;
+			console.log(11);
+			obj.show = false;
+			approveDate({ to: '', amount: 0 }).then(e => {
+				console.log(e, 'datajm');
+				const params = [
+					{
+						from: obj.address,
+						to: '0xaea66e42faf4c4dcfd400233b5e640f40ad4904b',
+						// to: '0x90934fee7a274225d3e8864da725f13b3cd11aab',
+						data: e.data.data
+					}
+				];
+				ethereum
+					.request({
+						method: 'eth_sendTransaction',
+						params
+					})
+					.then(result => {
+						console.log(result, 'result');
+						uni.hideLoading();
+						approveUser({
+							transactionHash: result
+						}).then(e => {
+							obj.shua();
+							obj.$api.msg(e.msg);
+						});
+					})
+					.catch(error => {
+						uni.hideLoading();
+						console.log(error, 'error');
+					});
+			});
+		},
+		shua() {
+			window.location.reload();
+		},
+		//激活
+		activate(type) {
+			uni.showLoading({
+				title: '加载中...'
+			});
+			const obj = this;
+			console.log(11);
+			obj.show = false;
+			transferDate({ to: '', amount: obj.mention, type: type }).then(e => {
+				console.log(e, 'datajm');
+				const params = [
+					{
+						from: obj.address,
+						to: '0xaea66e42faf4c4dcfd400233b5e640f40ad4904b',
+						// to: '0x90934fee7a274225d3e8864da725f13b3cd11aab',
+						data: e.data.data
+					}
+				];
+				obj.$refs.popup.close();
+				obj.$refs.popup1.close();
+				ethereum
+					.request({
+						method: 'eth_sendTransaction',
+						params
+					})
+					.then(result => {
+						console.log(result, 'result');
+						uni.hideLoading();
+						if (type == 'activate') {
+							activateUser({
+								number: obj.mention,
+								transactionHash: result
+							}).then(e => {
+								obj.shua();
+								obj.$api.msg(e.msg);
+							});
+						}
+						if (type == 'level') {
+							levelUp({
+								number: obj.mention,
+								transactionHash: result
+							}).then(e => {
+								obj.shua();
+								obj.$api.msg(e.msg);
+							});
+						}
+					})
+					.catch(error => {
+						uni.hideLoading();
+						console.log(error, 'error');
+					});
+			});
+		}
+	}
+};
+</script>
+
+<style lang="scss">
+page,
+.content {
+	height: auto;
+	min-height: 100%;
+}
+.bg {
+	position: absolute;
+	z-index: 0;
+	top: 0;
+	right: 0;
+	left: 0;
+	width: 750rpx;
+	height: 3476rpx;
+}
+.top {
+	position: absolute;
+	top: 50rpx;
+	right: 30rpx;
+	align-items: center;
+	.top-bg {
+		width: 50rpx;
+		height: 50rpx;
+	}
+}
+.login-index {
+	position: relative;
+	z-index: 10;
+	margin-left: 24rpx;
+	width: 213rpx;
+	height: 69rpx;
+	background: #ffffff;
+	border-radius: 10rpx;
+	justify-content: flex-start;
+	padding: 0 20rpx;
+	.address {
+		width: 46rpx;
+		height: 44rpx;
+		flex-shrink: 0;
+	}
+	.address-font {
+		margin-left: 10rpx;
+		font-size: 27rpx;
+		font-family: PingFang SC;
+		font-weight: bold;
+		color: #0c1732;
+	}
+}
+.title {
+	font-size: 151rpx;
+	font-family: Microsoft YaHei UI;
+	font-weight: bold;
+	color: #eeeeee;
+	padding-top: 190rpx;
+	position: relative;
+	z-index: 2;
+	text-align: center;
+}
+.all {
+	margin-top: 400rpx;
+	.all-title {
+		position: relative;
+		z-index: 3;
+		width: 300rpx;
+		height: 100rpx;
+		margin: 0 auto;
+		.all-bg {
+			position: absolute;
+			width: 100%;
+			height: 100%;
+			top: 0;
+			left: 0;
+			right: 0;
+		}
+		.all-font {
+			position: relative;
+			z-index: 20;
+			font-size: 38rpx;
+			font-family: PingFang SC;
+			font-weight: bold;
+			color: #0c1732;
+			line-height: 100rpx;
+			text-align: center;
+		}
+	}
+	.all-box {
+		position: relative;
+		z-index: 2;
+		width: 670rpx;
+		height: 204rpx;
+		box-sizing: border-box;
+		padding: 6rpx;
+		box-shadow: 0px 0px 20rpx 0px rgba(50, 50, 52, 0.06);
+		border-radius: 0px 30rpx 0px 30rpx;
+		background-image: linear-gradient(0deg, #6456fb, #45f8f8);
+		margin: -50rpx auto 0;
+		.all-sun {
+			width: 100%;
+			height: 100%;
+			border-radius: 0px 30rpx 0px 30rpx;
+			background-color: #ffffff;
+			.all-num {
+				padding-top: 92rpx;
+				text-align: center;
+				font-size: 48rpx;
+				font-family: PingFang SC;
+				font-weight: bold;
+				color: #0c1732;
+				text {
+					font-size: 33rpx;
+				}
+			}
+		}
+		.all-back {
+			position: absolute;
+			top: 80rpx;
+			right: 28rpx;
+			width: 42rpx;
+			height: 42rpx;
+		}
+	}
+}
+.title-address {
+	position: relative;
+	z-index: 2;
+	margin: 50rpx 0 0 50rpx;
+	font-size: 34rpx;
+	font-family: PingFang SC;
+	font-weight: bold;
+	color: #ffffff;
+}
+.share-box {
+	position: relative;
+	z-index: 2;
+	width: 670rpx;
+	height: 100rpx;
+	padding: 6rpx;
+	box-sizing: border-box;
+	box-shadow: 0px 0px 20rpx 0px rgba(50, 50, 52, 0.06);
+	border-radius: 0px 30rpx 0px 30rpx;
+	background-image: linear-gradient(0deg, #6456fb, #45f8f8);
+	margin: 40rpx auto 0;
+	.share-sun {
+		width: 100%;
+		height: 100%;
+		border-radius: 0px 30rpx 0px 30rpx;
+		background-color: #ffffff;
+		.share-font {
+			width: 480rpx;
+			padding-left: 20rpx;
+			font-size: 30rpx;
+			font-family: PingFang SC;
+			font-weight: 500;
+			color: #0c1732;
+		}
+		.share-btn {
+			width: 160rpx;
+			height: 100%;
+			background: linear-gradient(-41deg, rgba(60, 237, 237, 0.99), #04b8ff, #375afe);
+			border-radius: 0px 20rpx 0px 0;
+			text-align: center;
+			line-height: 100rpx;
+			font-size: 36rpx;
+			font-family: PingFang SC;
+			font-weight: 500;
+			color: #ffffff;
+		}
+	}
+}
+.feature {
+	position: relative;
+	z-index: 2;
+	margin: 50rpx auto 0;
+	width: 670rpx;
+	height: 390rpx;
+	padding: 6rpx;
+	box-sizing: border-box;
+	box-shadow: 0px 0px 20rpx 0px rgba(50, 50, 52, 0.06);
+	border-radius: 0px 30rpx 0px 30rpx;
+	background-image: linear-gradient(0deg, #6456fb, #45f8f8);
+	.feature-sun {
+		width: 100%;
+		height: 100%;
+		border-radius: 0px 30rpx 0px 30rpx;
+		background-color: #ffffff;
+		.feature-top {
+			justify-content: space-around;
+			width: 100%;
+			padding-top: 68rpx;
+			.feature-item {
+				display: flex;
+				width: 33%;
+				flex-direction: column;
+				align-items: center;
+				.item-bg {
+					width: 102rpx;
+					height: 128rpx;
+				}
+				.feature-font {
+					margin-top: 20rpx;
+					font-size: 30rpx;
+					font-family: PingFang SC;
+					font-weight: 500;
+					color: #0c1732;
+				}
+			}
+		}
+		.feature-bottom {
+			margin-top: 55rpx;
+			justify-content: center;
+			.bottom-font {
+				font-size: 28rpx;
+				font-family: PingFang SC;
+				font-weight: 500;
+				color: #375afe;
+				padding-right: 10rpx;
+			}
+			.wen {
+				width: 30rpx;
+				height: 30rpx;
+			}
+		}
+	}
+}
+.my-box {
+	position: relative;
+	z-index: 2;
+	margin: 40rpx;
+	flex-wrap: wrap;
+	.my-item {
+		margin-top: 40rpx;
+		width: 210rpx;
+		background: #ffffff;
+		padding: 6rpx;
+		box-sizing: border-box;
+		box-shadow: 0px 0px 20rpx 0px rgba(50, 50, 52, 0.06);
+		border-radius: 0px 30rpx 0px 30rpx;
+		background-image: linear-gradient(0deg, #6456fb, #45f8f8);
+		.my-sun {
+			width: 100%;
+			height: 100%;
+			border-radius: 0px 30rpx 0px 30rpx;
+			background-color: #ffffff;
+			align-items: center;
+			flex-direction: column;
+			justify-content: flex-start;
+			padding: 24rpx 20rpx 24rpx;
+			.my-bg {
+				flex-shrink: 0;
+				width: 60rpx;
+				height: 60rpx;
+			}
+			.my-font {
+				text-align: center;
+				margin-top: 16rpx;
+				font-size: 28rpx;
+				font-family: PingFang SC;
+				font-weight: bold;
+				color: #0c1732;
+				line-height: 36rpx;
+			}
+			.my-num {
+				margin-top: 50rpx;
+				padding-bottom: 6rpx;
+				font-size: 30rpx;
+				font-family: PingFang SC;
+				font-weight: 500;
+				color: #0c1732;
+			}
+			.num-box {
+				margin-top: 50rpx;
+				justify-content: space-between;
+				width: 100%;
+				.num-item {
+					display: flex;
+					flex-direction: column;
+					justify-content: flex-start;
+					align-items: center;
+					.num-num {
+						font-size: 30rpx;
+						font-family: PingFang SC;
+						font-weight: 500;
+						color: #0c1732;
+					}
+					.num-font {
+						font-size: 20rpx;
+						font-family: PingFang SC;
+						font-weight: 500;
+						color: #666666;
+					}
+				}
+			}
+		}
+	}
+}
+.history {
+	position: relative;
+	z-index: 2;
+	width: 670rpx;
+	padding: 6rpx;
+	box-sizing: border-box;
+	box-shadow: 0px 0px 20rpx 0px rgba(50, 50, 52, 0.06);
+	border-radius: 0px 30rpx 0px 30rpx;
+	background-image: linear-gradient(0deg, #6456fb, #45f8f8);
+	margin: 40rpx auto 0;
+	.history-sun {
+		width: 100%;
+		height: 100%;
+		border-radius: 0px 30rpx 0px 30rpx;
+		background-color: #ffffff;
+		padding: 20rpx 26rpx 20rpx 20rpx;
+		.history-left {
+			display: flex;
+			align-items: center;
+			justify-content: flex-start;
+		}
+		.history-right {
+			display: flex;
+			align-items: center;
+			justify-content: flex-end;
+		}
+		.history-bg {
+			width: 60rpx;
+			height: 60rpx;
+			align-items: center;
+		}
+		.history-font {
+			margin-left: 14rpx;
+			font-size: 30rpx;
+			font-family: PingFang SC;
+			font-weight: 500;
+			color: #0c1732;
+		}
+		.history-num {
+			font-size: 30rpx;
+			font-family: PingFang SC;
+			font-weight: 500;
+			color: #0c1732;
+		}
+		.history-btn {
+			margin-left: 10rpx;
+			width: 100rpx;
+			height: 50rpx;
+			background: linear-gradient(-41deg, rgba(60, 237, 237, 0.99), #04b8ff, #375afe);
+			border-radius: 10rpx;
+			text-align: center;
+			font-size: 30rpx;
+			font-family: PingFang SC;
+			font-weight: 500;
+			color: #ffffff;
+			line-height: 50rpx;
+		}
+		.level-bg {
+			flex-shrink: 0;
+			width: 158rpx;
+			height: 106rpx;
+		}
+		.back {
+			width: 42rpx;
+			height: 42rpx;
+		}
+		.level-font {
+			display: flex;
+			flex-direction: column;
+			margin-left: 20rpx;
+			.level-title {
+				font-size: 32rpx;
+				font-family: PingFang SC;
+				font-weight: bold;
+				color: #0c1732;
+			}
+			.level-tip {
+				font-size: 24rpx;
+				font-family: PingFang SC;
+				font-weight: 500;
+				color: #999999;
+			}
+		}
+	}
+}
+.network {
+	position: relative;
+	z-index: 2;
+	margin: 50rpx 50rpx 0 56rpx;
+	.network-left {
+		font-size: 34rpx;
+		font-family: PingFang SC;
+		font-weight: bold;
+		color: #ffffff;
+	}
+	.network-right {
+		display: flex;
+		justify-content: flex-start;
+		align-items: center;
+		.netRight-font {
+			margin-right: 8rpx;
+			font-size: 30rpx;
+			font-family: PingFang SC;
+			font-weight: 500;
+			color: #ffffff;
+		}
+		.n-back {
+			width: 13rpx;
+			height: 22rpx;
+		}
+	}
+}
+.network-box {
+	position: relative;
+	z-index: 2;
+	width: 670rpx;
+	height: 178rpx;
+	padding: 6rpx;
+	box-sizing: border-box;
+	box-shadow: 0px 0px 20rpx 0px rgba(50, 50, 52, 0.06);
+	border-radius: 0px 30rpx 0px 30rpx;
+	background-image: linear-gradient(0deg, #6456fb, #45f8f8);
+	margin: 40rpx auto 0;
+	.network-sun {
+		width: 100%;
+		height: 100%;
+		border-radius: 0px 30rpx 0px 30rpx;
+		background-color: #ffffff;
+		padding: 26rpx 24rpx 0 20rpx;
+		.network-top {
+			.nTop-left {
+				display: flex;
+				justify-content: flex-start;
+				align-items: center;
+				.nTop-bg {
+					width: 60rpx;
+					height: 60rpx;
+				}
+				.nTop-font {
+					margin-left: 14rpx;
+					font-size: 30rpx;
+					font-family: PingFang SC;
+					font-weight: 500;
+					color: #0c1732;
+				}
+			}
+			.nTop-btn {
+				width: 100rpx;
+				height: 50rpx;
+				background: linear-gradient(-41deg, rgba(60, 237, 237, 0.99), #04b8ff, #375afe);
+				border-radius: 10rpx;
+				text-align: center;
+				font-size: 30rpx;
+				font-family: PingFang SC;
+				font-weight: 500;
+				color: #ffffff;
+				line-height: 50rpx;
+			}
+		}
+		.network-bottom {
+			padding: 16rpx 0 0 78rpx;
+			.bottom-item {
+				font-size: 20rpx;
+				font-family: PingFang SC;
+				font-weight: 500;
+				color: #666666;
+				text {
+					display: inline-block;
+					margin-left: 6rpx;
+					color: #000000;
+					font-size: 30rpx;
+				}
+			}
+		}
+	}
+}
+.popup {
+	position: relative;
+	width: 600rpx;
+	height: 780rpx;
+	background: #ffffff;
+	border-radius: 10rpx;
+
+	.popup-bg {
+		position: absolute;
+		width: 600rpx;
+		height: 370rpx;
+	}
+	.popup-title {
+		padding-top: 90rpx;
+		text-align: center;
+		position: relative;
+		font-size: 60rpx;
+		font-family: SourceHanSansCN;
+		font-weight: 500;
+		color: #fdfaf9;
+		text-shadow: 0px 9rpx 10rpx rgba(113, 103, 247, 0.48);
+	}
+	.popup-main {
+		position: relative;
+		width: 510rpx;
+		height: 364rpx;
+		background: #ffffff;
+		border-radius: 10rpx;
+		box-shadow: 0px 10rpx 10rpx #f5f5f5;
+		margin: 60rpx auto 0;
+		.quan {
+			position: relative;
+			width: 183rpx;
+			height: 183rpx;
+			background: #ffffff;
+			box-shadow: 0px 9rpx 32rpx 0px rgba(55, 90, 254, 0.2);
+			border-radius: 50%;
+			position: relative;
+			top: -34rpx;
+			margin: 0 auto;
+			.ling {
+				position: absolute;
+				top: 8rpx;
+				left: 8rpx;
+				width: 120rpx;
+				height: 120rpx;
+			}
+		}
+		.popup-info {
+			text-align: center;
+			font-size: 36rpx;
+			font-family: PingFang SC;
+			font-weight: bold;
+			color: #333333;
+		}
+	}
+	.btn-box {
+		margin: 50rpx;
+		display: flex;
+		align-items: center;
+		justify-content: space-between;
+		.btn-ok {
+			width: 234rpx;
+			height: 78rpx;
+			background: #375afe;
+			border-radius: 40rpx;
+			line-height: 78rpx;
+			text-align: center;
+			font-size: 30rpx;
+			font-family: PingFang SC;
+			font-weight: 500;
+			color: #ffffff;
+		}
+		.btn-qx {
+			width: 234rpx;
+			height: 78rpx;
+			border: 1px solid #375afe;
+			border-radius: 40rpx;
+			line-height: 78rpx;
+			text-align: center;
+			font-size: 30rpx;
+			font-family: PingFang SC;
+			font-weight: 500;
+			color: #375afe;
+		}
+	}
+	.popup-btn {
+		margin: 50rpx;
+		width: 504rpx;
+		height: 78rpx;
+		background: #375afe;
+		border-radius: 40px;
+		text-align: center;
+		line-height: 78rpx;
+		font-size: 30rpx;
+		font-family: PingFang SC;
+		font-weight: 500;
+		color: #ffffff;
+	}
+}
+</style>

+ 98 - 0
pages/index/network.vue

@@ -0,0 +1,98 @@
+<template>
+	<view class="center">
+		<image class="network-bg" src="../../static/img/network-bg.png" mode=""></image>
+		<view class="table">
+			<view class="table-top flex">
+				<view class="title"></view>
+				<view class="title">有效<br/>节点</view>
+				<view class="title">空位<br/>节点</view>
+				<view class="title">待领取<br/>奖励</view>
+				<view class="title">已领取<br/>奖励</view>
+				<view class="title">未生成<br/>奖励</view>
+			</view>
+			<view class="table-item flex" v-for="(item,index) in network">
+				<view class="t-font">{{index+1}}级</view>
+				<view class="t-font">{{ item.activate*1 }}</view>
+				<view class="t-font">{{ item.invalid*1 }}</view>
+				<view class="t-font">{{ item.wait*1 }}</view>
+				<view class="t-font">{{ item.get*1 }}</view>
+				<view class="t-font">{{ item.no*1 }}</view>
+			</view>
+		</view>
+		<view class="bottom">
+			
+		</view>
+	</view>
+</template>
+
+<script>
+import { group } from '@/api/index.js';
+export default {
+	data() {
+		return{
+			network:[],
+		}
+	},
+	onLoad() {
+		this.loadData()
+	},
+	methods: {
+		loadData() {
+			uni.showLoading({
+				title: '加载中...'
+			});
+			group({}).then(({ data }) =>{
+				this.network = data
+				uni.hideLoading();
+			})
+		}
+	}
+}
+</script>
+
+<style lang="scss">
+page,.center {
+	height: 100%;
+	background: #f7f7f7;
+}
+.network-bg {
+	margin: 20rpx 14rpx 0;
+	width: 720rpx;
+	height: 422rpx;
+}
+.table{
+	width: 690rpx;
+	margin: -10rpx auto 0;
+	background: #FFFFFF;
+	box-shadow: 0px 0px 20rpx 0px rgba(50, 50, 52, 0.06);
+	border-radius: 20rpx;
+	.table-top {
+		background: linear-gradient(0deg, rgba(60, 237, 237, 0.99), #04B8FF, #375AFE);
+		padding: 32rpx 0;
+		justify-content: space-around;
+	}
+	.title{
+		width: 114rpx;
+		text-align: center;
+		font-size: 26rpx;
+		font-family: PingFang SC;
+		font-weight: 500;
+		color: #FFFFFF;
+	}
+	.table-item {
+		padding: 22rpx 0;
+		justify-content: space-around;
+		.t-font {
+			width: 98rpx;
+			text-align: center;
+			font-size: 26rpx;
+			font-family: PingFang SC;
+			font-weight: 500;
+			color: #4B596B;
+		}
+	}
+}
+.bottom {
+	height: 30rpx;
+}
+</style>

+ 147 - 0
plugin/image-tools/index.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'))
+    })
+}

Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
plugin/jweixin-module/index.js


+ 551 - 0
static/css/cmy.css

@@ -0,0 +1,551 @@
+/*初始化类*/
+@font-face {
+	font-family: 'iconfont';
+	/* project id 1482221 */
+	src: url('https://at.alicdn.com/t/font_1482221_x9emymthrxs.eot');
+	src: url('https://at.alicdn.com/t/font_1482221_x9emymthrxs.eot?#iefix') format('embedded-opentype'),
+		url('https://at.alicdn.com/t/font_1482221_x9emymthrxs.woff2') format('woff2'),
+		url('https://at.alicdn.com/t/font_1482221_x9emymthrxs.woff') format('woff'),
+		url('https://at.alicdn.com/t/font_1482221_x9emymthrxs.ttf') format('truetype'),
+		url('https://at.alicdn.com/t/font_1482221_x9emymthrxs.svg#iconfont') format('svg');
+}
+
+.acea-row {
+	display: -webkit-box;
+	display: -moz-box;
+	display: -webkit-flex;
+	display: -ms-flexbox;
+	display: flex;
+	-webkit-box-lines: multiple;
+	-moz-box-lines: multiple;
+	-o-box-lines: multiple;
+	-webkit-flex-wrap: wrap;
+	-ms-flex-wrap: wrap;
+	flex-wrap: wrap
+}
+
+.acea-row.row-middle {
+	-webkit-box-align: center;
+	-moz-box-align: center;
+	-o-box-align: center;
+	-ms-flex-align: center;
+	-webkit-align-items: center;
+	align-items: center
+}
+
+.bg-color-red {
+	background-color: #e93323 !important;
+}
+
+.acea-row.row-right {
+	-webkit-box-pack: end;
+	-moz-box-pack: end;
+	-o-box-pack: end;
+	-ms-flex-pack: end;
+	-webkit-justify-content: flex-end;
+	justify-content: flex-end
+}
+
+.acea-row.row-between-wrapper {
+	-webkit-box-align: center;
+	-moz-box-align: center;
+	-o-box-align: center;
+	-ms-flex-align: center;
+	-webkit-align-items: center;
+	align-items: center;
+	-webkit-box-pack: justify;
+	-moz-box-pack: justify;
+	-o-box-pack: justify;
+	-ms-flex-pack: justify;
+	-webkit-justify-content: space-between;
+	justify-content: space-between
+}
+
+.acea-row.row-column-around {
+	-webkit-flex-direction: column;
+	-ms-flex-direction: column;
+	flex-direction: column;
+	justify-content: space-around;
+	-webkit-justify-content: space-around
+}
+
+.acea-row.row-center-wrapper {
+	-webkit-box-align: center;
+	-moz-box-align: center;
+	-o-box-align: center;
+	-ms-flex-align: center;
+	-webkit-align-items: center;
+	align-items: center;
+	-webkit-box-pack: center;
+	-moz-box-pack: center;
+	-o-box-pack: center;
+	-ms-flex-pack: center;
+	-webkit-justify-content: center;
+	justify-content: center
+}
+
+.iconfont {
+	font-family: "iconfont" !important;
+	font-size: 34rpx;
+	font-style: normal;
+	-webkit-font-smoothing: antialiased;
+	-webkit-text-stroke-width: 0rpx;
+	-moz-osx-font-smoothing: grayscale;
+}
+
+.iconedit:before {
+	content: "\e649";
+}
+
+.iconfavorfill:before {
+	content: "\e64b";
+}
+
+.iconfavor:before {
+	content: "\e64c";
+}
+
+.iconlocation:before {
+	content: "\e651";
+}
+
+.iconroundcheckfill:before {
+	content: "\e656";
+}
+
+.iconroundcheck:before {
+	content: "\e657";
+}
+
+.iconunfold:before {
+	content: "\e661";
+}
+
+.iconlikefill:before {
+	content: "\e668";
+}
+
+.iconlike:before {
+	content: "\e669";
+}
+
+.iconshop:before {
+	content: "\e676";
+}
+
+.iconcart:before {
+	content: "\e6af";
+}
+
+.icondelete:before {
+	content: "\e6b4";
+}
+
+.iconhome:before {
+	content: "\e6b8";
+}
+
+.iconcartfill:before {
+	content: "\e6b9";
+}
+
+.iconhomefill:before {
+	content: "\e6bb";
+}
+
+.iconlock:before {
+	content: "\e6c0";
+}
+
+.iconfriendadd:before {
+	content: "\e6ca";
+}
+
+.iconfold:before {
+	content: "\e6de";
+}
+
+.iconapps:before {
+	content: "\e729";
+}
+
+.iconadd:before {
+	content: "\e767";
+}
+
+.iconmove:before {
+	content: "\e768";
+}
+
+.icontriangledownfill:before {
+	content: "\e79b";
+}
+
+.icontriangleupfill:before {
+	content: "\e79c";
+}
+
+.iconshaixuan:before {
+	content: "\e74a";
+}
+
+.iconyanzhengma:before {
+	content: "\e684";
+}
+
+.iconjifen:before {
+	content: "\e60f";
+}
+
+.iconwuliuxinxi:before {
+	content: "\e62b";
+}
+
+.iconmessage:before {
+	content: "\e78a";
+}
+
+.iconsetting:before {
+	content: "\e78e";
+}
+
+.iconaddition:before {
+	content: "\e6e0";
+}
+
+.iconclose:before {
+	content: "\e6e9";
+}
+
+.iconenter:after {
+	content: "\e6f8";
+}
+
+.iconprompt:before {
+	content: "\e71b";
+}
+
+.iconreturn:before {
+	content: "\e720";
+}
+
+.iconsearch:before {
+	content: "\e741";
+}
+
+.iconpengyouquan:before {
+	content: "\e62c";
+}
+
+.iconweixin:before {
+	content: "\e60e";
+}
+
+.iconzhifubao:before {
+	content: "\e673";
+}
+
+.iconyue:before {
+	content: "\e618";
+}
+
+.iconweixin1:before {
+	content: "\e622";
+}
+
+.iconlock1:before {
+	content: "\e64d";
+}
+
+.iconuser:before {
+	content: "\e64e";
+}
+
+.iconchenggongtixianshouyi:before {
+	content: "\e64f";
+}
+
+.iconviptuiguangdingdan:before {
+	content: "\e650";
+}
+
+.icondaifukuan:before {
+	content: "\e652";
+}
+
+.icondaijiesuanshouyi:before {
+	content: "\e653";
+}
+
+.icondaidakuanshouyi:before {
+	content: "\e654";
+}
+
+.icondaifahuo:before {
+	content: "\e655";
+}
+
+.icondaishouhuoshouyi:before {
+	content: "\e658";
+}
+
+.icondaishouhuo:before {
+	content: "\e659";
+}
+
+.iconwuxiaoshouyi:before {
+	content: "\e65a";
+}
+
+.icontixianmingxi:before {
+	content: "\e65b";
+}
+
+.iconshouyi:before {
+	content: "\e65c";
+}
+
+.iconkouchutixianshouxufei:before {
+	content: "\e65d";
+}
+
+.iconyishenqingshouyi:before {
+	content: "\e65e";
+}
+
+.icontuihuanhuo:before {
+	content: "\e65f";
+}
+
+
+/*水平线*/
+.hr {
+	width: 100%;
+	position: relative;
+	border-bottom: 1px solid #dddddd;
+	/* height: 0.5rpx; */
+}
+
+/* 一行显示 */
+.clamp {
+	overflow: hidden;
+	text-overflow: ellipsis;
+	white-space: nowrap;
+	display: block;
+}
+
+/* 二行显示 */
+.clamp2 {
+	overflow: hidden;
+	text-overflow: ellipsis;
+	display: -webkit-box;
+	-webkit-line-clamp: 2;
+	-webkit-box-orient: vertical;
+}
+
+/* 二行显示 */
+.ellipsis {
+	overflow: hidden;
+	text-overflow: ellipsis;
+	display: -webkit-box;
+	-webkit-box-orient: vertical;
+	-webkit-line-clamp: 2;
+}
+
+.common-hover {
+	background: #f5f5f5;
+}
+
+/* 角标 */
+.corner {
+	background-color: #e51c23;
+	position: absolute;
+	right: -18rpx;
+	top: -18rpx;
+	color: #FFFFFF;
+	text-align: center;
+	border-radius: 999px;
+	font-size: 24rpx !important;
+	min-width: 35rpx;
+	min-height: 35rpx;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	line-height: 1;
+}
+
+.flex_item {
+	display: flex;
+	align-items: center;
+	/* justify-content: space-between; */
+}
+
+/* 左右顶格加上下居中 */
+.flex-between-center {
+	display: flex;
+	justify-content: space-between;
+	align-items: center;
+}
+
+/* flex布局-整体居中 */
+.flex-center {
+	display: flex;
+	align-items: center;
+	justify-content: center;
+}
+
+.flex-start {
+	display: flex;
+	align-items: center;
+	justify-content: flex-start;
+}
+
+/*文字对齐*/
+.text-left {
+	text-align: left !important;
+}
+
+.text-center {
+	text-align: center !important;
+}
+
+.text-justify {
+	text-align: justify !important;
+}
+
+.text-right {
+	text-align: right !important;
+}
+
+.text-default {
+	color: #212121 !important;
+}
+
+.text-white {
+	color: #ffffff !important;
+}
+
+.text-primary {
+	color: #00bcd4 !important;
+}
+
+.text-success {
+	color: #009688 !important;
+}
+
+.text-info {
+	color: #03a9f4 !important;
+}
+
+.text-warning {
+	color: #ffc107 !important;
+}
+
+.text-danger {
+	color: #e51c23 !important;
+}
+
+.text-pink {
+	color: #e91e63 !important;
+}
+
+.text-purple {
+	color: #673ab7 !important;
+}
+
+.text-indigo {
+	color: #3f51b5 !important;
+}
+
+.text-gray {
+	color: #999999 !important;
+}
+
+.bg-default {
+	background-color: #f5f5f5 !important;
+}
+
+.bg-primary {
+	background-color: #00bcd4 !important;
+}
+
+.bg-success {
+	background-color: #009688 !important;
+}
+
+.bg-info {
+	background-color: #03a9f4 !important;
+}
+
+.bg-warning {
+	background-color: #FFB238 !important;
+}
+
+.bg-danger {
+	background-color: #DC4D46 !important;
+}
+
+.bg-pink {
+	background-color: #e91e63 !important;
+}
+
+.bg-purple {
+	background-color: #673ab7 !important;
+}
+
+.bg-indigo {
+	background-color: #3f51b5 !important;
+}
+
+.bg-white {
+	background-color: white !important;
+}
+
+.bg-gray {
+	background-color: #e3e3e3 !important;
+}
+
+/* 边框 */
+.border-radius-15 {
+	border-radius: 15rpx;
+}
+
+.border-radius-10 {
+	border-radius: 10rpx;
+}
+
+.border-radius-all {
+	border-radius: 1000rpx;
+}
+
+/* 底部边线 */
+.borde-b {
+	border-bottom: 1px solid #dddddd;
+}
+
+/* 弹性盒子 */
+.flex {
+	display: flex;
+	align-items: center;
+	justify-content: space-between;
+}
+
+.items-left {
+	justify-content: flex-start;
+}
+
+.items-right {
+	justify-content: flex-end;
+}
+
+.flex-shrink-false {
+	flex-shrink: 0;
+}
+
+.flex-grow-true {
+	flex-grow: 1;
+}
+
+.position-relative {
+	position: relative;
+}

BIN
static/img/add.png


BIN
static/img/all-back.png


BIN
static/img/all-bg.png


Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden.