xiansin 3 jaren geleden
bovenliggende
commit
f01e2e7df9
100 gewijzigde bestanden met toevoegingen van 17697 en 0 verwijderingen
  1. 3 0
      .gitignore
  2. 18 0
      App.vue
  3. 21 0
      LICENSE
  4. 57 0
      components/app-layout.vue
  5. 25 0
      components/app-link.vue
  6. 68 0
      components/index/app-math-card.vue
  7. 8 0
      core/apiList.js
  8. 4 0
      core/constant.js
  9. 20 0
      core/http.api.js
  10. 47 0
      core/http.interceptor.js
  11. 47 0
      core/jump.js
  12. 152 0
      core/math-imgs.js
  13. 110 0
      core/math-lists.js
  14. 8 0
      core/site.js
  15. 5670 0
      core/unpkg/moment.js
  16. 638 0
      core/unpkg/qs.js
  17. 110 0
      core/util.js
  18. 54 0
      main.js
  19. 139 0
      manifest.json
  20. 13 0
      package-lock.json
  21. 23 0
      package.json
  22. 270 0
      pages.json
  23. 136 0
      pages/formula/angle.vue
  24. 173 0
      pages/formula/anyangle.vue
  25. 315 0
      pages/formula/anytriangle.vue
  26. 185 0
      pages/formula/baoliangwan.vue
  27. 130 0
      pages/formula/bianjindanbian.vue
  28. 140 0
      pages/formula/bianjinshuangbian.vue
  29. 146 0
      pages/formula/downhill.vue
  30. 309 0
      pages/formula/duochengdengwan.vue
  31. 307 0
      pages/formula/duochengzhijiao.vue
  32. 137 0
      pages/formula/guoqiangwan.vue
  33. 184 0
      pages/formula/guozhuwan.vue
  34. 205 0
      pages/formula/normal.vue
  35. 155 0
      pages/formula/rightAngle22.vue
  36. 146 0
      pages/formula/rightAngle30.vue
  37. 138 0
      pages/formula/rightAngle45.vue
  38. 131 0
      pages/formula/rightAngleHorizontal.vue
  39. 130 0
      pages/formula/rightAngleVertical.vue
  40. 137 0
      pages/formula/santongwan.vue
  41. 137 0
      pages/formula/santongwanHorizontal.vue
  42. 162 0
      pages/formula/slice.vue
  43. 146 0
      pages/formula/uphill.vue
  44. 85 0
      pages/income/index.vue
  45. 74 0
      pages/index/index.vue
  46. 170 0
      pages/math/index.vue
  47. 155 0
      pages/my/index.vue
  48. 64 0
      pages/my/member-record.vue
  49. 135 0
      pages/my/member.vue
  50. 113 0
      pages/price/apply.vue
  51. 84 0
      pages/price/index.vue
  52. 210 0
      pages/share/index.vue
  53. 87 0
      pages/share/list.vue
  54. 201 0
      pages/withdraw/index.vue
  55. 363 0
      static/common/js/touch-emulator.js
  56. 18 0
      static/css/common.scss
  57. 266 0
      static/css/flex.scss
  58. 63 0
      static/css/math.scss
  59. 2 0
      static/css/variable.scss
  60. BIN
      static/images/agree.png
  61. BIN
      static/images/bg.png
  62. BIN
      static/images/icons/bridge.png
  63. BIN
      static/images/icons/kefu.png
  64. BIN
      static/images/icons/member.png
  65. BIN
      static/images/icons/share.png
  66. BIN
      static/images/icons/tabs/bridge-HL.png
  67. BIN
      static/images/icons/tabs/bridge.png
  68. BIN
      static/images/icons/tabs/math-HL.png
  69. BIN
      static/images/icons/tabs/math.png
  70. BIN
      static/images/icons/tabs/my-HL.png
  71. BIN
      static/images/icons/tabs/my.png
  72. BIN
      static/images/icons/wechat.png
  73. BIN
      static/images/income.png
  74. BIN
      static/images/list.png
  75. BIN
      static/images/member-bg.png
  76. BIN
      static/images/member-bg1.png
  77. BIN
      static/images/price.png
  78. BIN
      static/images/qrcode.png
  79. BIN
      static/images/share-bg.png
  80. BIN
      static/images/share-qrcode-bg.png
  81. BIN
      static/images/share.png
  82. BIN
      static/images/vip.png
  83. BIN
      static/images/withdraw.png
  84. 27 0
      store/$u.mixin.js
  85. 62 0
      store/index.js
  86. 42 0
      template.h5.html
  87. 10 0
      uni.scss
  88. 21 0
      uview-ui/LICENSE
  89. 106 0
      uview-ui/README.md
  90. 190 0
      uview-ui/components/u-action-sheet/u-action-sheet.vue
  91. 256 0
      uview-ui/components/u-alert-tips/u-alert-tips.vue
  92. 290 0
      uview-ui/components/u-avatar-cropper/u-avatar-cropper.vue
  93. 1265 0
      uview-ui/components/u-avatar-cropper/weCropper.js
  94. 24 0
      uview-ui/components/u-avatar/u-avatar.vue
  95. 153 0
      uview-ui/components/u-back-top/u-back-top.vue
  96. 216 0
      uview-ui/components/u-badge/u-badge.vue
  97. 596 0
      uview-ui/components/u-button/u-button.vue
  98. 639 0
      uview-ui/components/u-calendar/u-calendar.vue
  99. 257 0
      uview-ui/components/u-car-keyboard/u-car-keyboard.vue
  100. 299 0
      uview-ui/components/u-card/u-card.vue

+ 3 - 0
.gitignore

xqd
@@ -0,0 +1,3 @@
+/.idea
+/.hbuilderx
+/unpackage

+ 18 - 0
App.vue

xqd
@@ -0,0 +1,18 @@
+<script>
+	export default {
+        globalData() {
+        },
+		onLaunch(options) {
+		},
+        onShow(options){
+            console.log('app show -->')
+        },
+        onLoad(option) {
+            console.log('app load -->')
+        }
+    }
+</script>
+
+<style lang="scss">
+	@import "uview-ui/index.scss";
+</style>

+ 21 - 0
LICENSE

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

+ 57 - 0
components/app-layout.vue

xqd
@@ -0,0 +1,57 @@
+<template>
+    <view class="app-layout">
+        <scroll-view scroll-y="true" class="scroll-view">
+            <slot></slot>
+        </scroll-view>
+    </view>
+</template>
+
+<script>
+    export default {
+        name: "app-layout",
+        data() {
+            return {}
+        },
+        methods: {
+            login(){
+                uni.login({
+                    provider: 'weixin',
+                    success: res => {
+                        console.log('-->data',res)
+                        uni.getUserInfo({
+                            provider: 'weixin',
+                            success: data =>  {
+                                let params = {
+                                    code: res.code,
+                                    encryptedData: data.encryptedData,
+                                    iv:data.iv,
+                                    signature:data.signature
+                                }
+                                this.$u.api.login(params).then(loginRes => {
+                                    this.$u.vuex(this.$const.USER_TOKEN,loginRes.token)
+                                    this.$u.vuex(this.$const.USER_DATA,loginRes.user)
+                                })
+                            }
+                        });
+                    }
+                });
+            }
+        },
+        created(){
+            if(!this.vuex_user_token){
+                this.login()
+            }
+        }
+    }
+</script>
+
+<style lang="scss" scoped>
+    .app-layout{
+        .scroll-view{
+            padding-bottom: calc(var(--window-bottom) + 20px);
+            /*height: calc(100vh - var(--window-top));*/
+            width: 100%;
+            overflow: hidden;
+        }
+    }
+</style>

+ 25 - 0
components/app-link.vue

xqd
@@ -0,0 +1,25 @@
+<template>
+    <view @click="jump">
+        <slot></slot>
+    </view>
+</template>
+<script>
+    export default {
+        name:'app-link',
+        props:{
+            link:String,
+            type:{
+                type: String,
+                default: 'to'
+            }
+        },
+        methods:{
+            jump(){
+                this.$jump({
+                    url: this.link,
+                    type: this.type
+                })
+            }
+        }
+    }
+</script>

+ 68 - 0
components/index/app-math-card.vue

xqd
@@ -0,0 +1,68 @@
+<template>
+    <view class="app-math-card" @click="handleClick">
+        <view class="cover-image">
+            <u-image width="100%" height="200rpx" :src="coverImage"></u-image>
+        </view>
+        <view class="title">{{title}}</view>
+    </view>
+</template>
+
+<script>
+    export default {
+        name: "app-math-card",
+        props:{
+            coverImage: {
+                type: String,
+                required: true
+            },
+            index:  {
+                type: Number,
+            },
+            title:  {
+                type: String,
+                required: true
+            },
+            customStyle:{
+                type: Object,
+                default() {
+                    return {};
+                }
+            }
+        },
+        data() {
+            return {}
+        },
+        methods: {
+            handleClick(){
+                this.$emit('open',this.index);
+            }
+        },
+        computed:{
+            getStyle() {
+                return Object.assign(this.customStyle);
+            },
+        }
+    }
+</script>
+
+<style lang="scss" scoped>
+    .app-math-card{
+        width: 345rpx;
+        border-radius: 16rpx;
+        overflow: hidden;
+        margin-bottom: 24rpx;
+        box-shadow: 0 4rpx 4rpx rgba(6, 149, 137, 0.15);
+        background: #ffffff;
+        margin-left: 10px;
+        float: left;
+        .cover-image{
+            padding: 0 10rpx;
+        }
+        >.title{
+            padding: 16rpx 0;
+            text-align: center;
+            background: $main-color;
+            color: #ffffff;
+        }
+    }
+</style>

+ 8 - 0
core/apiList.js

xqd
@@ -0,0 +1,8 @@
+/**
+ * Created by JianJia.Zhou<jianjia.zhou> on 2021/3/17.
+ */
+module.exports = {
+    login: '/passport/login',
+    userGet: '/user/get',
+    settingGet: '/setting/get',
+}

+ 4 - 0
core/constant.js

xqd
@@ -0,0 +1,4 @@
+module.exports = {
+   USER_TOKEN: 'vuex_user_token',
+   USER_DATA: 'vuex_user_data',
+}

+ 20 - 0
core/http.api.js

xqd
@@ -0,0 +1,20 @@
+// 如果没有通过拦截器配置域名的话,可以在这里写上完整的URL(加上域名部分)
+const apiList = require("./apiList")
+// 此处第二个参数vm,就是我们在页面使用的this,你可以通过vm获取vuex等操作,更多内容详见uView对拦截器的介绍部分:
+// https://uviewui.com/js/http.html#%E4%BD%95%E8%B0%93%E8%AF%B7%E6%B1%82%E6%8B%A6%E6%88%AA%EF%BC%9F
+const install = (Vue, vm) => {
+
+	const login = (data = {}) => vm.$u.post(apiList.login, data)
+	const userGet = (params = {}) => vm.$u.get(apiList.userGet, params)
+	const settingGet = (params = {}) => vm.$u.get(apiList.settingGet, params)
+	//将各个定义的接口名称,统一放进对象挂载到vm.$u.api(因为vm就是this,也即this.$u.api)下
+	vm.$u.api = {
+		login,
+		userGet,
+		settingGet,
+	};
+}
+
+export default {
+	install
+}

+ 47 - 0
core/http.interceptor.js

xqd
@@ -0,0 +1,47 @@
+// 这里的vm,就是我们在vue文件里面的this,所以我们能在这里获取vuex的变量,比如存放在里面的token
+// 同时,我们也可以在此使用getApp().globalData,如果你把token放在getApp().globalData的话,也是可以使用的
+import md5Libs from "@/uview-ui/libs/function/md5";
+const install = (Vue, vm) => {
+	Vue.prototype.$u.http.setConfig({
+		baseUrl: vm.$site.root+'api', // 请求的本域名
+		// 设置为json,返回后会对数据进行一次JSON.parse()
+		dataType: 'json',
+		showLoading: true, // 是否显示请求中的loading
+		loadingText: '努力加载中...', // 请求loading中的文字提示
+		loadingTime: 300, // 在此时间内,请求还没回来的话,就显示加载中动画,单位ms
+		originalData: false, // 是否在拦截器中返回服务端的原始数据
+		loadingMask: true, // 展示loading的时候,是否给一个透明的蒙层,防止触摸穿透
+		// 配置请求头信息
+		header: {
+			"Content-type":"application/x-www-form-urlencoded",
+			"X-Requested-With":"XMLHttpRequest",
+		},
+	});
+	// 请求拦截,配置Token等参数
+	Vue.prototype.$u.http.interceptor.request = (config) => {
+		if(vm.vuex_user_token){
+			config.header["Authorization"] = vm.vuex_user_token
+		}
+		return config;
+	}
+	// 响应拦截,判断状态码是否通过
+	Vue.prototype.$u.http.interceptor.response = (res) => {
+		// 如果把originalData设置为了true,这里得到将会是服务器返回的所有的原始数据
+		// 判断可能变成了res.statueCode,或者res.data.code之类的,请打印查看结果
+		if(res.code === 200) {
+			// 如果把originalData设置为了true,这里return回什么,this.$u.post的then回调中就会得到什么
+			return res.data;
+		} else {
+			uni.showModal({
+				title: '提示',
+				content: res.msg,
+				showCancel: false,
+			});
+			return false;
+		}
+	}
+}
+
+export default {
+	install
+}

+ 47 - 0
core/jump.js

xqd
@@ -0,0 +1,47 @@
+const jump = function(data) {
+    console.log('-->data',data)
+    switch (data.type) {
+        case 'redirect':
+            uni.redirectTo({
+                url: data.url
+            });
+            break;
+        case 'navigate':
+        case 'to':
+            uni.navigateTo({
+                url: data.url,
+                animationType: "pop-in",
+            });
+            break;
+        case 'tel':
+            if (data.params) {
+                uni.makePhoneCall({
+                    phoneNumber: data.params[0].value
+                });
+            } else if (data.number) {
+                uni.makePhoneCall({
+                    phoneNumber: data.number
+                });
+            }
+            break;
+        case 'map':
+            uni.openLocation({
+                latitude: Number(data.latitude),
+                longitude: Number(data.longitude),
+                name: data.address
+            });
+            break;
+        case 'reload':
+            uni.redirectTo({
+                url: this.$platDiff.routeWithOption()
+            });
+            break;
+        case 'relaunch':
+            uni.reLaunch({
+                url: '/pages/login/login'
+            });
+            break
+    }
+};
+
+export default jump;

+ 152 - 0
core/math-imgs.js

xqd
@@ -0,0 +1,152 @@
+import site from './site'
+// const url = "https://xiansin.oss-cn-shenzhen.aliyuncs.com/sange-bridge";
+const url = site.root+"assets";
+module.exports = {
+    // 万能计算
+    normal: {
+        cover: url+"/images/normal/normal-cover.png",
+        big: url+"/images/normal/normal-big.png",
+        calc: url+"/images/normal/normal-calc.png",
+        slice: url+"/images/normal/normal-slice.png",
+    },
+    // 切口计算
+    slice: {
+        cover: url+"/images/slice/slice-cover.png",
+        big: url+"/images/slice/slice-big.png",
+        calc: url+"/images/slice/slice-calc.png",
+        slice: url+"/images/slice/slice-slice.png",
+    },
+    // 角度计算
+    angle: {
+        cover: url+"/images/angle/angle-cover.png",
+        big: url+"/images/angle/angle-big.png",
+        calc: url+"/images/angle/angle-calc.png",
+        slice: url+"/images/angle/angle-slice.png",
+    },
+    // 任意三角形计算
+    anytriangle: {
+        cover: url+"/images/anytriangle/anytriangle-cover.png",
+        big: url+"/images/anytriangle/anytriangle-big.png",
+        calc: url+"/images/anytriangle/anytriangle-calc.png",
+        slice: url+"/images/anytriangle/anytriangle-slice.png",
+    },
+    // 任意角度计算
+    anyangle: {
+        cover: url+"/images/anyangle/anyangle-cover.png",
+        big: url+"/images/anyangle/anyangle-calc.png",
+        calc: url+"/images/anyangle/anyangle-calc.png",
+        slice: url+"/images/anyangle/anyangle-slice.png",
+    },
+    // 直角弯计算(水平)
+    rightAngleHorizontal: {
+        cover: url+"/images/right-angle-horizontal/right-angle-horizontal-cover.png",
+        big: url+"/images/right-angle-horizontal/right-angle-horizontal-big.png",
+        calc: url+"/images/right-angle-horizontal/right-angle-horizontal-calc.png",
+        slice: url+"/images/right-angle-horizontal/right-angle-horizontal-slice.png",
+    },
+    // 直角弯计算(垂直)
+    rightAngleVertical: {
+        cover: url+"/images/right-angle-vertical/right-angle-vertical-cover.png",
+        big: url+"/images/right-angle-vertical/right-angle-vertical-big.png",
+        calc: url+"/images/right-angle-vertical/right-angle-vertical-calc.png",
+        slice: url+"/images/right-angle-vertical/right-angle-vertical-slice.png",
+    },
+    // 直角弯计算(2*45°)
+    rightAngle45: {
+        cover: url+"/images/right-angle-45/right-angle-45-cover.png",
+        big: url+"/images/right-angle-45/right-angle-45-big.png",
+        calc: url+"/images/right-angle-45/right-angle-45-calc.png",
+        slice: url+"/images/right-angle-45/right-angle-45-slice.png",
+    },
+    // 直角弯计算(3*30°)
+    rightAngle30: {
+        cover: url+"/images/right-angle-30/right-angle-30-cover.png",
+        big: url+"/images/right-angle-30/right-angle-30-big.png",
+        calc: url+"/images/right-angle-30/right-angle-30-calc.png",
+        slice: url+"/images/right-angle-30/right-angle-30-slice.png",
+    },
+    // 直角弯计算(4*22.5°)
+    rightAngle22: {
+        cover: url+"/images/right-angle-22/right-angle-22-cover.png",
+        big: url+"/images/right-angle-22/right-angle-22-big.png",
+        calc: url+"/images/right-angle-22/right-angle-22-calc.png",
+        slice: url+"/images/right-angle-22/right-angle-22-slice.png",
+    },
+    // 坡度计算(上坡)
+    uphill: {
+        cover: url+"/images/uphill/uphill-cover.png",
+        big: url+"/images/uphill/uphill-big.png",
+        calc: url+"/images/uphill/uphill-calc.png",
+        slice: url+"/images/uphill/uphill-slice.png",
+    },
+    // 坡度计算(下坡)
+    downhill: {
+        cover: url+"/images/downhill/downhill-cover.png",
+        big: url+"/images/downhill/downhill-big.png",
+        calc: url+"/images/downhill/downhill-calc.png",
+        slice: url+"/images/downhill/downhill-slice.png",
+    },
+    // 多层直角弯计算
+    duochengzhijiao: {
+        cover: url+"/images/duochengzhijiao/duochengzhijiao-cover.png",
+        big: url+"/images/duochengzhijiao/duochengzhijiao-big.png",
+        calc: url+"/images/duochengzhijiao/duochengzhijiao-calc.png",
+        slice: url+"/images/duochengzhijiao/duochengzhijiao-slice.png",
+    },
+    // 多层等差弯计算
+    duochengdengwan: {
+        cover: url+"/images/duochengdengwan/duochengdengwan-cover.png",
+        big: url+"/images/duochengdengwan/duochengdengwan-big.png",
+        calc: url+"/images/duochengdengwan/duochengdengwan-calc.png",
+        slice: url+"/images/duochengdengwan/duochengdengwan-slice.png",
+    },
+    // 过柱弯计算
+    guozhuwan: {
+        cover: url+"/images/guozhuwan/guozhuwan-cover.png",
+        big: url+"/images/guozhuwan/guozhuwan-big.png",
+        calc: url+"/images/guozhuwan/guozhuwan-calc.png",
+        slice: url+"/images/guozhuwan/guozhuwan-slice.png",
+    },
+    // 抱梁弯计算
+    baoliangwan: {
+        cover: url+"/images/baoliangwan/baoliangwan-cover.png",
+        big: url+"/images/baoliangwan/baoliangwan-big.png",
+        calc: url+"/images/baoliangwan/baoliangwan-calc.png",
+        slice: url+"/images/baoliangwan/baoliangwan-slice.png",
+    },
+    // 过墙弯计算
+    guoqiangwan: {
+        cover: url+"/images/guoqiangwan/guoqiangwan-cover.png",
+        big: url+"/images/guoqiangwan/guoqiangwan-big.png",
+        calc: url+"/images/guoqiangwan/guoqiangwan-calc.png",
+        slice: url+"/images/guoqiangwan/guoqiangwan-slice.png",
+    },
+    // 三通弯计算(水平)
+    santongwanHorizontal: {
+        cover: url+"/images/santongwan-horizontal/santongwan-horizontal-cover.png",
+        big: url+"/images/santongwan-horizontal/santongwan-horizontal-big.png",
+        calc: url+"/images/santongwan-horizontal/santongwan-horizontal-calc.png",
+        slice: url+"/images/santongwan-horizontal/santongwan-horizontal-slice.png",
+    },
+    // 三通弯计算(垂直)
+    santongwan: {
+        cover: url+"/images/santongwan-vertical/santongwan-vertical-cover.png",
+        big: url+"/images/santongwan-vertical/santongwan-vertical-big.png",
+        calc: url+"/images/santongwan-vertical/santongwan-vertical-calc.png",
+        slice: url+"/images/santongwan-vertical/santongwan-vertical-slice.png",
+    },
+    // 变径计算(单边)
+    bianjindanbian: {
+        cover: url+"/images/bianjindanbian/bianjindanbian-cover.png",
+        big: url+"/images/bianjindanbian/bianjindanbian-big.png",
+        calc: url+"/images/bianjindanbian/bianjindanbian-calc.png",
+        slice: url+"/images/bianjindanbian/bianjindanbian-slice.png",
+    },
+    // 变径计算(双边)
+    bianjinshuangbian: {
+        cover: url+"/images/bianjinshuangbian/bianjinshuangbian-cover.png",
+        big: url+"/images/bianjinshuangbian/bianjinshuangbian-big.png",
+        calc: url+"/images/bianjinshuangbian/bianjinshuangbian-calc.png",
+        slice: url+"/images/bianjinshuangbian/bianjinshuangbian-slice.png",
+    },
+}

+ 110 - 0
core/math-lists.js

xqd
@@ -0,0 +1,110 @@
+import mathImg from "./math-imgs"
+
+module.exports = [
+    {
+        title: "万能计算",
+        coverImage: mathImg.normal.cover,
+        url: "/pages/formula/normal"
+    },
+    {
+        title: "切口计算",
+        coverImage: mathImg.slice.cover,
+        url: "/pages/formula/slice"
+    },
+    {
+        title: "角度计算",
+        coverImage: mathImg.angle.cover,
+        url: "/pages/formula/angle"
+    },
+    {
+        title: "任意三角形计算",
+        coverImage: mathImg.anytriangle.cover,
+        url: "/pages/formula/anytriangle"
+    },
+    {
+        title: "任意角度计算",
+        coverImage: mathImg.anyangle.cover,
+        url: "/pages/formula/anyangle"
+    },
+    {
+        title: "直角弯计算(水平)",
+        coverImage: mathImg.rightAngleHorizontal.cover,
+        url: "/pages/formula/rightAngleHorizontal"
+    },
+    {
+        title: "直角弯计算(垂直)",
+        coverImage: mathImg.rightAngleVertical.cover,
+        url: "/pages/formula/rightAngleVertical"
+    },
+    {
+        title: "直角弯计算(2*45°)",
+        coverImage: mathImg.rightAngle45.cover,
+        url: "/pages/formula/rightAngle45"
+    },
+    {
+        title: "直角弯计算(3*30°)",
+        coverImage: mathImg.rightAngle30.cover,
+        url: "/pages/formula/rightAngle30"
+    },
+    {
+        title: "直角弯计算(4*22.5°)",
+        coverImage: mathImg.rightAngle22.cover,
+        url: "/pages/formula/rightAngle22"
+    },
+    {
+        title: "坡度计算(上坡)",
+        coverImage: mathImg.uphill.cover,
+        url: "/pages/formula/uphill"
+    },
+    {
+        title: "坡度计算(下坡)",
+        coverImage: mathImg.downhill.cover,
+        url: "/pages/formula/downhill"
+    },
+    {
+        title: "多层直角弯计算",
+        coverImage: mathImg.duochengzhijiao.cover,
+        url: "/pages/formula/duochengzhijiao"
+    },
+    {
+        title: "多层等差弯计算",
+        coverImage:
+        mathImg.duochengdengwan.cover,
+        url: "/pages/formula/duochengdengwan"
+    },
+    {
+        title: "过柱弯计算",
+        coverImage: mathImg.guozhuwan.cover,
+        url: "/pages/formula/guozhuwan"
+    },
+    {
+        title: "抱梁弯计算",
+        coverImage: mathImg.baoliangwan.cover,
+        url: "/pages/formula/baoliangwan"
+    },
+    {
+        title: "过墙弯计算",
+        coverImage: mathImg.guoqiangwan.cover,
+        url: "/pages/formula/guoqiangwan"
+    },
+    {
+        title: "三通弯计算(水平)",
+        coverImage: mathImg.santongwanHorizontal.cover,
+        url: "/pages/formula/santongwanHorizontal"
+    },
+    {
+        title: "三通弯计算(垂直)",
+        coverImage: mathImg.santongwan.cover,
+        url: "/pages/formula/santongwan"
+    },
+    {
+        title: "变径计算(单边)",
+        coverImage: mathImg.bianjindanbian.cover,
+        url: "/pages/formula/bianjindanbian"
+    },
+    {
+        title: "变径计算(双边)",
+        coverImage: mathImg.bianjinshuangbian.cover,
+        url: "/pages/formula/bianjinshuangbian"
+    }
+]

+ 8 - 0
core/site.js

xqd
@@ -0,0 +1,8 @@
+/**
+ * Created by JianJia.Zhou<jianjia.zhou> on 2021/3/17.
+ */
+const ENV = process.env.NODE_ENV === 'development';
+module.exports = {
+    root: ENV ? 'http://t16.9026.com/' : '',
+    authKey: "!j1^z9hE4sXJdEE$#S1GLYQnWfEBakex",
+}

+ 5670 - 0
core/unpkg/moment.js

xqd
@@ -0,0 +1,5670 @@
+//! moment.js
+//! version : 2.29.1
+//! authors : Tim Wood, Iskren Chernev, Moment.js contributors
+//! license : MIT
+//! momentjs.com
+
+;(function (global, factory) {
+    typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
+    typeof define === 'function' && define.amd ? define(factory) :
+    global.moment = factory()
+}(this, (function () { 'use strict';
+
+    var hookCallback;
+
+    function hooks() {
+        return hookCallback.apply(null, arguments);
+    }
+
+    // This is done to register the method called with moment()
+    // without creating circular dependencies.
+    function setHookCallback(callback) {
+        hookCallback = callback;
+    }
+
+    function isArray(input) {
+        return (
+            input instanceof Array ||
+            Object.prototype.toString.call(input) === '[object Array]'
+        );
+    }
+
+    function isObject(input) {
+        // IE8 will treat undefined and null as object if it wasn't for
+        // input != null
+        return (
+            input != null &&
+            Object.prototype.toString.call(input) === '[object Object]'
+        );
+    }
+
+    function hasOwnProp(a, b) {
+        return Object.prototype.hasOwnProperty.call(a, b);
+    }
+
+    function isObjectEmpty(obj) {
+        if (Object.getOwnPropertyNames) {
+            return Object.getOwnPropertyNames(obj).length === 0;
+        } else {
+            var k;
+            for (k in obj) {
+                if (hasOwnProp(obj, k)) {
+                    return false;
+                }
+            }
+            return true;
+        }
+    }
+
+    function isUndefined(input) {
+        return input === void 0;
+    }
+
+    function isNumber(input) {
+        return (
+            typeof input === 'number' ||
+            Object.prototype.toString.call(input) === '[object Number]'
+        );
+    }
+
+    function isDate(input) {
+        return (
+            input instanceof Date ||
+            Object.prototype.toString.call(input) === '[object Date]'
+        );
+    }
+
+    function map(arr, fn) {
+        var res = [],
+            i;
+        for (i = 0; i < arr.length; ++i) {
+            res.push(fn(arr[i], i));
+        }
+        return res;
+    }
+
+    function extend(a, b) {
+        for (var i in b) {
+            if (hasOwnProp(b, i)) {
+                a[i] = b[i];
+            }
+        }
+
+        if (hasOwnProp(b, 'toString')) {
+            a.toString = b.toString;
+        }
+
+        if (hasOwnProp(b, 'valueOf')) {
+            a.valueOf = b.valueOf;
+        }
+
+        return a;
+    }
+
+    function createUTC(input, format, locale, strict) {
+        return createLocalOrUTC(input, format, locale, strict, true).utc();
+    }
+
+    function defaultParsingFlags() {
+        // We need to deep clone this object.
+        return {
+            empty: false,
+            unusedTokens: [],
+            unusedInput: [],
+            overflow: -2,
+            charsLeftOver: 0,
+            nullInput: false,
+            invalidEra: null,
+            invalidMonth: null,
+            invalidFormat: false,
+            userInvalidated: false,
+            iso: false,
+            parsedDateParts: [],
+            era: null,
+            meridiem: null,
+            rfc2822: false,
+            weekdayMismatch: false,
+        };
+    }
+
+    function getParsingFlags(m) {
+        if (m._pf == null) {
+            m._pf = defaultParsingFlags();
+        }
+        return m._pf;
+    }
+
+    var some;
+    if (Array.prototype.some) {
+        some = Array.prototype.some;
+    } else {
+        some = function (fun) {
+            var t = Object(this),
+                len = t.length >>> 0,
+                i;
+
+            for (i = 0; i < len; i++) {
+                if (i in t && fun.call(this, t[i], i, t)) {
+                    return true;
+                }
+            }
+
+            return false;
+        };
+    }
+
+    function isValid(m) {
+        if (m._isValid == null) {
+            var flags = getParsingFlags(m),
+                parsedParts = some.call(flags.parsedDateParts, function (i) {
+                    return i != null;
+                }),
+                isNowValid =
+                    !isNaN(m._d.getTime()) &&
+                    flags.overflow < 0 &&
+                    !flags.empty &&
+                    !flags.invalidEra &&
+                    !flags.invalidMonth &&
+                    !flags.invalidWeekday &&
+                    !flags.weekdayMismatch &&
+                    !flags.nullInput &&
+                    !flags.invalidFormat &&
+                    !flags.userInvalidated &&
+                    (!flags.meridiem || (flags.meridiem && parsedParts));
+
+            if (m._strict) {
+                isNowValid =
+                    isNowValid &&
+                    flags.charsLeftOver === 0 &&
+                    flags.unusedTokens.length === 0 &&
+                    flags.bigHour === undefined;
+            }
+
+            if (Object.isFrozen == null || !Object.isFrozen(m)) {
+                m._isValid = isNowValid;
+            } else {
+                return isNowValid;
+            }
+        }
+        return m._isValid;
+    }
+
+    function createInvalid(flags) {
+        var m = createUTC(NaN);
+        if (flags != null) {
+            extend(getParsingFlags(m), flags);
+        } else {
+            getParsingFlags(m).userInvalidated = true;
+        }
+
+        return m;
+    }
+
+    // Plugins that add properties should also add the key here (null value),
+    // so we can properly clone ourselves.
+    var momentProperties = (hooks.momentProperties = []),
+        updateInProgress = false;
+
+    function copyConfig(to, from) {
+        var i, prop, val;
+
+        if (!isUndefined(from._isAMomentObject)) {
+            to._isAMomentObject = from._isAMomentObject;
+        }
+        if (!isUndefined(from._i)) {
+            to._i = from._i;
+        }
+        if (!isUndefined(from._f)) {
+            to._f = from._f;
+        }
+        if (!isUndefined(from._l)) {
+            to._l = from._l;
+        }
+        if (!isUndefined(from._strict)) {
+            to._strict = from._strict;
+        }
+        if (!isUndefined(from._tzm)) {
+            to._tzm = from._tzm;
+        }
+        if (!isUndefined(from._isUTC)) {
+            to._isUTC = from._isUTC;
+        }
+        if (!isUndefined(from._offset)) {
+            to._offset = from._offset;
+        }
+        if (!isUndefined(from._pf)) {
+            to._pf = getParsingFlags(from);
+        }
+        if (!isUndefined(from._locale)) {
+            to._locale = from._locale;
+        }
+
+        if (momentProperties.length > 0) {
+            for (i = 0; i < momentProperties.length; i++) {
+                prop = momentProperties[i];
+                val = from[prop];
+                if (!isUndefined(val)) {
+                    to[prop] = val;
+                }
+            }
+        }
+
+        return to;
+    }
+
+    // Moment prototype object
+    function Moment(config) {
+        copyConfig(this, config);
+        this._d = new Date(config._d != null ? config._d.getTime() : NaN);
+        if (!this.isValid()) {
+            this._d = new Date(NaN);
+        }
+        // Prevent infinite loop in case updateOffset creates new moment
+        // objects.
+        if (updateInProgress === false) {
+            updateInProgress = true;
+            hooks.updateOffset(this);
+            updateInProgress = false;
+        }
+    }
+
+    function isMoment(obj) {
+        return (
+            obj instanceof Moment || (obj != null && obj._isAMomentObject != null)
+        );
+    }
+
+    function warn(msg) {
+        if (
+            hooks.suppressDeprecationWarnings === false &&
+            typeof console !== 'undefined' &&
+            console.warn
+        ) {
+            console.warn('Deprecation warning: ' + msg);
+        }
+    }
+
+    function deprecate(msg, fn) {
+        var firstTime = true;
+
+        return extend(function () {
+            if (hooks.deprecationHandler != null) {
+                hooks.deprecationHandler(null, msg);
+            }
+            if (firstTime) {
+                var args = [],
+                    arg,
+                    i,
+                    key;
+                for (i = 0; i < arguments.length; i++) {
+                    arg = '';
+                    if (typeof arguments[i] === 'object') {
+                        arg += '\n[' + i + '] ';
+                        for (key in arguments[0]) {
+                            if (hasOwnProp(arguments[0], key)) {
+                                arg += key + ': ' + arguments[0][key] + ', ';
+                            }
+                        }
+                        arg = arg.slice(0, -2); // Remove trailing comma and space
+                    } else {
+                        arg = arguments[i];
+                    }
+                    args.push(arg);
+                }
+                warn(
+                    msg +
+                        '\nArguments: ' +
+                        Array.prototype.slice.call(args).join('') +
+                        '\n' +
+                        new Error().stack
+                );
+                firstTime = false;
+            }
+            return fn.apply(this, arguments);
+        }, fn);
+    }
+
+    var deprecations = {};
+
+    function deprecateSimple(name, msg) {
+        if (hooks.deprecationHandler != null) {
+            hooks.deprecationHandler(name, msg);
+        }
+        if (!deprecations[name]) {
+            warn(msg);
+            deprecations[name] = true;
+        }
+    }
+
+    hooks.suppressDeprecationWarnings = false;
+    hooks.deprecationHandler = null;
+
+    function isFunction(input) {
+        return (
+            (typeof Function !== 'undefined' && input instanceof Function) ||
+            Object.prototype.toString.call(input) === '[object Function]'
+        );
+    }
+
+    function set(config) {
+        var prop, i;
+        for (i in config) {
+            if (hasOwnProp(config, i)) {
+                prop = config[i];
+                if (isFunction(prop)) {
+                    this[i] = prop;
+                } else {
+                    this['_' + i] = prop;
+                }
+            }
+        }
+        this._config = config;
+        // Lenient ordinal parsing accepts just a number in addition to
+        // number + (possibly) stuff coming from _dayOfMonthOrdinalParse.
+        // TODO: Remove "ordinalParse" fallback in next major release.
+        this._dayOfMonthOrdinalParseLenient = new RegExp(
+            (this._dayOfMonthOrdinalParse.source || this._ordinalParse.source) +
+                '|' +
+                /\d{1,2}/.source
+        );
+    }
+
+    function mergeConfigs(parentConfig, childConfig) {
+        var res = extend({}, parentConfig),
+            prop;
+        for (prop in childConfig) {
+            if (hasOwnProp(childConfig, prop)) {
+                if (isObject(parentConfig[prop]) && isObject(childConfig[prop])) {
+                    res[prop] = {};
+                    extend(res[prop], parentConfig[prop]);
+                    extend(res[prop], childConfig[prop]);
+                } else if (childConfig[prop] != null) {
+                    res[prop] = childConfig[prop];
+                } else {
+                    delete res[prop];
+                }
+            }
+        }
+        for (prop in parentConfig) {
+            if (
+                hasOwnProp(parentConfig, prop) &&
+                !hasOwnProp(childConfig, prop) &&
+                isObject(parentConfig[prop])
+            ) {
+                // make sure changes to properties don't modify parent config
+                res[prop] = extend({}, res[prop]);
+            }
+        }
+        return res;
+    }
+
+    function Locale(config) {
+        if (config != null) {
+            this.set(config);
+        }
+    }
+
+    var keys;
+
+    if (Object.keys) {
+        keys = Object.keys;
+    } else {
+        keys = function (obj) {
+            var i,
+                res = [];
+            for (i in obj) {
+                if (hasOwnProp(obj, i)) {
+                    res.push(i);
+                }
+            }
+            return res;
+        };
+    }
+
+    var defaultCalendar = {
+        sameDay: '[Today at] LT',
+        nextDay: '[Tomorrow at] LT',
+        nextWeek: 'dddd [at] LT',
+        lastDay: '[Yesterday at] LT',
+        lastWeek: '[Last] dddd [at] LT',
+        sameElse: 'L',
+    };
+
+    function calendar(key, mom, now) {
+        var output = this._calendar[key] || this._calendar['sameElse'];
+        return isFunction(output) ? output.call(mom, now) : output;
+    }
+
+    function zeroFill(number, targetLength, forceSign) {
+        var absNumber = '' + Math.abs(number),
+            zerosToFill = targetLength - absNumber.length,
+            sign = number >= 0;
+        return (
+            (sign ? (forceSign ? '+' : '') : '-') +
+            Math.pow(10, Math.max(0, zerosToFill)).toString().substr(1) +
+            absNumber
+        );
+    }
+
+    var formattingTokens = /(\[[^\[]*\])|(\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|N{1,5}|YYYYYY|YYYYY|YYYY|YY|y{2,4}|yo?|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g,
+        localFormattingTokens = /(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g,
+        formatFunctions = {},
+        formatTokenFunctions = {};
+
+    // token:    'M'
+    // padded:   ['MM', 2]
+    // ordinal:  'Mo'
+    // callback: function () { this.month() + 1 }
+    function addFormatToken(token, padded, ordinal, callback) {
+        var func = callback;
+        if (typeof callback === 'string') {
+            func = function () {
+                return this[callback]();
+            };
+        }
+        if (token) {
+            formatTokenFunctions[token] = func;
+        }
+        if (padded) {
+            formatTokenFunctions[padded[0]] = function () {
+                return zeroFill(func.apply(this, arguments), padded[1], padded[2]);
+            };
+        }
+        if (ordinal) {
+            formatTokenFunctions[ordinal] = function () {
+                return this.localeData().ordinal(
+                    func.apply(this, arguments),
+                    token
+                );
+            };
+        }
+    }
+
+    function removeFormattingTokens(input) {
+        if (input.match(/\[[\s\S]/)) {
+            return input.replace(/^\[|\]$/g, '');
+        }
+        return input.replace(/\\/g, '');
+    }
+
+    function makeFormatFunction(format) {
+        var array = format.match(formattingTokens),
+            i,
+            length;
+
+        for (i = 0, length = array.length; i < length; i++) {
+            if (formatTokenFunctions[array[i]]) {
+                array[i] = formatTokenFunctions[array[i]];
+            } else {
+                array[i] = removeFormattingTokens(array[i]);
+            }
+        }
+
+        return function (mom) {
+            var output = '',
+                i;
+            for (i = 0; i < length; i++) {
+                output += isFunction(array[i])
+                    ? array[i].call(mom, format)
+                    : array[i];
+            }
+            return output;
+        };
+    }
+
+    // format date using native date object
+    function formatMoment(m, format) {
+        if (!m.isValid()) {
+            return m.localeData().invalidDate();
+        }
+
+        format = expandFormat(format, m.localeData());
+        formatFunctions[format] =
+            formatFunctions[format] || makeFormatFunction(format);
+
+        return formatFunctions[format](m);
+    }
+
+    function expandFormat(format, locale) {
+        var i = 5;
+
+        function replaceLongDateFormatTokens(input) {
+            return locale.longDateFormat(input) || input;
+        }
+
+        localFormattingTokens.lastIndex = 0;
+        while (i >= 0 && localFormattingTokens.test(format)) {
+            format = format.replace(
+                localFormattingTokens,
+                replaceLongDateFormatTokens
+            );
+            localFormattingTokens.lastIndex = 0;
+            i -= 1;
+        }
+
+        return format;
+    }
+
+    var defaultLongDateFormat = {
+        LTS: 'h:mm:ss A',
+        LT: 'h:mm A',
+        L: 'MM/DD/YYYY',
+        LL: 'MMMM D, YYYY',
+        LLL: 'MMMM D, YYYY h:mm A',
+        LLLL: 'dddd, MMMM D, YYYY h:mm A',
+    };
+
+    function longDateFormat(key) {
+        var format = this._longDateFormat[key],
+            formatUpper = this._longDateFormat[key.toUpperCase()];
+
+        if (format || !formatUpper) {
+            return format;
+        }
+
+        this._longDateFormat[key] = formatUpper
+            .match(formattingTokens)
+            .map(function (tok) {
+                if (
+                    tok === 'MMMM' ||
+                    tok === 'MM' ||
+                    tok === 'DD' ||
+                    tok === 'dddd'
+                ) {
+                    return tok.slice(1);
+                }
+                return tok;
+            })
+            .join('');
+
+        return this._longDateFormat[key];
+    }
+
+    var defaultInvalidDate = 'Invalid date';
+
+    function invalidDate() {
+        return this._invalidDate;
+    }
+
+    var defaultOrdinal = '%d',
+        defaultDayOfMonthOrdinalParse = /\d{1,2}/;
+
+    function ordinal(number) {
+        return this._ordinal.replace('%d', number);
+    }
+
+    var defaultRelativeTime = {
+        future: 'in %s',
+        past: '%s ago',
+        s: 'a few seconds',
+        ss: '%d seconds',
+        m: 'a minute',
+        mm: '%d minutes',
+        h: 'an hour',
+        hh: '%d hours',
+        d: 'a day',
+        dd: '%d days',
+        w: 'a week',
+        ww: '%d weeks',
+        M: 'a month',
+        MM: '%d months',
+        y: 'a year',
+        yy: '%d years',
+    };
+
+    function relativeTime(number, withoutSuffix, string, isFuture) {
+        var output = this._relativeTime[string];
+        return isFunction(output)
+            ? output(number, withoutSuffix, string, isFuture)
+            : output.replace(/%d/i, number);
+    }
+
+    function pastFuture(diff, output) {
+        var format = this._relativeTime[diff > 0 ? 'future' : 'past'];
+        return isFunction(format) ? format(output) : format.replace(/%s/i, output);
+    }
+
+    var aliases = {};
+
+    function addUnitAlias(unit, shorthand) {
+        var lowerCase = unit.toLowerCase();
+        aliases[lowerCase] = aliases[lowerCase + 's'] = aliases[shorthand] = unit;
+    }
+
+    function normalizeUnits(units) {
+        return typeof units === 'string'
+            ? aliases[units] || aliases[units.toLowerCase()]
+            : undefined;
+    }
+
+    function normalizeObjectUnits(inputObject) {
+        var normalizedInput = {},
+            normalizedProp,
+            prop;
+
+        for (prop in inputObject) {
+            if (hasOwnProp(inputObject, prop)) {
+                normalizedProp = normalizeUnits(prop);
+                if (normalizedProp) {
+                    normalizedInput[normalizedProp] = inputObject[prop];
+                }
+            }
+        }
+
+        return normalizedInput;
+    }
+
+    var priorities = {};
+
+    function addUnitPriority(unit, priority) {
+        priorities[unit] = priority;
+    }
+
+    function getPrioritizedUnits(unitsObj) {
+        var units = [],
+            u;
+        for (u in unitsObj) {
+            if (hasOwnProp(unitsObj, u)) {
+                units.push({ unit: u, priority: priorities[u] });
+            }
+        }
+        units.sort(function (a, b) {
+            return a.priority - b.priority;
+        });
+        return units;
+    }
+
+    function isLeapYear(year) {
+        return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
+    }
+
+    function absFloor(number) {
+        if (number < 0) {
+            // -0 -> 0
+            return Math.ceil(number) || 0;
+        } else {
+            return Math.floor(number);
+        }
+    }
+
+    function toInt(argumentForCoercion) {
+        var coercedNumber = +argumentForCoercion,
+            value = 0;
+
+        if (coercedNumber !== 0 && isFinite(coercedNumber)) {
+            value = absFloor(coercedNumber);
+        }
+
+        return value;
+    }
+
+    function makeGetSet(unit, keepTime) {
+        return function (value) {
+            if (value != null) {
+                set$1(this, unit, value);
+                hooks.updateOffset(this, keepTime);
+                return this;
+            } else {
+                return get(this, unit);
+            }
+        };
+    }
+
+    function get(mom, unit) {
+        return mom.isValid()
+            ? mom._d['get' + (mom._isUTC ? 'UTC' : '') + unit]()
+            : NaN;
+    }
+
+    function set$1(mom, unit, value) {
+        if (mom.isValid() && !isNaN(value)) {
+            if (
+                unit === 'FullYear' &&
+                isLeapYear(mom.year()) &&
+                mom.month() === 1 &&
+                mom.date() === 29
+            ) {
+                value = toInt(value);
+                mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](
+                    value,
+                    mom.month(),
+                    daysInMonth(value, mom.month())
+                );
+            } else {
+                mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value);
+            }
+        }
+    }
+
+    // MOMENTS
+
+    function stringGet(units) {
+        units = normalizeUnits(units);
+        if (isFunction(this[units])) {
+            return this[units]();
+        }
+        return this;
+    }
+
+    function stringSet(units, value) {
+        if (typeof units === 'object') {
+            units = normalizeObjectUnits(units);
+            var prioritized = getPrioritizedUnits(units),
+                i;
+            for (i = 0; i < prioritized.length; i++) {
+                this[prioritized[i].unit](units[prioritized[i].unit]);
+            }
+        } else {
+            units = normalizeUnits(units);
+            if (isFunction(this[units])) {
+                return this[units](value);
+            }
+        }
+        return this;
+    }
+
+    var match1 = /\d/, //       0 - 9
+        match2 = /\d\d/, //      00 - 99
+        match3 = /\d{3}/, //     000 - 999
+        match4 = /\d{4}/, //    0000 - 9999
+        match6 = /[+-]?\d{6}/, // -999999 - 999999
+        match1to2 = /\d\d?/, //       0 - 99
+        match3to4 = /\d\d\d\d?/, //     999 - 9999
+        match5to6 = /\d\d\d\d\d\d?/, //   99999 - 999999
+        match1to3 = /\d{1,3}/, //       0 - 999
+        match1to4 = /\d{1,4}/, //       0 - 9999
+        match1to6 = /[+-]?\d{1,6}/, // -999999 - 999999
+        matchUnsigned = /\d+/, //       0 - inf
+        matchSigned = /[+-]?\d+/, //    -inf - inf
+        matchOffset = /Z|[+-]\d\d:?\d\d/gi, // +00:00 -00:00 +0000 -0000 or Z
+        matchShortOffset = /Z|[+-]\d\d(?::?\d\d)?/gi, // +00 -00 +00:00 -00:00 +0000 -0000 or Z
+        matchTimestamp = /[+-]?\d+(\.\d{1,3})?/, // 123456789 123456789.123
+        // any word (or two) characters or numbers including two/three word month in arabic.
+        // includes scottish gaelic two word and hyphenated months
+        matchWord = /[0-9]{0,256}['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFF07\uFF10-\uFFEF]{1,256}|[\u0600-\u06FF\/]{1,256}(\s*?[\u0600-\u06FF]{1,256}){1,2}/i,
+        regexes;
+
+    regexes = {};
+
+    function addRegexToken(token, regex, strictRegex) {
+        regexes[token] = isFunction(regex)
+            ? regex
+            : function (isStrict, localeData) {
+                  return isStrict && strictRegex ? strictRegex : regex;
+              };
+    }
+
+    function getParseRegexForToken(token, config) {
+        if (!hasOwnProp(regexes, token)) {
+            return new RegExp(unescapeFormat(token));
+        }
+
+        return regexes[token](config._strict, config._locale);
+    }
+
+    // Code from http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript
+    function unescapeFormat(s) {
+        return regexEscape(
+            s
+                .replace('\\', '')
+                .replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g, function (
+                    matched,
+                    p1,
+                    p2,
+                    p3,
+                    p4
+                ) {
+                    return p1 || p2 || p3 || p4;
+                })
+        );
+    }
+
+    function regexEscape(s) {
+        return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
+    }
+
+    var tokens = {};
+
+    function addParseToken(token, callback) {
+        var i,
+            func = callback;
+        if (typeof token === 'string') {
+            token = [token];
+        }
+        if (isNumber(callback)) {
+            func = function (input, array) {
+                array[callback] = toInt(input);
+            };
+        }
+        for (i = 0; i < token.length; i++) {
+            tokens[token[i]] = func;
+        }
+    }
+
+    function addWeekParseToken(token, callback) {
+        addParseToken(token, function (input, array, config, token) {
+            config._w = config._w || {};
+            callback(input, config._w, config, token);
+        });
+    }
+
+    function addTimeToArrayFromToken(token, input, config) {
+        if (input != null && hasOwnProp(tokens, token)) {
+            tokens[token](input, config._a, config, token);
+        }
+    }
+
+    var YEAR = 0,
+        MONTH = 1,
+        DATE = 2,
+        HOUR = 3,
+        MINUTE = 4,
+        SECOND = 5,
+        MILLISECOND = 6,
+        WEEK = 7,
+        WEEKDAY = 8;
+
+    function mod(n, x) {
+        return ((n % x) + x) % x;
+    }
+
+    var indexOf;
+
+    if (Array.prototype.indexOf) {
+        indexOf = Array.prototype.indexOf;
+    } else {
+        indexOf = function (o) {
+            // I know
+            var i;
+            for (i = 0; i < this.length; ++i) {
+                if (this[i] === o) {
+                    return i;
+                }
+            }
+            return -1;
+        };
+    }
+
+    function daysInMonth(year, month) {
+        if (isNaN(year) || isNaN(month)) {
+            return NaN;
+        }
+        var modMonth = mod(month, 12);
+        year += (month - modMonth) / 12;
+        return modMonth === 1
+            ? isLeapYear(year)
+                ? 29
+                : 28
+            : 31 - ((modMonth % 7) % 2);
+    }
+
+    // FORMATTING
+
+    addFormatToken('M', ['MM', 2], 'Mo', function () {
+        return this.month() + 1;
+    });
+
+    addFormatToken('MMM', 0, 0, function (format) {
+        return this.localeData().monthsShort(this, format);
+    });
+
+    addFormatToken('MMMM', 0, 0, function (format) {
+        return this.localeData().months(this, format);
+    });
+
+    // ALIASES
+
+    addUnitAlias('month', 'M');
+
+    // PRIORITY
+
+    addUnitPriority('month', 8);
+
+    // PARSING
+
+    addRegexToken('M', match1to2);
+    addRegexToken('MM', match1to2, match2);
+    addRegexToken('MMM', function (isStrict, locale) {
+        return locale.monthsShortRegex(isStrict);
+    });
+    addRegexToken('MMMM', function (isStrict, locale) {
+        return locale.monthsRegex(isStrict);
+    });
+
+    addParseToken(['M', 'MM'], function (input, array) {
+        array[MONTH] = toInt(input) - 1;
+    });
+
+    addParseToken(['MMM', 'MMMM'], function (input, array, config, token) {
+        var month = config._locale.monthsParse(input, token, config._strict);
+        // if we didn't find a month name, mark the date as invalid.
+        if (month != null) {
+            array[MONTH] = month;
+        } else {
+            getParsingFlags(config).invalidMonth = input;
+        }
+    });
+
+    // LOCALES
+
+    var defaultLocaleMonths = 'January_February_March_April_May_June_July_August_September_October_November_December'.split(
+            '_'
+        ),
+        defaultLocaleMonthsShort = 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split(
+            '_'
+        ),
+        MONTHS_IN_FORMAT = /D[oD]?(\[[^\[\]]*\]|\s)+MMMM?/,
+        defaultMonthsShortRegex = matchWord,
+        defaultMonthsRegex = matchWord;
+
+    function localeMonths(m, format) {
+        if (!m) {
+            return isArray(this._months)
+                ? this._months
+                : this._months['standalone'];
+        }
+        return isArray(this._months)
+            ? this._months[m.month()]
+            : this._months[
+                  (this._months.isFormat || MONTHS_IN_FORMAT).test(format)
+                      ? 'format'
+                      : 'standalone'
+              ][m.month()];
+    }
+
+    function localeMonthsShort(m, format) {
+        if (!m) {
+            return isArray(this._monthsShort)
+                ? this._monthsShort
+                : this._monthsShort['standalone'];
+        }
+        return isArray(this._monthsShort)
+            ? this._monthsShort[m.month()]
+            : this._monthsShort[
+                  MONTHS_IN_FORMAT.test(format) ? 'format' : 'standalone'
+              ][m.month()];
+    }
+
+    function handleStrictParse(monthName, format, strict) {
+        var i,
+            ii,
+            mom,
+            llc = monthName.toLocaleLowerCase();
+        if (!this._monthsParse) {
+            // this is not used
+            this._monthsParse = [];
+            this._longMonthsParse = [];
+            this._shortMonthsParse = [];
+            for (i = 0; i < 12; ++i) {
+                mom = createUTC([2000, i]);
+                this._shortMonthsParse[i] = this.monthsShort(
+                    mom,
+                    ''
+                ).toLocaleLowerCase();
+                this._longMonthsParse[i] = this.months(mom, '').toLocaleLowerCase();
+            }
+        }
+
+        if (strict) {
+            if (format === 'MMM') {
+                ii = indexOf.call(this._shortMonthsParse, llc);
+                return ii !== -1 ? ii : null;
+            } else {
+                ii = indexOf.call(this._longMonthsParse, llc);
+                return ii !== -1 ? ii : null;
+            }
+        } else {
+            if (format === 'MMM') {
+                ii = indexOf.call(this._shortMonthsParse, llc);
+                if (ii !== -1) {
+                    return ii;
+                }
+                ii = indexOf.call(this._longMonthsParse, llc);
+                return ii !== -1 ? ii : null;
+            } else {
+                ii = indexOf.call(this._longMonthsParse, llc);
+                if (ii !== -1) {
+                    return ii;
+                }
+                ii = indexOf.call(this._shortMonthsParse, llc);
+                return ii !== -1 ? ii : null;
+            }
+        }
+    }
+
+    function localeMonthsParse(monthName, format, strict) {
+        var i, mom, regex;
+
+        if (this._monthsParseExact) {
+            return handleStrictParse.call(this, monthName, format, strict);
+        }
+
+        if (!this._monthsParse) {
+            this._monthsParse = [];
+            this._longMonthsParse = [];
+            this._shortMonthsParse = [];
+        }
+
+        // TODO: add sorting
+        // Sorting makes sure if one month (or abbr) is a prefix of another
+        // see sorting in computeMonthsParse
+        for (i = 0; i < 12; i++) {
+            // make the regex if we don't have it already
+            mom = createUTC([2000, i]);
+            if (strict && !this._longMonthsParse[i]) {
+                this._longMonthsParse[i] = new RegExp(
+                    '^' + this.months(mom, '').replace('.', '') + '$',
+                    'i'
+                );
+                this._shortMonthsParse[i] = new RegExp(
+                    '^' + this.monthsShort(mom, '').replace('.', '') + '$',
+                    'i'
+                );
+            }
+            if (!strict && !this._monthsParse[i]) {
+                regex =
+                    '^' + this.months(mom, '') + '|^' + this.monthsShort(mom, '');
+                this._monthsParse[i] = new RegExp(regex.replace('.', ''), 'i');
+            }
+            // test the regex
+            if (
+                strict &&
+                format === 'MMMM' &&
+                this._longMonthsParse[i].test(monthName)
+            ) {
+                return i;
+            } else if (
+                strict &&
+                format === 'MMM' &&
+                this._shortMonthsParse[i].test(monthName)
+            ) {
+                return i;
+            } else if (!strict && this._monthsParse[i].test(monthName)) {
+                return i;
+            }
+        }
+    }
+
+    // MOMENTS
+
+    function setMonth(mom, value) {
+        var dayOfMonth;
+
+        if (!mom.isValid()) {
+            // No op
+            return mom;
+        }
+
+        if (typeof value === 'string') {
+            if (/^\d+$/.test(value)) {
+                value = toInt(value);
+            } else {
+                value = mom.localeData().monthsParse(value);
+                // TODO: Another silent failure?
+                if (!isNumber(value)) {
+                    return mom;
+                }
+            }
+        }
+
+        dayOfMonth = Math.min(mom.date(), daysInMonth(mom.year(), value));
+        mom._d['set' + (mom._isUTC ? 'UTC' : '') + 'Month'](value, dayOfMonth);
+        return mom;
+    }
+
+    function getSetMonth(value) {
+        if (value != null) {
+            setMonth(this, value);
+            hooks.updateOffset(this, true);
+            return this;
+        } else {
+            return get(this, 'Month');
+        }
+    }
+
+    function getDaysInMonth() {
+        return daysInMonth(this.year(), this.month());
+    }
+
+    function monthsShortRegex(isStrict) {
+        if (this._monthsParseExact) {
+            if (!hasOwnProp(this, '_monthsRegex')) {
+                computeMonthsParse.call(this);
+            }
+            if (isStrict) {
+                return this._monthsShortStrictRegex;
+            } else {
+                return this._monthsShortRegex;
+            }
+        } else {
+            if (!hasOwnProp(this, '_monthsShortRegex')) {
+                this._monthsShortRegex = defaultMonthsShortRegex;
+            }
+            return this._monthsShortStrictRegex && isStrict
+                ? this._monthsShortStrictRegex
+                : this._monthsShortRegex;
+        }
+    }
+
+    function monthsRegex(isStrict) {
+        if (this._monthsParseExact) {
+            if (!hasOwnProp(this, '_monthsRegex')) {
+                computeMonthsParse.call(this);
+            }
+            if (isStrict) {
+                return this._monthsStrictRegex;
+            } else {
+                return this._monthsRegex;
+            }
+        } else {
+            if (!hasOwnProp(this, '_monthsRegex')) {
+                this._monthsRegex = defaultMonthsRegex;
+            }
+            return this._monthsStrictRegex && isStrict
+                ? this._monthsStrictRegex
+                : this._monthsRegex;
+        }
+    }
+
+    function computeMonthsParse() {
+        function cmpLenRev(a, b) {
+            return b.length - a.length;
+        }
+
+        var shortPieces = [],
+            longPieces = [],
+            mixedPieces = [],
+            i,
+            mom;
+        for (i = 0; i < 12; i++) {
+            // make the regex if we don't have it already
+            mom = createUTC([2000, i]);
+            shortPieces.push(this.monthsShort(mom, ''));
+            longPieces.push(this.months(mom, ''));
+            mixedPieces.push(this.months(mom, ''));
+            mixedPieces.push(this.monthsShort(mom, ''));
+        }
+        // Sorting makes sure if one month (or abbr) is a prefix of another it
+        // will match the longer piece.
+        shortPieces.sort(cmpLenRev);
+        longPieces.sort(cmpLenRev);
+        mixedPieces.sort(cmpLenRev);
+        for (i = 0; i < 12; i++) {
+            shortPieces[i] = regexEscape(shortPieces[i]);
+            longPieces[i] = regexEscape(longPieces[i]);
+        }
+        for (i = 0; i < 24; i++) {
+            mixedPieces[i] = regexEscape(mixedPieces[i]);
+        }
+
+        this._monthsRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i');
+        this._monthsShortRegex = this._monthsRegex;
+        this._monthsStrictRegex = new RegExp(
+            '^(' + longPieces.join('|') + ')',
+            'i'
+        );
+        this._monthsShortStrictRegex = new RegExp(
+            '^(' + shortPieces.join('|') + ')',
+            'i'
+        );
+    }
+
+    // FORMATTING
+
+    addFormatToken('Y', 0, 0, function () {
+        var y = this.year();
+        return y <= 9999 ? zeroFill(y, 4) : '+' + y;
+    });
+
+    addFormatToken(0, ['YY', 2], 0, function () {
+        return this.year() % 100;
+    });
+
+    addFormatToken(0, ['YYYY', 4], 0, 'year');
+    addFormatToken(0, ['YYYYY', 5], 0, 'year');
+    addFormatToken(0, ['YYYYYY', 6, true], 0, 'year');
+
+    // ALIASES
+
+    addUnitAlias('year', 'y');
+
+    // PRIORITIES
+
+    addUnitPriority('year', 1);
+
+    // PARSING
+
+    addRegexToken('Y', matchSigned);
+    addRegexToken('YY', match1to2, match2);
+    addRegexToken('YYYY', match1to4, match4);
+    addRegexToken('YYYYY', match1to6, match6);
+    addRegexToken('YYYYYY', match1to6, match6);
+
+    addParseToken(['YYYYY', 'YYYYYY'], YEAR);
+    addParseToken('YYYY', function (input, array) {
+        array[YEAR] =
+            input.length === 2 ? hooks.parseTwoDigitYear(input) : toInt(input);
+    });
+    addParseToken('YY', function (input, array) {
+        array[YEAR] = hooks.parseTwoDigitYear(input);
+    });
+    addParseToken('Y', function (input, array) {
+        array[YEAR] = parseInt(input, 10);
+    });
+
+    // HELPERS
+
+    function daysInYear(year) {
+        return isLeapYear(year) ? 366 : 365;
+    }
+
+    // HOOKS
+
+    hooks.parseTwoDigitYear = function (input) {
+        return toInt(input) + (toInt(input) > 68 ? 1900 : 2000);
+    };
+
+    // MOMENTS
+
+    var getSetYear = makeGetSet('FullYear', true);
+
+    function getIsLeapYear() {
+        return isLeapYear(this.year());
+    }
+
+    function createDate(y, m, d, h, M, s, ms) {
+        // can't just apply() to create a date:
+        // https://stackoverflow.com/q/181348
+        var date;
+        // the date constructor remaps years 0-99 to 1900-1999
+        if (y < 100 && y >= 0) {
+            // preserve leap years using a full 400 year cycle, then reset
+            date = new Date(y + 400, m, d, h, M, s, ms);
+            if (isFinite(date.getFullYear())) {
+                date.setFullYear(y);
+            }
+        } else {
+            date = new Date(y, m, d, h, M, s, ms);
+        }
+
+        return date;
+    }
+
+    function createUTCDate(y) {
+        var date, args;
+        // the Date.UTC function remaps years 0-99 to 1900-1999
+        if (y < 100 && y >= 0) {
+            args = Array.prototype.slice.call(arguments);
+            // preserve leap years using a full 400 year cycle, then reset
+            args[0] = y + 400;
+            date = new Date(Date.UTC.apply(null, args));
+            if (isFinite(date.getUTCFullYear())) {
+                date.setUTCFullYear(y);
+            }
+        } else {
+            date = new Date(Date.UTC.apply(null, arguments));
+        }
+
+        return date;
+    }
+
+    // start-of-first-week - start-of-year
+    function firstWeekOffset(year, dow, doy) {
+        var // first-week day -- which january is always in the first week (4 for iso, 1 for other)
+            fwd = 7 + dow - doy,
+            // first-week day local weekday -- which local weekday is fwd
+            fwdlw = (7 + createUTCDate(year, 0, fwd).getUTCDay() - dow) % 7;
+
+        return -fwdlw + fwd - 1;
+    }
+
+    // https://en.wikipedia.org/wiki/ISO_week_date#Calculating_a_date_given_the_year.2C_week_number_and_weekday
+    function dayOfYearFromWeeks(year, week, weekday, dow, doy) {
+        var localWeekday = (7 + weekday - dow) % 7,
+            weekOffset = firstWeekOffset(year, dow, doy),
+            dayOfYear = 1 + 7 * (week - 1) + localWeekday + weekOffset,
+            resYear,
+            resDayOfYear;
+
+        if (dayOfYear <= 0) {
+            resYear = year - 1;
+            resDayOfYear = daysInYear(resYear) + dayOfYear;
+        } else if (dayOfYear > daysInYear(year)) {
+            resYear = year + 1;
+            resDayOfYear = dayOfYear - daysInYear(year);
+        } else {
+            resYear = year;
+            resDayOfYear = dayOfYear;
+        }
+
+        return {
+            year: resYear,
+            dayOfYear: resDayOfYear,
+        };
+    }
+
+    function weekOfYear(mom, dow, doy) {
+        var weekOffset = firstWeekOffset(mom.year(), dow, doy),
+            week = Math.floor((mom.dayOfYear() - weekOffset - 1) / 7) + 1,
+            resWeek,
+            resYear;
+
+        if (week < 1) {
+            resYear = mom.year() - 1;
+            resWeek = week + weeksInYear(resYear, dow, doy);
+        } else if (week > weeksInYear(mom.year(), dow, doy)) {
+            resWeek = week - weeksInYear(mom.year(), dow, doy);
+            resYear = mom.year() + 1;
+        } else {
+            resYear = mom.year();
+            resWeek = week;
+        }
+
+        return {
+            week: resWeek,
+            year: resYear,
+        };
+    }
+
+    function weeksInYear(year, dow, doy) {
+        var weekOffset = firstWeekOffset(year, dow, doy),
+            weekOffsetNext = firstWeekOffset(year + 1, dow, doy);
+        return (daysInYear(year) - weekOffset + weekOffsetNext) / 7;
+    }
+
+    // FORMATTING
+
+    addFormatToken('w', ['ww', 2], 'wo', 'week');
+    addFormatToken('W', ['WW', 2], 'Wo', 'isoWeek');
+
+    // ALIASES
+
+    addUnitAlias('week', 'w');
+    addUnitAlias('isoWeek', 'W');
+
+    // PRIORITIES
+
+    addUnitPriority('week', 5);
+    addUnitPriority('isoWeek', 5);
+
+    // PARSING
+
+    addRegexToken('w', match1to2);
+    addRegexToken('ww', match1to2, match2);
+    addRegexToken('W', match1to2);
+    addRegexToken('WW', match1to2, match2);
+
+    addWeekParseToken(['w', 'ww', 'W', 'WW'], function (
+        input,
+        week,
+        config,
+        token
+    ) {
+        week[token.substr(0, 1)] = toInt(input);
+    });
+
+    // HELPERS
+
+    // LOCALES
+
+    function localeWeek(mom) {
+        return weekOfYear(mom, this._week.dow, this._week.doy).week;
+    }
+
+    var defaultLocaleWeek = {
+        dow: 0, // Sunday is the first day of the week.
+        doy: 6, // The week that contains Jan 6th is the first week of the year.
+    };
+
+    function localeFirstDayOfWeek() {
+        return this._week.dow;
+    }
+
+    function localeFirstDayOfYear() {
+        return this._week.doy;
+    }
+
+    // MOMENTS
+
+    function getSetWeek(input) {
+        var week = this.localeData().week(this);
+        return input == null ? week : this.add((input - week) * 7, 'd');
+    }
+
+    function getSetISOWeek(input) {
+        var week = weekOfYear(this, 1, 4).week;
+        return input == null ? week : this.add((input - week) * 7, 'd');
+    }
+
+    // FORMATTING
+
+    addFormatToken('d', 0, 'do', 'day');
+
+    addFormatToken('dd', 0, 0, function (format) {
+        return this.localeData().weekdaysMin(this, format);
+    });
+
+    addFormatToken('ddd', 0, 0, function (format) {
+        return this.localeData().weekdaysShort(this, format);
+    });
+
+    addFormatToken('dddd', 0, 0, function (format) {
+        return this.localeData().weekdays(this, format);
+    });
+
+    addFormatToken('e', 0, 0, 'weekday');
+    addFormatToken('E', 0, 0, 'isoWeekday');
+
+    // ALIASES
+
+    addUnitAlias('day', 'd');
+    addUnitAlias('weekday', 'e');
+    addUnitAlias('isoWeekday', 'E');
+
+    // PRIORITY
+    addUnitPriority('day', 11);
+    addUnitPriority('weekday', 11);
+    addUnitPriority('isoWeekday', 11);
+
+    // PARSING
+
+    addRegexToken('d', match1to2);
+    addRegexToken('e', match1to2);
+    addRegexToken('E', match1to2);
+    addRegexToken('dd', function (isStrict, locale) {
+        return locale.weekdaysMinRegex(isStrict);
+    });
+    addRegexToken('ddd', function (isStrict, locale) {
+        return locale.weekdaysShortRegex(isStrict);
+    });
+    addRegexToken('dddd', function (isStrict, locale) {
+        return locale.weekdaysRegex(isStrict);
+    });
+
+    addWeekParseToken(['dd', 'ddd', 'dddd'], function (input, week, config, token) {
+        var weekday = config._locale.weekdaysParse(input, token, config._strict);
+        // if we didn't get a weekday name, mark the date as invalid
+        if (weekday != null) {
+            week.d = weekday;
+        } else {
+            getParsingFlags(config).invalidWeekday = input;
+        }
+    });
+
+    addWeekParseToken(['d', 'e', 'E'], function (input, week, config, token) {
+        week[token] = toInt(input);
+    });
+
+    // HELPERS
+
+    function parseWeekday(input, locale) {
+        if (typeof input !== 'string') {
+            return input;
+        }
+
+        if (!isNaN(input)) {
+            return parseInt(input, 10);
+        }
+
+        input = locale.weekdaysParse(input);
+        if (typeof input === 'number') {
+            return input;
+        }
+
+        return null;
+    }
+
+    function parseIsoWeekday(input, locale) {
+        if (typeof input === 'string') {
+            return locale.weekdaysParse(input) % 7 || 7;
+        }
+        return isNaN(input) ? null : input;
+    }
+
+    // LOCALES
+    function shiftWeekdays(ws, n) {
+        return ws.slice(n, 7).concat(ws.slice(0, n));
+    }
+
+    var defaultLocaleWeekdays = 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split(
+            '_'
+        ),
+        defaultLocaleWeekdaysShort = 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_'),
+        defaultLocaleWeekdaysMin = 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_'),
+        defaultWeekdaysRegex = matchWord,
+        defaultWeekdaysShortRegex = matchWord,
+        defaultWeekdaysMinRegex = matchWord;
+
+    function localeWeekdays(m, format) {
+        var weekdays = isArray(this._weekdays)
+            ? this._weekdays
+            : this._weekdays[
+                  m && m !== true && this._weekdays.isFormat.test(format)
+                      ? 'format'
+                      : 'standalone'
+              ];
+        return m === true
+            ? shiftWeekdays(weekdays, this._week.dow)
+            : m
+            ? weekdays[m.day()]
+            : weekdays;
+    }
+
+    function localeWeekdaysShort(m) {
+        return m === true
+            ? shiftWeekdays(this._weekdaysShort, this._week.dow)
+            : m
+            ? this._weekdaysShort[m.day()]
+            : this._weekdaysShort;
+    }
+
+    function localeWeekdaysMin(m) {
+        return m === true
+            ? shiftWeekdays(this._weekdaysMin, this._week.dow)
+            : m
+            ? this._weekdaysMin[m.day()]
+            : this._weekdaysMin;
+    }
+
+    function handleStrictParse$1(weekdayName, format, strict) {
+        var i,
+            ii,
+            mom,
+            llc = weekdayName.toLocaleLowerCase();
+        if (!this._weekdaysParse) {
+            this._weekdaysParse = [];
+            this._shortWeekdaysParse = [];
+            this._minWeekdaysParse = [];
+
+            for (i = 0; i < 7; ++i) {
+                mom = createUTC([2000, 1]).day(i);
+                this._minWeekdaysParse[i] = this.weekdaysMin(
+                    mom,
+                    ''
+                ).toLocaleLowerCase();
+                this._shortWeekdaysParse[i] = this.weekdaysShort(
+                    mom,
+                    ''
+                ).toLocaleLowerCase();
+                this._weekdaysParse[i] = this.weekdays(mom, '').toLocaleLowerCase();
+            }
+        }
+
+        if (strict) {
+            if (format === 'dddd') {
+                ii = indexOf.call(this._weekdaysParse, llc);
+                return ii !== -1 ? ii : null;
+            } else if (format === 'ddd') {
+                ii = indexOf.call(this._shortWeekdaysParse, llc);
+                return ii !== -1 ? ii : null;
+            } else {
+                ii = indexOf.call(this._minWeekdaysParse, llc);
+                return ii !== -1 ? ii : null;
+            }
+        } else {
+            if (format === 'dddd') {
+                ii = indexOf.call(this._weekdaysParse, llc);
+                if (ii !== -1) {
+                    return ii;
+                }
+                ii = indexOf.call(this._shortWeekdaysParse, llc);
+                if (ii !== -1) {
+                    return ii;
+                }
+                ii = indexOf.call(this._minWeekdaysParse, llc);
+                return ii !== -1 ? ii : null;
+            } else if (format === 'ddd') {
+                ii = indexOf.call(this._shortWeekdaysParse, llc);
+                if (ii !== -1) {
+                    return ii;
+                }
+                ii = indexOf.call(this._weekdaysParse, llc);
+                if (ii !== -1) {
+                    return ii;
+                }
+                ii = indexOf.call(this._minWeekdaysParse, llc);
+                return ii !== -1 ? ii : null;
+            } else {
+                ii = indexOf.call(this._minWeekdaysParse, llc);
+                if (ii !== -1) {
+                    return ii;
+                }
+                ii = indexOf.call(this._weekdaysParse, llc);
+                if (ii !== -1) {
+                    return ii;
+                }
+                ii = indexOf.call(this._shortWeekdaysParse, llc);
+                return ii !== -1 ? ii : null;
+            }
+        }
+    }
+
+    function localeWeekdaysParse(weekdayName, format, strict) {
+        var i, mom, regex;
+
+        if (this._weekdaysParseExact) {
+            return handleStrictParse$1.call(this, weekdayName, format, strict);
+        }
+
+        if (!this._weekdaysParse) {
+            this._weekdaysParse = [];
+            this._minWeekdaysParse = [];
+            this._shortWeekdaysParse = [];
+            this._fullWeekdaysParse = [];
+        }
+
+        for (i = 0; i < 7; i++) {
+            // make the regex if we don't have it already
+
+            mom = createUTC([2000, 1]).day(i);
+            if (strict && !this._fullWeekdaysParse[i]) {
+                this._fullWeekdaysParse[i] = new RegExp(
+                    '^' + this.weekdays(mom, '').replace('.', '\\.?') + '$',
+                    'i'
+                );
+                this._shortWeekdaysParse[i] = new RegExp(
+                    '^' + this.weekdaysShort(mom, '').replace('.', '\\.?') + '$',
+                    'i'
+                );
+                this._minWeekdaysParse[i] = new RegExp(
+                    '^' + this.weekdaysMin(mom, '').replace('.', '\\.?') + '$',
+                    'i'
+                );
+            }
+            if (!this._weekdaysParse[i]) {
+                regex =
+                    '^' +
+                    this.weekdays(mom, '') +
+                    '|^' +
+                    this.weekdaysShort(mom, '') +
+                    '|^' +
+                    this.weekdaysMin(mom, '');
+                this._weekdaysParse[i] = new RegExp(regex.replace('.', ''), 'i');
+            }
+            // test the regex
+            if (
+                strict &&
+                format === 'dddd' &&
+                this._fullWeekdaysParse[i].test(weekdayName)
+            ) {
+                return i;
+            } else if (
+                strict &&
+                format === 'ddd' &&
+                this._shortWeekdaysParse[i].test(weekdayName)
+            ) {
+                return i;
+            } else if (
+                strict &&
+                format === 'dd' &&
+                this._minWeekdaysParse[i].test(weekdayName)
+            ) {
+                return i;
+            } else if (!strict && this._weekdaysParse[i].test(weekdayName)) {
+                return i;
+            }
+        }
+    }
+
+    // MOMENTS
+
+    function getSetDayOfWeek(input) {
+        if (!this.isValid()) {
+            return input != null ? this : NaN;
+        }
+        var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay();
+        if (input != null) {
+            input = parseWeekday(input, this.localeData());
+            return this.add(input - day, 'd');
+        } else {
+            return day;
+        }
+    }
+
+    function getSetLocaleDayOfWeek(input) {
+        if (!this.isValid()) {
+            return input != null ? this : NaN;
+        }
+        var weekday = (this.day() + 7 - this.localeData()._week.dow) % 7;
+        return input == null ? weekday : this.add(input - weekday, 'd');
+    }
+
+    function getSetISODayOfWeek(input) {
+        if (!this.isValid()) {
+            return input != null ? this : NaN;
+        }
+
+        // behaves the same as moment#day except
+        // as a getter, returns 7 instead of 0 (1-7 range instead of 0-6)
+        // as a setter, sunday should belong to the previous week.
+
+        if (input != null) {
+            var weekday = parseIsoWeekday(input, this.localeData());
+            return this.day(this.day() % 7 ? weekday : weekday - 7);
+        } else {
+            return this.day() || 7;
+        }
+    }
+
+    function weekdaysRegex(isStrict) {
+        if (this._weekdaysParseExact) {
+            if (!hasOwnProp(this, '_weekdaysRegex')) {
+                computeWeekdaysParse.call(this);
+            }
+            if (isStrict) {
+                return this._weekdaysStrictRegex;
+            } else {
+                return this._weekdaysRegex;
+            }
+        } else {
+            if (!hasOwnProp(this, '_weekdaysRegex')) {
+                this._weekdaysRegex = defaultWeekdaysRegex;
+            }
+            return this._weekdaysStrictRegex && isStrict
+                ? this._weekdaysStrictRegex
+                : this._weekdaysRegex;
+        }
+    }
+
+    function weekdaysShortRegex(isStrict) {
+        if (this._weekdaysParseExact) {
+            if (!hasOwnProp(this, '_weekdaysRegex')) {
+                computeWeekdaysParse.call(this);
+            }
+            if (isStrict) {
+                return this._weekdaysShortStrictRegex;
+            } else {
+                return this._weekdaysShortRegex;
+            }
+        } else {
+            if (!hasOwnProp(this, '_weekdaysShortRegex')) {
+                this._weekdaysShortRegex = defaultWeekdaysShortRegex;
+            }
+            return this._weekdaysShortStrictRegex && isStrict
+                ? this._weekdaysShortStrictRegex
+                : this._weekdaysShortRegex;
+        }
+    }
+
+    function weekdaysMinRegex(isStrict) {
+        if (this._weekdaysParseExact) {
+            if (!hasOwnProp(this, '_weekdaysRegex')) {
+                computeWeekdaysParse.call(this);
+            }
+            if (isStrict) {
+                return this._weekdaysMinStrictRegex;
+            } else {
+                return this._weekdaysMinRegex;
+            }
+        } else {
+            if (!hasOwnProp(this, '_weekdaysMinRegex')) {
+                this._weekdaysMinRegex = defaultWeekdaysMinRegex;
+            }
+            return this._weekdaysMinStrictRegex && isStrict
+                ? this._weekdaysMinStrictRegex
+                : this._weekdaysMinRegex;
+        }
+    }
+
+    function computeWeekdaysParse() {
+        function cmpLenRev(a, b) {
+            return b.length - a.length;
+        }
+
+        var minPieces = [],
+            shortPieces = [],
+            longPieces = [],
+            mixedPieces = [],
+            i,
+            mom,
+            minp,
+            shortp,
+            longp;
+        for (i = 0; i < 7; i++) {
+            // make the regex if we don't have it already
+            mom = createUTC([2000, 1]).day(i);
+            minp = regexEscape(this.weekdaysMin(mom, ''));
+            shortp = regexEscape(this.weekdaysShort(mom, ''));
+            longp = regexEscape(this.weekdays(mom, ''));
+            minPieces.push(minp);
+            shortPieces.push(shortp);
+            longPieces.push(longp);
+            mixedPieces.push(minp);
+            mixedPieces.push(shortp);
+            mixedPieces.push(longp);
+        }
+        // Sorting makes sure if one weekday (or abbr) is a prefix of another it
+        // will match the longer piece.
+        minPieces.sort(cmpLenRev);
+        shortPieces.sort(cmpLenRev);
+        longPieces.sort(cmpLenRev);
+        mixedPieces.sort(cmpLenRev);
+
+        this._weekdaysRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i');
+        this._weekdaysShortRegex = this._weekdaysRegex;
+        this._weekdaysMinRegex = this._weekdaysRegex;
+
+        this._weekdaysStrictRegex = new RegExp(
+            '^(' + longPieces.join('|') + ')',
+            'i'
+        );
+        this._weekdaysShortStrictRegex = new RegExp(
+            '^(' + shortPieces.join('|') + ')',
+            'i'
+        );
+        this._weekdaysMinStrictRegex = new RegExp(
+            '^(' + minPieces.join('|') + ')',
+            'i'
+        );
+    }
+
+    // FORMATTING
+
+    function hFormat() {
+        return this.hours() % 12 || 12;
+    }
+
+    function kFormat() {
+        return this.hours() || 24;
+    }
+
+    addFormatToken('H', ['HH', 2], 0, 'hour');
+    addFormatToken('h', ['hh', 2], 0, hFormat);
+    addFormatToken('k', ['kk', 2], 0, kFormat);
+
+    addFormatToken('hmm', 0, 0, function () {
+        return '' + hFormat.apply(this) + zeroFill(this.minutes(), 2);
+    });
+
+    addFormatToken('hmmss', 0, 0, function () {
+        return (
+            '' +
+            hFormat.apply(this) +
+            zeroFill(this.minutes(), 2) +
+            zeroFill(this.seconds(), 2)
+        );
+    });
+
+    addFormatToken('Hmm', 0, 0, function () {
+        return '' + this.hours() + zeroFill(this.minutes(), 2);
+    });
+
+    addFormatToken('Hmmss', 0, 0, function () {
+        return (
+            '' +
+            this.hours() +
+            zeroFill(this.minutes(), 2) +
+            zeroFill(this.seconds(), 2)
+        );
+    });
+
+    function meridiem(token, lowercase) {
+        addFormatToken(token, 0, 0, function () {
+            return this.localeData().meridiem(
+                this.hours(),
+                this.minutes(),
+                lowercase
+            );
+        });
+    }
+
+    meridiem('a', true);
+    meridiem('A', false);
+
+    // ALIASES
+
+    addUnitAlias('hour', 'h');
+
+    // PRIORITY
+    addUnitPriority('hour', 13);
+
+    // PARSING
+
+    function matchMeridiem(isStrict, locale) {
+        return locale._meridiemParse;
+    }
+
+    addRegexToken('a', matchMeridiem);
+    addRegexToken('A', matchMeridiem);
+    addRegexToken('H', match1to2);
+    addRegexToken('h', match1to2);
+    addRegexToken('k', match1to2);
+    addRegexToken('HH', match1to2, match2);
+    addRegexToken('hh', match1to2, match2);
+    addRegexToken('kk', match1to2, match2);
+
+    addRegexToken('hmm', match3to4);
+    addRegexToken('hmmss', match5to6);
+    addRegexToken('Hmm', match3to4);
+    addRegexToken('Hmmss', match5to6);
+
+    addParseToken(['H', 'HH'], HOUR);
+    addParseToken(['k', 'kk'], function (input, array, config) {
+        var kInput = toInt(input);
+        array[HOUR] = kInput === 24 ? 0 : kInput;
+    });
+    addParseToken(['a', 'A'], function (input, array, config) {
+        config._isPm = config._locale.isPM(input);
+        config._meridiem = input;
+    });
+    addParseToken(['h', 'hh'], function (input, array, config) {
+        array[HOUR] = toInt(input);
+        getParsingFlags(config).bigHour = true;
+    });
+    addParseToken('hmm', function (input, array, config) {
+        var pos = input.length - 2;
+        array[HOUR] = toInt(input.substr(0, pos));
+        array[MINUTE] = toInt(input.substr(pos));
+        getParsingFlags(config).bigHour = true;
+    });
+    addParseToken('hmmss', function (input, array, config) {
+        var pos1 = input.length - 4,
+            pos2 = input.length - 2;
+        array[HOUR] = toInt(input.substr(0, pos1));
+        array[MINUTE] = toInt(input.substr(pos1, 2));
+        array[SECOND] = toInt(input.substr(pos2));
+        getParsingFlags(config).bigHour = true;
+    });
+    addParseToken('Hmm', function (input, array, config) {
+        var pos = input.length - 2;
+        array[HOUR] = toInt(input.substr(0, pos));
+        array[MINUTE] = toInt(input.substr(pos));
+    });
+    addParseToken('Hmmss', function (input, array, config) {
+        var pos1 = input.length - 4,
+            pos2 = input.length - 2;
+        array[HOUR] = toInt(input.substr(0, pos1));
+        array[MINUTE] = toInt(input.substr(pos1, 2));
+        array[SECOND] = toInt(input.substr(pos2));
+    });
+
+    // LOCALES
+
+    function localeIsPM(input) {
+        // IE8 Quirks Mode & IE7 Standards Mode do not allow accessing strings like arrays
+        // Using charAt should be more compatible.
+        return (input + '').toLowerCase().charAt(0) === 'p';
+    }
+
+    var defaultLocaleMeridiemParse = /[ap]\.?m?\.?/i,
+        // Setting the hour should keep the time, because the user explicitly
+        // specified which hour they want. So trying to maintain the same hour (in
+        // a new timezone) makes sense. Adding/subtracting hours does not follow
+        // this rule.
+        getSetHour = makeGetSet('Hours', true);
+
+    function localeMeridiem(hours, minutes, isLower) {
+        if (hours > 11) {
+            return isLower ? 'pm' : 'PM';
+        } else {
+            return isLower ? 'am' : 'AM';
+        }
+    }
+
+    var baseConfig = {
+        calendar: defaultCalendar,
+        longDateFormat: defaultLongDateFormat,
+        invalidDate: defaultInvalidDate,
+        ordinal: defaultOrdinal,
+        dayOfMonthOrdinalParse: defaultDayOfMonthOrdinalParse,
+        relativeTime: defaultRelativeTime,
+
+        months: defaultLocaleMonths,
+        monthsShort: defaultLocaleMonthsShort,
+
+        week: defaultLocaleWeek,
+
+        weekdays: defaultLocaleWeekdays,
+        weekdaysMin: defaultLocaleWeekdaysMin,
+        weekdaysShort: defaultLocaleWeekdaysShort,
+
+        meridiemParse: defaultLocaleMeridiemParse,
+    };
+
+    // internal storage for locale config files
+    var locales = {},
+        localeFamilies = {},
+        globalLocale;
+
+    function commonPrefix(arr1, arr2) {
+        var i,
+            minl = Math.min(arr1.length, arr2.length);
+        for (i = 0; i < minl; i += 1) {
+            if (arr1[i] !== arr2[i]) {
+                return i;
+            }
+        }
+        return minl;
+    }
+
+    function normalizeLocale(key) {
+        return key ? key.toLowerCase().replace('_', '-') : key;
+    }
+
+    // pick the locale from the array
+    // try ['en-au', 'en-gb'] as 'en-au', 'en-gb', 'en', as in move through the list trying each
+    // substring from most specific to least, but move to the next array item if it's a more specific variant than the current root
+    function chooseLocale(names) {
+        var i = 0,
+            j,
+            next,
+            locale,
+            split;
+
+        while (i < names.length) {
+            split = normalizeLocale(names[i]).split('-');
+            j = split.length;
+            next = normalizeLocale(names[i + 1]);
+            next = next ? next.split('-') : null;
+            while (j > 0) {
+                locale = loadLocale(split.slice(0, j).join('-'));
+                if (locale) {
+                    return locale;
+                }
+                if (
+                    next &&
+                    next.length >= j &&
+                    commonPrefix(split, next) >= j - 1
+                ) {
+                    //the next array item is better than a shallower substring of this one
+                    break;
+                }
+                j--;
+            }
+            i++;
+        }
+        return globalLocale;
+    }
+
+    function loadLocale(name) {
+        var oldLocale = null,
+            aliasedRequire;
+        // TODO: Find a better way to register and load all the locales in Node
+        if (
+            locales[name] === undefined &&
+            typeof module !== 'undefined' &&
+            module &&
+            module.exports
+        ) {
+            try {
+                oldLocale = globalLocale._abbr;
+                aliasedRequire = require;
+                aliasedRequire('./locale/' + name);
+                getSetGlobalLocale(oldLocale);
+            } catch (e) {
+                // mark as not found to avoid repeating expensive file require call causing high CPU
+                // when trying to find en-US, en_US, en-us for every format call
+                locales[name] = null; // null means not found
+            }
+        }
+        return locales[name];
+    }
+
+    // This function will load locale and then set the global locale.  If
+    // no arguments are passed in, it will simply return the current global
+    // locale key.
+    function getSetGlobalLocale(key, values) {
+        var data;
+        if (key) {
+            if (isUndefined(values)) {
+                data = getLocale(key);
+            } else {
+                data = defineLocale(key, values);
+            }
+
+            if (data) {
+                // moment.duration._locale = moment._locale = data;
+                globalLocale = data;
+            } else {
+                if (typeof console !== 'undefined' && console.warn) {
+                    //warn user if arguments are passed but the locale could not be set
+                    console.warn(
+                        'Locale ' + key + ' not found. Did you forget to load it?'
+                    );
+                }
+            }
+        }
+
+        return globalLocale._abbr;
+    }
+
+    function defineLocale(name, config) {
+        if (config !== null) {
+            var locale,
+                parentConfig = baseConfig;
+            config.abbr = name;
+            if (locales[name] != null) {
+                deprecateSimple(
+                    'defineLocaleOverride',
+                    'use moment.updateLocale(localeName, config) to change ' +
+                        'an existing locale. moment.defineLocale(localeName, ' +
+                        'config) should only be used for creating a new locale ' +
+                        'See http://momentjs.com/guides/#/warnings/define-locale/ for more info.'
+                );
+                parentConfig = locales[name]._config;
+            } else if (config.parentLocale != null) {
+                if (locales[config.parentLocale] != null) {
+                    parentConfig = locales[config.parentLocale]._config;
+                } else {
+                    locale = loadLocale(config.parentLocale);
+                    if (locale != null) {
+                        parentConfig = locale._config;
+                    } else {
+                        if (!localeFamilies[config.parentLocale]) {
+                            localeFamilies[config.parentLocale] = [];
+                        }
+                        localeFamilies[config.parentLocale].push({
+                            name: name,
+                            config: config,
+                        });
+                        return null;
+                    }
+                }
+            }
+            locales[name] = new Locale(mergeConfigs(parentConfig, config));
+
+            if (localeFamilies[name]) {
+                localeFamilies[name].forEach(function (x) {
+                    defineLocale(x.name, x.config);
+                });
+            }
+
+            // backwards compat for now: also set the locale
+            // make sure we set the locale AFTER all child locales have been
+            // created, so we won't end up with the child locale set.
+            getSetGlobalLocale(name);
+
+            return locales[name];
+        } else {
+            // useful for testing
+            delete locales[name];
+            return null;
+        }
+    }
+
+    function updateLocale(name, config) {
+        if (config != null) {
+            var locale,
+                tmpLocale,
+                parentConfig = baseConfig;
+
+            if (locales[name] != null && locales[name].parentLocale != null) {
+                // Update existing child locale in-place to avoid memory-leaks
+                locales[name].set(mergeConfigs(locales[name]._config, config));
+            } else {
+                // MERGE
+                tmpLocale = loadLocale(name);
+                if (tmpLocale != null) {
+                    parentConfig = tmpLocale._config;
+                }
+                config = mergeConfigs(parentConfig, config);
+                if (tmpLocale == null) {
+                    // updateLocale is called for creating a new locale
+                    // Set abbr so it will have a name (getters return
+                    // undefined otherwise).
+                    config.abbr = name;
+                }
+                locale = new Locale(config);
+                locale.parentLocale = locales[name];
+                locales[name] = locale;
+            }
+
+            // backwards compat for now: also set the locale
+            getSetGlobalLocale(name);
+        } else {
+            // pass null for config to unupdate, useful for tests
+            if (locales[name] != null) {
+                if (locales[name].parentLocale != null) {
+                    locales[name] = locales[name].parentLocale;
+                    if (name === getSetGlobalLocale()) {
+                        getSetGlobalLocale(name);
+                    }
+                } else if (locales[name] != null) {
+                    delete locales[name];
+                }
+            }
+        }
+        return locales[name];
+    }
+
+    // returns locale data
+    function getLocale(key) {
+        var locale;
+
+        if (key && key._locale && key._locale._abbr) {
+            key = key._locale._abbr;
+        }
+
+        if (!key) {
+            return globalLocale;
+        }
+
+        if (!isArray(key)) {
+            //short-circuit everything else
+            locale = loadLocale(key);
+            if (locale) {
+                return locale;
+            }
+            key = [key];
+        }
+
+        return chooseLocale(key);
+    }
+
+    function listLocales() {
+        return keys(locales);
+    }
+
+    function checkOverflow(m) {
+        var overflow,
+            a = m._a;
+
+        if (a && getParsingFlags(m).overflow === -2) {
+            overflow =
+                a[MONTH] < 0 || a[MONTH] > 11
+                    ? MONTH
+                    : a[DATE] < 1 || a[DATE] > daysInMonth(a[YEAR], a[MONTH])
+                    ? DATE
+                    : a[HOUR] < 0 ||
+                      a[HOUR] > 24 ||
+                      (a[HOUR] === 24 &&
+                          (a[MINUTE] !== 0 ||
+                              a[SECOND] !== 0 ||
+                              a[MILLISECOND] !== 0))
+                    ? HOUR
+                    : a[MINUTE] < 0 || a[MINUTE] > 59
+                    ? MINUTE
+                    : a[SECOND] < 0 || a[SECOND] > 59
+                    ? SECOND
+                    : a[MILLISECOND] < 0 || a[MILLISECOND] > 999
+                    ? MILLISECOND
+                    : -1;
+
+            if (
+                getParsingFlags(m)._overflowDayOfYear &&
+                (overflow < YEAR || overflow > DATE)
+            ) {
+                overflow = DATE;
+            }
+            if (getParsingFlags(m)._overflowWeeks && overflow === -1) {
+                overflow = WEEK;
+            }
+            if (getParsingFlags(m)._overflowWeekday && overflow === -1) {
+                overflow = WEEKDAY;
+            }
+
+            getParsingFlags(m).overflow = overflow;
+        }
+
+        return m;
+    }
+
+    // iso 8601 regex
+    // 0000-00-00 0000-W00 or 0000-W00-0 + T + 00 or 00:00 or 00:00:00 or 00:00:00.000 + +00:00 or +0000 or +00)
+    var extendedIsoRegex = /^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([+-]\d\d(?::?\d\d)?|\s*Z)?)?$/,
+        basicIsoRegex = /^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d|))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([+-]\d\d(?::?\d\d)?|\s*Z)?)?$/,
+        tzRegex = /Z|[+-]\d\d(?::?\d\d)?/,
+        isoDates = [
+            ['YYYYYY-MM-DD', /[+-]\d{6}-\d\d-\d\d/],
+            ['YYYY-MM-DD', /\d{4}-\d\d-\d\d/],
+            ['GGGG-[W]WW-E', /\d{4}-W\d\d-\d/],
+            ['GGGG-[W]WW', /\d{4}-W\d\d/, false],
+            ['YYYY-DDD', /\d{4}-\d{3}/],
+            ['YYYY-MM', /\d{4}-\d\d/, false],
+            ['YYYYYYMMDD', /[+-]\d{10}/],
+            ['YYYYMMDD', /\d{8}/],
+            ['GGGG[W]WWE', /\d{4}W\d{3}/],
+            ['GGGG[W]WW', /\d{4}W\d{2}/, false],
+            ['YYYYDDD', /\d{7}/],
+            ['YYYYMM', /\d{6}/, false],
+            ['YYYY', /\d{4}/, false],
+        ],
+        // iso time formats and regexes
+        isoTimes = [
+            ['HH:mm:ss.SSSS', /\d\d:\d\d:\d\d\.\d+/],
+            ['HH:mm:ss,SSSS', /\d\d:\d\d:\d\d,\d+/],
+            ['HH:mm:ss', /\d\d:\d\d:\d\d/],
+            ['HH:mm', /\d\d:\d\d/],
+            ['HHmmss.SSSS', /\d\d\d\d\d\d\.\d+/],
+            ['HHmmss,SSSS', /\d\d\d\d\d\d,\d+/],
+            ['HHmmss', /\d\d\d\d\d\d/],
+            ['HHmm', /\d\d\d\d/],
+            ['HH', /\d\d/],
+        ],
+        aspNetJsonRegex = /^\/?Date\((-?\d+)/i,
+        // RFC 2822 regex: For details see https://tools.ietf.org/html/rfc2822#section-3.3
+        rfc2822 = /^(?:(Mon|Tue|Wed|Thu|Fri|Sat|Sun),?\s)?(\d{1,2})\s(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s(\d{2,4})\s(\d\d):(\d\d)(?::(\d\d))?\s(?:(UT|GMT|[ECMP][SD]T)|([Zz])|([+-]\d{4}))$/,
+        obsOffsets = {
+            UT: 0,
+            GMT: 0,
+            EDT: -4 * 60,
+            EST: -5 * 60,
+            CDT: -5 * 60,
+            CST: -6 * 60,
+            MDT: -6 * 60,
+            MST: -7 * 60,
+            PDT: -7 * 60,
+            PST: -8 * 60,
+        };
+
+    // date from iso format
+    function configFromISO(config) {
+        var i,
+            l,
+            string = config._i,
+            match = extendedIsoRegex.exec(string) || basicIsoRegex.exec(string),
+            allowTime,
+            dateFormat,
+            timeFormat,
+            tzFormat;
+
+        if (match) {
+            getParsingFlags(config).iso = true;
+
+            for (i = 0, l = isoDates.length; i < l; i++) {
+                if (isoDates[i][1].exec(match[1])) {
+                    dateFormat = isoDates[i][0];
+                    allowTime = isoDates[i][2] !== false;
+                    break;
+                }
+            }
+            if (dateFormat == null) {
+                config._isValid = false;
+                return;
+            }
+            if (match[3]) {
+                for (i = 0, l = isoTimes.length; i < l; i++) {
+                    if (isoTimes[i][1].exec(match[3])) {
+                        // match[2] should be 'T' or space
+                        timeFormat = (match[2] || ' ') + isoTimes[i][0];
+                        break;
+                    }
+                }
+                if (timeFormat == null) {
+                    config._isValid = false;
+                    return;
+                }
+            }
+            if (!allowTime && timeFormat != null) {
+                config._isValid = false;
+                return;
+            }
+            if (match[4]) {
+                if (tzRegex.exec(match[4])) {
+                    tzFormat = 'Z';
+                } else {
+                    config._isValid = false;
+                    return;
+                }
+            }
+            config._f = dateFormat + (timeFormat || '') + (tzFormat || '');
+            configFromStringAndFormat(config);
+        } else {
+            config._isValid = false;
+        }
+    }
+
+    function extractFromRFC2822Strings(
+        yearStr,
+        monthStr,
+        dayStr,
+        hourStr,
+        minuteStr,
+        secondStr
+    ) {
+        var result = [
+            untruncateYear(yearStr),
+            defaultLocaleMonthsShort.indexOf(monthStr),
+            parseInt(dayStr, 10),
+            parseInt(hourStr, 10),
+            parseInt(minuteStr, 10),
+        ];
+
+        if (secondStr) {
+            result.push(parseInt(secondStr, 10));
+        }
+
+        return result;
+    }
+
+    function untruncateYear(yearStr) {
+        var year = parseInt(yearStr, 10);
+        if (year <= 49) {
+            return 2000 + year;
+        } else if (year <= 999) {
+            return 1900 + year;
+        }
+        return year;
+    }
+
+    function preprocessRFC2822(s) {
+        // Remove comments and folding whitespace and replace multiple-spaces with a single space
+        return s
+            .replace(/\([^)]*\)|[\n\t]/g, ' ')
+            .replace(/(\s\s+)/g, ' ')
+            .replace(/^\s\s*/, '')
+            .replace(/\s\s*$/, '');
+    }
+
+    function checkWeekday(weekdayStr, parsedInput, config) {
+        if (weekdayStr) {
+            // TODO: Replace the vanilla JS Date object with an independent day-of-week check.
+            var weekdayProvided = defaultLocaleWeekdaysShort.indexOf(weekdayStr),
+                weekdayActual = new Date(
+                    parsedInput[0],
+                    parsedInput[1],
+                    parsedInput[2]
+                ).getDay();
+            if (weekdayProvided !== weekdayActual) {
+                getParsingFlags(config).weekdayMismatch = true;
+                config._isValid = false;
+                return false;
+            }
+        }
+        return true;
+    }
+
+    function calculateOffset(obsOffset, militaryOffset, numOffset) {
+        if (obsOffset) {
+            return obsOffsets[obsOffset];
+        } else if (militaryOffset) {
+            // the only allowed military tz is Z
+            return 0;
+        } else {
+            var hm = parseInt(numOffset, 10),
+                m = hm % 100,
+                h = (hm - m) / 100;
+            return h * 60 + m;
+        }
+    }
+
+    // date and time from ref 2822 format
+    function configFromRFC2822(config) {
+        var match = rfc2822.exec(preprocessRFC2822(config._i)),
+            parsedArray;
+        if (match) {
+            parsedArray = extractFromRFC2822Strings(
+                match[4],
+                match[3],
+                match[2],
+                match[5],
+                match[6],
+                match[7]
+            );
+            if (!checkWeekday(match[1], parsedArray, config)) {
+                return;
+            }
+
+            config._a = parsedArray;
+            config._tzm = calculateOffset(match[8], match[9], match[10]);
+
+            config._d = createUTCDate.apply(null, config._a);
+            config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm);
+
+            getParsingFlags(config).rfc2822 = true;
+        } else {
+            config._isValid = false;
+        }
+    }
+
+    // date from 1) ASP.NET, 2) ISO, 3) RFC 2822 formats, or 4) optional fallback if parsing isn't strict
+    function configFromString(config) {
+        var matched = aspNetJsonRegex.exec(config._i);
+        if (matched !== null) {
+            config._d = new Date(+matched[1]);
+            return;
+        }
+
+        configFromISO(config);
+        if (config._isValid === false) {
+            delete config._isValid;
+        } else {
+            return;
+        }
+
+        configFromRFC2822(config);
+        if (config._isValid === false) {
+            delete config._isValid;
+        } else {
+            return;
+        }
+
+        if (config._strict) {
+            config._isValid = false;
+        } else {
+            // Final attempt, use Input Fallback
+            hooks.createFromInputFallback(config);
+        }
+    }
+
+    hooks.createFromInputFallback = deprecate(
+        'value provided is not in a recognized RFC2822 or ISO format. moment construction falls back to js Date(), ' +
+            'which is not reliable across all browsers and versions. Non RFC2822/ISO date formats are ' +
+            'discouraged. Please refer to http://momentjs.com/guides/#/warnings/js-date/ for more info.',
+        function (config) {
+            config._d = new Date(config._i + (config._useUTC ? ' UTC' : ''));
+        }
+    );
+
+    // Pick the first defined of two or three arguments.
+    function defaults(a, b, c) {
+        if (a != null) {
+            return a;
+        }
+        if (b != null) {
+            return b;
+        }
+        return c;
+    }
+
+    function currentDateArray(config) {
+        // hooks is actually the exported moment object
+        var nowValue = new Date(hooks.now());
+        if (config._useUTC) {
+            return [
+                nowValue.getUTCFullYear(),
+                nowValue.getUTCMonth(),
+                nowValue.getUTCDate(),
+            ];
+        }
+        return [nowValue.getFullYear(), nowValue.getMonth(), nowValue.getDate()];
+    }
+
+    // convert an array to a date.
+    // the array should mirror the parameters below
+    // note: all values past the year are optional and will default to the lowest possible value.
+    // [year, month, day , hour, minute, second, millisecond]
+    function configFromArray(config) {
+        var i,
+            date,
+            input = [],
+            currentDate,
+            expectedWeekday,
+            yearToUse;
+
+        if (config._d) {
+            return;
+        }
+
+        currentDate = currentDateArray(config);
+
+        //compute day of the year from weeks and weekdays
+        if (config._w && config._a[DATE] == null && config._a[MONTH] == null) {
+            dayOfYearFromWeekInfo(config);
+        }
+
+        //if the day of the year is set, figure out what it is
+        if (config._dayOfYear != null) {
+            yearToUse = defaults(config._a[YEAR], currentDate[YEAR]);
+
+            if (
+                config._dayOfYear > daysInYear(yearToUse) ||
+                config._dayOfYear === 0
+            ) {
+                getParsingFlags(config)._overflowDayOfYear = true;
+            }
+
+            date = createUTCDate(yearToUse, 0, config._dayOfYear);
+            config._a[MONTH] = date.getUTCMonth();
+            config._a[DATE] = date.getUTCDate();
+        }
+
+        // Default to current date.
+        // * if no year, month, day of month are given, default to today
+        // * if day of month is given, default month and year
+        // * if month is given, default only year
+        // * if year is given, don't default anything
+        for (i = 0; i < 3 && config._a[i] == null; ++i) {
+            config._a[i] = input[i] = currentDate[i];
+        }
+
+        // Zero out whatever was not defaulted, including time
+        for (; i < 7; i++) {
+            config._a[i] = input[i] =
+                config._a[i] == null ? (i === 2 ? 1 : 0) : config._a[i];
+        }
+
+        // Check for 24:00:00.000
+        if (
+            config._a[HOUR] === 24 &&
+            config._a[MINUTE] === 0 &&
+            config._a[SECOND] === 0 &&
+            config._a[MILLISECOND] === 0
+        ) {
+            config._nextDay = true;
+            config._a[HOUR] = 0;
+        }
+
+        config._d = (config._useUTC ? createUTCDate : createDate).apply(
+            null,
+            input
+        );
+        expectedWeekday = config._useUTC
+            ? config._d.getUTCDay()
+            : config._d.getDay();
+
+        // Apply timezone offset from input. The actual utcOffset can be changed
+        // with parseZone.
+        if (config._tzm != null) {
+            config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm);
+        }
+
+        if (config._nextDay) {
+            config._a[HOUR] = 24;
+        }
+
+        // check for mismatching day of week
+        if (
+            config._w &&
+            typeof config._w.d !== 'undefined' &&
+            config._w.d !== expectedWeekday
+        ) {
+            getParsingFlags(config).weekdayMismatch = true;
+        }
+    }
+
+    function dayOfYearFromWeekInfo(config) {
+        var w, weekYear, week, weekday, dow, doy, temp, weekdayOverflow, curWeek;
+
+        w = config._w;
+        if (w.GG != null || w.W != null || w.E != null) {
+            dow = 1;
+            doy = 4;
+
+            // TODO: We need to take the current isoWeekYear, but that depends on
+            // how we interpret now (local, utc, fixed offset). So create
+            // a now version of current config (take local/utc/offset flags, and
+            // create now).
+            weekYear = defaults(
+                w.GG,
+                config._a[YEAR],
+                weekOfYear(createLocal(), 1, 4).year
+            );
+            week = defaults(w.W, 1);
+            weekday = defaults(w.E, 1);
+            if (weekday < 1 || weekday > 7) {
+                weekdayOverflow = true;
+            }
+        } else {
+            dow = config._locale._week.dow;
+            doy = config._locale._week.doy;
+
+            curWeek = weekOfYear(createLocal(), dow, doy);
+
+            weekYear = defaults(w.gg, config._a[YEAR], curWeek.year);
+
+            // Default to current week.
+            week = defaults(w.w, curWeek.week);
+
+            if (w.d != null) {
+                // weekday -- low day numbers are considered next week
+                weekday = w.d;
+                if (weekday < 0 || weekday > 6) {
+                    weekdayOverflow = true;
+                }
+            } else if (w.e != null) {
+                // local weekday -- counting starts from beginning of week
+                weekday = w.e + dow;
+                if (w.e < 0 || w.e > 6) {
+                    weekdayOverflow = true;
+                }
+            } else {
+                // default to beginning of week
+                weekday = dow;
+            }
+        }
+        if (week < 1 || week > weeksInYear(weekYear, dow, doy)) {
+            getParsingFlags(config)._overflowWeeks = true;
+        } else if (weekdayOverflow != null) {
+            getParsingFlags(config)._overflowWeekday = true;
+        } else {
+            temp = dayOfYearFromWeeks(weekYear, week, weekday, dow, doy);
+            config._a[YEAR] = temp.year;
+            config._dayOfYear = temp.dayOfYear;
+        }
+    }
+
+    // constant that refers to the ISO standard
+    hooks.ISO_8601 = function () {};
+
+    // constant that refers to the RFC 2822 form
+    hooks.RFC_2822 = function () {};
+
+    // date from string and format string
+    function configFromStringAndFormat(config) {
+        // TODO: Move this to another part of the creation flow to prevent circular deps
+        if (config._f === hooks.ISO_8601) {
+            configFromISO(config);
+            return;
+        }
+        if (config._f === hooks.RFC_2822) {
+            configFromRFC2822(config);
+            return;
+        }
+        config._a = [];
+        getParsingFlags(config).empty = true;
+
+        // This array is used to make a Date, either with `new Date` or `Date.UTC`
+        var string = '' + config._i,
+            i,
+            parsedInput,
+            tokens,
+            token,
+            skipped,
+            stringLength = string.length,
+            totalParsedInputLength = 0,
+            era;
+
+        tokens =
+            expandFormat(config._f, config._locale).match(formattingTokens) || [];
+
+        for (i = 0; i < tokens.length; i++) {
+            token = tokens[i];
+            parsedInput = (string.match(getParseRegexForToken(token, config)) ||
+                [])[0];
+            if (parsedInput) {
+                skipped = string.substr(0, string.indexOf(parsedInput));
+                if (skipped.length > 0) {
+                    getParsingFlags(config).unusedInput.push(skipped);
+                }
+                string = string.slice(
+                    string.indexOf(parsedInput) + parsedInput.length
+                );
+                totalParsedInputLength += parsedInput.length;
+            }
+            // don't parse if it's not a known token
+            if (formatTokenFunctions[token]) {
+                if (parsedInput) {
+                    getParsingFlags(config).empty = false;
+                } else {
+                    getParsingFlags(config).unusedTokens.push(token);
+                }
+                addTimeToArrayFromToken(token, parsedInput, config);
+            } else if (config._strict && !parsedInput) {
+                getParsingFlags(config).unusedTokens.push(token);
+            }
+        }
+
+        // add remaining unparsed input length to the string
+        getParsingFlags(config).charsLeftOver =
+            stringLength - totalParsedInputLength;
+        if (string.length > 0) {
+            getParsingFlags(config).unusedInput.push(string);
+        }
+
+        // clear _12h flag if hour is <= 12
+        if (
+            config._a[HOUR] <= 12 &&
+            getParsingFlags(config).bigHour === true &&
+            config._a[HOUR] > 0
+        ) {
+            getParsingFlags(config).bigHour = undefined;
+        }
+
+        getParsingFlags(config).parsedDateParts = config._a.slice(0);
+        getParsingFlags(config).meridiem = config._meridiem;
+        // handle meridiem
+        config._a[HOUR] = meridiemFixWrap(
+            config._locale,
+            config._a[HOUR],
+            config._meridiem
+        );
+
+        // handle era
+        era = getParsingFlags(config).era;
+        if (era !== null) {
+            config._a[YEAR] = config._locale.erasConvertYear(era, config._a[YEAR]);
+        }
+
+        configFromArray(config);
+        checkOverflow(config);
+    }
+
+    function meridiemFixWrap(locale, hour, meridiem) {
+        var isPm;
+
+        if (meridiem == null) {
+            // nothing to do
+            return hour;
+        }
+        if (locale.meridiemHour != null) {
+            return locale.meridiemHour(hour, meridiem);
+        } else if (locale.isPM != null) {
+            // Fallback
+            isPm = locale.isPM(meridiem);
+            if (isPm && hour < 12) {
+                hour += 12;
+            }
+            if (!isPm && hour === 12) {
+                hour = 0;
+            }
+            return hour;
+        } else {
+            // this is not supposed to happen
+            return hour;
+        }
+    }
+
+    // date from string and array of format strings
+    function configFromStringAndArray(config) {
+        var tempConfig,
+            bestMoment,
+            scoreToBeat,
+            i,
+            currentScore,
+            validFormatFound,
+            bestFormatIsValid = false;
+
+        if (config._f.length === 0) {
+            getParsingFlags(config).invalidFormat = true;
+            config._d = new Date(NaN);
+            return;
+        }
+
+        for (i = 0; i < config._f.length; i++) {
+            currentScore = 0;
+            validFormatFound = false;
+            tempConfig = copyConfig({}, config);
+            if (config._useUTC != null) {
+                tempConfig._useUTC = config._useUTC;
+            }
+            tempConfig._f = config._f[i];
+            configFromStringAndFormat(tempConfig);
+
+            if (isValid(tempConfig)) {
+                validFormatFound = true;
+            }
+
+            // if there is any input that was not parsed add a penalty for that format
+            currentScore += getParsingFlags(tempConfig).charsLeftOver;
+
+            //or tokens
+            currentScore += getParsingFlags(tempConfig).unusedTokens.length * 10;
+
+            getParsingFlags(tempConfig).score = currentScore;
+
+            if (!bestFormatIsValid) {
+                if (
+                    scoreToBeat == null ||
+                    currentScore < scoreToBeat ||
+                    validFormatFound
+                ) {
+                    scoreToBeat = currentScore;
+                    bestMoment = tempConfig;
+                    if (validFormatFound) {
+                        bestFormatIsValid = true;
+                    }
+                }
+            } else {
+                if (currentScore < scoreToBeat) {
+                    scoreToBeat = currentScore;
+                    bestMoment = tempConfig;
+                }
+            }
+        }
+
+        extend(config, bestMoment || tempConfig);
+    }
+
+    function configFromObject(config) {
+        if (config._d) {
+            return;
+        }
+
+        var i = normalizeObjectUnits(config._i),
+            dayOrDate = i.day === undefined ? i.date : i.day;
+        config._a = map(
+            [i.year, i.month, dayOrDate, i.hour, i.minute, i.second, i.millisecond],
+            function (obj) {
+                return obj && parseInt(obj, 10);
+            }
+        );
+
+        configFromArray(config);
+    }
+
+    function createFromConfig(config) {
+        var res = new Moment(checkOverflow(prepareConfig(config)));
+        if (res._nextDay) {
+            // Adding is smart enough around DST
+            res.add(1, 'd');
+            res._nextDay = undefined;
+        }
+
+        return res;
+    }
+
+    function prepareConfig(config) {
+        var input = config._i,
+            format = config._f;
+
+        config._locale = config._locale || getLocale(config._l);
+
+        if (input === null || (format === undefined && input === '')) {
+            return createInvalid({ nullInput: true });
+        }
+
+        if (typeof input === 'string') {
+            config._i = input = config._locale.preparse(input);
+        }
+
+        if (isMoment(input)) {
+            return new Moment(checkOverflow(input));
+        } else if (isDate(input)) {
+            config._d = input;
+        } else if (isArray(format)) {
+            configFromStringAndArray(config);
+        } else if (format) {
+            configFromStringAndFormat(config);
+        } else {
+            configFromInput(config);
+        }
+
+        if (!isValid(config)) {
+            config._d = null;
+        }
+
+        return config;
+    }
+
+    function configFromInput(config) {
+        var input = config._i;
+        if (isUndefined(input)) {
+            config._d = new Date(hooks.now());
+        } else if (isDate(input)) {
+            config._d = new Date(input.valueOf());
+        } else if (typeof input === 'string') {
+            configFromString(config);
+        } else if (isArray(input)) {
+            config._a = map(input.slice(0), function (obj) {
+                return parseInt(obj, 10);
+            });
+            configFromArray(config);
+        } else if (isObject(input)) {
+            configFromObject(config);
+        } else if (isNumber(input)) {
+            // from milliseconds
+            config._d = new Date(input);
+        } else {
+            hooks.createFromInputFallback(config);
+        }
+    }
+
+    function createLocalOrUTC(input, format, locale, strict, isUTC) {
+        var c = {};
+
+        if (format === true || format === false) {
+            strict = format;
+            format = undefined;
+        }
+
+        if (locale === true || locale === false) {
+            strict = locale;
+            locale = undefined;
+        }
+
+        if (
+            (isObject(input) && isObjectEmpty(input)) ||
+            (isArray(input) && input.length === 0)
+        ) {
+            input = undefined;
+        }
+        // object construction must be done this way.
+        // https://github.com/moment/moment/issues/1423
+        c._isAMomentObject = true;
+        c._useUTC = c._isUTC = isUTC;
+        c._l = locale;
+        c._i = input;
+        c._f = format;
+        c._strict = strict;
+
+        return createFromConfig(c);
+    }
+
+    function createLocal(input, format, locale, strict) {
+        return createLocalOrUTC(input, format, locale, strict, false);
+    }
+
+    var prototypeMin = deprecate(
+            'moment().min is deprecated, use moment.max instead. http://momentjs.com/guides/#/warnings/min-max/',
+            function () {
+                var other = createLocal.apply(null, arguments);
+                if (this.isValid() && other.isValid()) {
+                    return other < this ? this : other;
+                } else {
+                    return createInvalid();
+                }
+            }
+        ),
+        prototypeMax = deprecate(
+            'moment().max is deprecated, use moment.min instead. http://momentjs.com/guides/#/warnings/min-max/',
+            function () {
+                var other = createLocal.apply(null, arguments);
+                if (this.isValid() && other.isValid()) {
+                    return other > this ? this : other;
+                } else {
+                    return createInvalid();
+                }
+            }
+        );
+
+    // Pick a moment m from moments so that m[fn](other) is true for all
+    // other. This relies on the function fn to be transitive.
+    //
+    // moments should either be an array of moment objects or an array, whose
+    // first element is an array of moment objects.
+    function pickBy(fn, moments) {
+        var res, i;
+        if (moments.length === 1 && isArray(moments[0])) {
+            moments = moments[0];
+        }
+        if (!moments.length) {
+            return createLocal();
+        }
+        res = moments[0];
+        for (i = 1; i < moments.length; ++i) {
+            if (!moments[i].isValid() || moments[i][fn](res)) {
+                res = moments[i];
+            }
+        }
+        return res;
+    }
+
+    // TODO: Use [].sort instead?
+    function min() {
+        var args = [].slice.call(arguments, 0);
+
+        return pickBy('isBefore', args);
+    }
+
+    function max() {
+        var args = [].slice.call(arguments, 0);
+
+        return pickBy('isAfter', args);
+    }
+
+    var now = function () {
+        return Date.now ? Date.now() : +new Date();
+    };
+
+    var ordering = [
+        'year',
+        'quarter',
+        'month',
+        'week',
+        'day',
+        'hour',
+        'minute',
+        'second',
+        'millisecond',
+    ];
+
+    function isDurationValid(m) {
+        var key,
+            unitHasDecimal = false,
+            i;
+        for (key in m) {
+            if (
+                hasOwnProp(m, key) &&
+                !(
+                    indexOf.call(ordering, key) !== -1 &&
+                    (m[key] == null || !isNaN(m[key]))
+                )
+            ) {
+                return false;
+            }
+        }
+
+        for (i = 0; i < ordering.length; ++i) {
+            if (m[ordering[i]]) {
+                if (unitHasDecimal) {
+                    return false; // only allow non-integers for smallest unit
+                }
+                if (parseFloat(m[ordering[i]]) !== toInt(m[ordering[i]])) {
+                    unitHasDecimal = true;
+                }
+            }
+        }
+
+        return true;
+    }
+
+    function isValid$1() {
+        return this._isValid;
+    }
+
+    function createInvalid$1() {
+        return createDuration(NaN);
+    }
+
+    function Duration(duration) {
+        var normalizedInput = normalizeObjectUnits(duration),
+            years = normalizedInput.year || 0,
+            quarters = normalizedInput.quarter || 0,
+            months = normalizedInput.month || 0,
+            weeks = normalizedInput.week || normalizedInput.isoWeek || 0,
+            days = normalizedInput.day || 0,
+            hours = normalizedInput.hour || 0,
+            minutes = normalizedInput.minute || 0,
+            seconds = normalizedInput.second || 0,
+            milliseconds = normalizedInput.millisecond || 0;
+
+        this._isValid = isDurationValid(normalizedInput);
+
+        // representation for dateAddRemove
+        this._milliseconds =
+            +milliseconds +
+            seconds * 1e3 + // 1000
+            minutes * 6e4 + // 1000 * 60
+            hours * 1000 * 60 * 60; //using 1000 * 60 * 60 instead of 36e5 to avoid floating point rounding errors https://github.com/moment/moment/issues/2978
+        // Because of dateAddRemove treats 24 hours as different from a
+        // day when working around DST, we need to store them separately
+        this._days = +days + weeks * 7;
+        // It is impossible to translate months into days without knowing
+        // which months you are are talking about, so we have to store
+        // it separately.
+        this._months = +months + quarters * 3 + years * 12;
+
+        this._data = {};
+
+        this._locale = getLocale();
+
+        this._bubble();
+    }
+
+    function isDuration(obj) {
+        return obj instanceof Duration;
+    }
+
+    function absRound(number) {
+        if (number < 0) {
+            return Math.round(-1 * number) * -1;
+        } else {
+            return Math.round(number);
+        }
+    }
+
+    // compare two arrays, return the number of differences
+    function compareArrays(array1, array2, dontConvert) {
+        var len = Math.min(array1.length, array2.length),
+            lengthDiff = Math.abs(array1.length - array2.length),
+            diffs = 0,
+            i;
+        for (i = 0; i < len; i++) {
+            if (
+                (dontConvert && array1[i] !== array2[i]) ||
+                (!dontConvert && toInt(array1[i]) !== toInt(array2[i]))
+            ) {
+                diffs++;
+            }
+        }
+        return diffs + lengthDiff;
+    }
+
+    // FORMATTING
+
+    function offset(token, separator) {
+        addFormatToken(token, 0, 0, function () {
+            var offset = this.utcOffset(),
+                sign = '+';
+            if (offset < 0) {
+                offset = -offset;
+                sign = '-';
+            }
+            return (
+                sign +
+                zeroFill(~~(offset / 60), 2) +
+                separator +
+                zeroFill(~~offset % 60, 2)
+            );
+        });
+    }
+
+    offset('Z', ':');
+    offset('ZZ', '');
+
+    // PARSING
+
+    addRegexToken('Z', matchShortOffset);
+    addRegexToken('ZZ', matchShortOffset);
+    addParseToken(['Z', 'ZZ'], function (input, array, config) {
+        config._useUTC = true;
+        config._tzm = offsetFromString(matchShortOffset, input);
+    });
+
+    // HELPERS
+
+    // timezone chunker
+    // '+10:00' > ['10',  '00']
+    // '-1530'  > ['-15', '30']
+    var chunkOffset = /([\+\-]|\d\d)/gi;
+
+    function offsetFromString(matcher, string) {
+        var matches = (string || '').match(matcher),
+            chunk,
+            parts,
+            minutes;
+
+        if (matches === null) {
+            return null;
+        }
+
+        chunk = matches[matches.length - 1] || [];
+        parts = (chunk + '').match(chunkOffset) || ['-', 0, 0];
+        minutes = +(parts[1] * 60) + toInt(parts[2]);
+
+        return minutes === 0 ? 0 : parts[0] === '+' ? minutes : -minutes;
+    }
+
+    // Return a moment from input, that is local/utc/zone equivalent to model.
+    function cloneWithOffset(input, model) {
+        var res, diff;
+        if (model._isUTC) {
+            res = model.clone();
+            diff =
+                (isMoment(input) || isDate(input)
+                    ? input.valueOf()
+                    : createLocal(input).valueOf()) - res.valueOf();
+            // Use low-level api, because this fn is low-level api.
+            res._d.setTime(res._d.valueOf() + diff);
+            hooks.updateOffset(res, false);
+            return res;
+        } else {
+            return createLocal(input).local();
+        }
+    }
+
+    function getDateOffset(m) {
+        // On Firefox.24 Date#getTimezoneOffset returns a floating point.
+        // https://github.com/moment/moment/pull/1871
+        return -Math.round(m._d.getTimezoneOffset());
+    }
+
+    // HOOKS
+
+    // This function will be called whenever a moment is mutated.
+    // It is intended to keep the offset in sync with the timezone.
+    hooks.updateOffset = function () {};
+
+    // MOMENTS
+
+    // keepLocalTime = true means only change the timezone, without
+    // affecting the local hour. So 5:31:26 +0300 --[utcOffset(2, true)]-->
+    // 5:31:26 +0200 It is possible that 5:31:26 doesn't exist with offset
+    // +0200, so we adjust the time as needed, to be valid.
+    //
+    // Keeping the time actually adds/subtracts (one hour)
+    // from the actual represented time. That is why we call updateOffset
+    // a second time. In case it wants us to change the offset again
+    // _changeInProgress == true case, then we have to adjust, because
+    // there is no such time in the given timezone.
+    function getSetOffset(input, keepLocalTime, keepMinutes) {
+        var offset = this._offset || 0,
+            localAdjust;
+        if (!this.isValid()) {
+            return input != null ? this : NaN;
+        }
+        if (input != null) {
+            if (typeof input === 'string') {
+                input = offsetFromString(matchShortOffset, input);
+                if (input === null) {
+                    return this;
+                }
+            } else if (Math.abs(input) < 16 && !keepMinutes) {
+                input = input * 60;
+            }
+            if (!this._isUTC && keepLocalTime) {
+                localAdjust = getDateOffset(this);
+            }
+            this._offset = input;
+            this._isUTC = true;
+            if (localAdjust != null) {
+                this.add(localAdjust, 'm');
+            }
+            if (offset !== input) {
+                if (!keepLocalTime || this._changeInProgress) {
+                    addSubtract(
+                        this,
+                        createDuration(input - offset, 'm'),
+                        1,
+                        false
+                    );
+                } else if (!this._changeInProgress) {
+                    this._changeInProgress = true;
+                    hooks.updateOffset(this, true);
+                    this._changeInProgress = null;
+                }
+            }
+            return this;
+        } else {
+            return this._isUTC ? offset : getDateOffset(this);
+        }
+    }
+
+    function getSetZone(input, keepLocalTime) {
+        if (input != null) {
+            if (typeof input !== 'string') {
+                input = -input;
+            }
+
+            this.utcOffset(input, keepLocalTime);
+
+            return this;
+        } else {
+            return -this.utcOffset();
+        }
+    }
+
+    function setOffsetToUTC(keepLocalTime) {
+        return this.utcOffset(0, keepLocalTime);
+    }
+
+    function setOffsetToLocal(keepLocalTime) {
+        if (this._isUTC) {
+            this.utcOffset(0, keepLocalTime);
+            this._isUTC = false;
+
+            if (keepLocalTime) {
+                this.subtract(getDateOffset(this), 'm');
+            }
+        }
+        return this;
+    }
+
+    function setOffsetToParsedOffset() {
+        if (this._tzm != null) {
+            this.utcOffset(this._tzm, false, true);
+        } else if (typeof this._i === 'string') {
+            var tZone = offsetFromString(matchOffset, this._i);
+            if (tZone != null) {
+                this.utcOffset(tZone);
+            } else {
+                this.utcOffset(0, true);
+            }
+        }
+        return this;
+    }
+
+    function hasAlignedHourOffset(input) {
+        if (!this.isValid()) {
+            return false;
+        }
+        input = input ? createLocal(input).utcOffset() : 0;
+
+        return (this.utcOffset() - input) % 60 === 0;
+    }
+
+    function isDaylightSavingTime() {
+        return (
+            this.utcOffset() > this.clone().month(0).utcOffset() ||
+            this.utcOffset() > this.clone().month(5).utcOffset()
+        );
+    }
+
+    function isDaylightSavingTimeShifted() {
+        if (!isUndefined(this._isDSTShifted)) {
+            return this._isDSTShifted;
+        }
+
+        var c = {},
+            other;
+
+        copyConfig(c, this);
+        c = prepareConfig(c);
+
+        if (c._a) {
+            other = c._isUTC ? createUTC(c._a) : createLocal(c._a);
+            this._isDSTShifted =
+                this.isValid() && compareArrays(c._a, other.toArray()) > 0;
+        } else {
+            this._isDSTShifted = false;
+        }
+
+        return this._isDSTShifted;
+    }
+
+    function isLocal() {
+        return this.isValid() ? !this._isUTC : false;
+    }
+
+    function isUtcOffset() {
+        return this.isValid() ? this._isUTC : false;
+    }
+
+    function isUtc() {
+        return this.isValid() ? this._isUTC && this._offset === 0 : false;
+    }
+
+    // ASP.NET json date format regex
+    var aspNetRegex = /^(-|\+)?(?:(\d*)[. ])?(\d+):(\d+)(?::(\d+)(\.\d*)?)?$/,
+        // from http://docs.closure-library.googlecode.com/git/closure_goog_date_date.js.source.html
+        // somewhat more in line with 4.4.3.2 2004 spec, but allows decimal anywhere
+        // and further modified to allow for strings containing both week and day
+        isoRegex = /^(-|\+)?P(?:([-+]?[0-9,.]*)Y)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)W)?(?:([-+]?[0-9,.]*)D)?(?:T(?:([-+]?[0-9,.]*)H)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)S)?)?$/;
+
+    function createDuration(input, key) {
+        var duration = input,
+            // matching against regexp is expensive, do it on demand
+            match = null,
+            sign,
+            ret,
+            diffRes;
+
+        if (isDuration(input)) {
+            duration = {
+                ms: input._milliseconds,
+                d: input._days,
+                M: input._months,
+            };
+        } else if (isNumber(input) || !isNaN(+input)) {
+            duration = {};
+            if (key) {
+                duration[key] = +input;
+            } else {
+                duration.milliseconds = +input;
+            }
+        } else if ((match = aspNetRegex.exec(input))) {
+            sign = match[1] === '-' ? -1 : 1;
+            duration = {
+                y: 0,
+                d: toInt(match[DATE]) * sign,
+                h: toInt(match[HOUR]) * sign,
+                m: toInt(match[MINUTE]) * sign,
+                s: toInt(match[SECOND]) * sign,
+                ms: toInt(absRound(match[MILLISECOND] * 1000)) * sign, // the millisecond decimal point is included in the match
+            };
+        } else if ((match = isoRegex.exec(input))) {
+            sign = match[1] === '-' ? -1 : 1;
+            duration = {
+                y: parseIso(match[2], sign),
+                M: parseIso(match[3], sign),
+                w: parseIso(match[4], sign),
+                d: parseIso(match[5], sign),
+                h: parseIso(match[6], sign),
+                m: parseIso(match[7], sign),
+                s: parseIso(match[8], sign),
+            };
+        } else if (duration == null) {
+            // checks for null or undefined
+            duration = {};
+        } else if (
+            typeof duration === 'object' &&
+            ('from' in duration || 'to' in duration)
+        ) {
+            diffRes = momentsDifference(
+                createLocal(duration.from),
+                createLocal(duration.to)
+            );
+
+            duration = {};
+            duration.ms = diffRes.milliseconds;
+            duration.M = diffRes.months;
+        }
+
+        ret = new Duration(duration);
+
+        if (isDuration(input) && hasOwnProp(input, '_locale')) {
+            ret._locale = input._locale;
+        }
+
+        if (isDuration(input) && hasOwnProp(input, '_isValid')) {
+            ret._isValid = input._isValid;
+        }
+
+        return ret;
+    }
+
+    createDuration.fn = Duration.prototype;
+    createDuration.invalid = createInvalid$1;
+
+    function parseIso(inp, sign) {
+        // We'd normally use ~~inp for this, but unfortunately it also
+        // converts floats to ints.
+        // inp may be undefined, so careful calling replace on it.
+        var res = inp && parseFloat(inp.replace(',', '.'));
+        // apply sign while we're at it
+        return (isNaN(res) ? 0 : res) * sign;
+    }
+
+    function positiveMomentsDifference(base, other) {
+        var res = {};
+
+        res.months =
+            other.month() - base.month() + (other.year() - base.year()) * 12;
+        if (base.clone().add(res.months, 'M').isAfter(other)) {
+            --res.months;
+        }
+
+        res.milliseconds = +other - +base.clone().add(res.months, 'M');
+
+        return res;
+    }
+
+    function momentsDifference(base, other) {
+        var res;
+        if (!(base.isValid() && other.isValid())) {
+            return { milliseconds: 0, months: 0 };
+        }
+
+        other = cloneWithOffset(other, base);
+        if (base.isBefore(other)) {
+            res = positiveMomentsDifference(base, other);
+        } else {
+            res = positiveMomentsDifference(other, base);
+            res.milliseconds = -res.milliseconds;
+            res.months = -res.months;
+        }
+
+        return res;
+    }
+
+    // TODO: remove 'name' arg after deprecation is removed
+    function createAdder(direction, name) {
+        return function (val, period) {
+            var dur, tmp;
+            //invert the arguments, but complain about it
+            if (period !== null && !isNaN(+period)) {
+                deprecateSimple(
+                    name,
+                    'moment().' +
+                        name +
+                        '(period, number) is deprecated. Please use moment().' +
+                        name +
+                        '(number, period). ' +
+                        'See http://momentjs.com/guides/#/warnings/add-inverted-param/ for more info.'
+                );
+                tmp = val;
+                val = period;
+                period = tmp;
+            }
+
+            dur = createDuration(val, period);
+            addSubtract(this, dur, direction);
+            return this;
+        };
+    }
+
+    function addSubtract(mom, duration, isAdding, updateOffset) {
+        var milliseconds = duration._milliseconds,
+            days = absRound(duration._days),
+            months = absRound(duration._months);
+
+        if (!mom.isValid()) {
+            // No op
+            return;
+        }
+
+        updateOffset = updateOffset == null ? true : updateOffset;
+
+        if (months) {
+            setMonth(mom, get(mom, 'Month') + months * isAdding);
+        }
+        if (days) {
+            set$1(mom, 'Date', get(mom, 'Date') + days * isAdding);
+        }
+        if (milliseconds) {
+            mom._d.setTime(mom._d.valueOf() + milliseconds * isAdding);
+        }
+        if (updateOffset) {
+            hooks.updateOffset(mom, days || months);
+        }
+    }
+
+    var add = createAdder(1, 'add'),
+        subtract = createAdder(-1, 'subtract');
+
+    function isString(input) {
+        return typeof input === 'string' || input instanceof String;
+    }
+
+    // type MomentInput = Moment | Date | string | number | (number | string)[] | MomentInputObject | void; // null | undefined
+    function isMomentInput(input) {
+        return (
+            isMoment(input) ||
+            isDate(input) ||
+            isString(input) ||
+            isNumber(input) ||
+            isNumberOrStringArray(input) ||
+            isMomentInputObject(input) ||
+            input === null ||
+            input === undefined
+        );
+    }
+
+    function isMomentInputObject(input) {
+        var objectTest = isObject(input) && !isObjectEmpty(input),
+            propertyTest = false,
+            properties = [
+                'years',
+                'year',
+                'y',
+                'months',
+                'month',
+                'M',
+                'days',
+                'day',
+                'd',
+                'dates',
+                'date',
+                'D',
+                'hours',
+                'hour',
+                'h',
+                'minutes',
+                'minute',
+                'm',
+                'seconds',
+                'second',
+                's',
+                'milliseconds',
+                'millisecond',
+                'ms',
+            ],
+            i,
+            property;
+
+        for (i = 0; i < properties.length; i += 1) {
+            property = properties[i];
+            propertyTest = propertyTest || hasOwnProp(input, property);
+        }
+
+        return objectTest && propertyTest;
+    }
+
+    function isNumberOrStringArray(input) {
+        var arrayTest = isArray(input),
+            dataTypeTest = false;
+        if (arrayTest) {
+            dataTypeTest =
+                input.filter(function (item) {
+                    return !isNumber(item) && isString(input);
+                }).length === 0;
+        }
+        return arrayTest && dataTypeTest;
+    }
+
+    function isCalendarSpec(input) {
+        var objectTest = isObject(input) && !isObjectEmpty(input),
+            propertyTest = false,
+            properties = [
+                'sameDay',
+                'nextDay',
+                'lastDay',
+                'nextWeek',
+                'lastWeek',
+                'sameElse',
+            ],
+            i,
+            property;
+
+        for (i = 0; i < properties.length; i += 1) {
+            property = properties[i];
+            propertyTest = propertyTest || hasOwnProp(input, property);
+        }
+
+        return objectTest && propertyTest;
+    }
+
+    function getCalendarFormat(myMoment, now) {
+        var diff = myMoment.diff(now, 'days', true);
+        return diff < -6
+            ? 'sameElse'
+            : diff < -1
+            ? 'lastWeek'
+            : diff < 0
+            ? 'lastDay'
+            : diff < 1
+            ? 'sameDay'
+            : diff < 2
+            ? 'nextDay'
+            : diff < 7
+            ? 'nextWeek'
+            : 'sameElse';
+    }
+
+    function calendar$1(time, formats) {
+        // Support for single parameter, formats only overload to the calendar function
+        if (arguments.length === 1) {
+            if (!arguments[0]) {
+                time = undefined;
+                formats = undefined;
+            } else if (isMomentInput(arguments[0])) {
+                time = arguments[0];
+                formats = undefined;
+            } else if (isCalendarSpec(arguments[0])) {
+                formats = arguments[0];
+                time = undefined;
+            }
+        }
+        // We want to compare the start of today, vs this.
+        // Getting start-of-today depends on whether we're local/utc/offset or not.
+        var now = time || createLocal(),
+            sod = cloneWithOffset(now, this).startOf('day'),
+            format = hooks.calendarFormat(this, sod) || 'sameElse',
+            output =
+                formats &&
+                (isFunction(formats[format])
+                    ? formats[format].call(this, now)
+                    : formats[format]);
+
+        return this.format(
+            output || this.localeData().calendar(format, this, createLocal(now))
+        );
+    }
+
+    function clone() {
+        return new Moment(this);
+    }
+
+    function isAfter(input, units) {
+        var localInput = isMoment(input) ? input : createLocal(input);
+        if (!(this.isValid() && localInput.isValid())) {
+            return false;
+        }
+        units = normalizeUnits(units) || 'millisecond';
+        if (units === 'millisecond') {
+            return this.valueOf() > localInput.valueOf();
+        } else {
+            return localInput.valueOf() < this.clone().startOf(units).valueOf();
+        }
+    }
+
+    function isBefore(input, units) {
+        var localInput = isMoment(input) ? input : createLocal(input);
+        if (!(this.isValid() && localInput.isValid())) {
+            return false;
+        }
+        units = normalizeUnits(units) || 'millisecond';
+        if (units === 'millisecond') {
+            return this.valueOf() < localInput.valueOf();
+        } else {
+            return this.clone().endOf(units).valueOf() < localInput.valueOf();
+        }
+    }
+
+    function isBetween(from, to, units, inclusivity) {
+        var localFrom = isMoment(from) ? from : createLocal(from),
+            localTo = isMoment(to) ? to : createLocal(to);
+        if (!(this.isValid() && localFrom.isValid() && localTo.isValid())) {
+            return false;
+        }
+        inclusivity = inclusivity || '()';
+        return (
+            (inclusivity[0] === '('
+                ? this.isAfter(localFrom, units)
+                : !this.isBefore(localFrom, units)) &&
+            (inclusivity[1] === ')'
+                ? this.isBefore(localTo, units)
+                : !this.isAfter(localTo, units))
+        );
+    }
+
+    function isSame(input, units) {
+        var localInput = isMoment(input) ? input : createLocal(input),
+            inputMs;
+        if (!(this.isValid() && localInput.isValid())) {
+            return false;
+        }
+        units = normalizeUnits(units) || 'millisecond';
+        if (units === 'millisecond') {
+            return this.valueOf() === localInput.valueOf();
+        } else {
+            inputMs = localInput.valueOf();
+            return (
+                this.clone().startOf(units).valueOf() <= inputMs &&
+                inputMs <= this.clone().endOf(units).valueOf()
+            );
+        }
+    }
+
+    function isSameOrAfter(input, units) {
+        return this.isSame(input, units) || this.isAfter(input, units);
+    }
+
+    function isSameOrBefore(input, units) {
+        return this.isSame(input, units) || this.isBefore(input, units);
+    }
+
+    function diff(input, units, asFloat) {
+        var that, zoneDelta, output;
+
+        if (!this.isValid()) {
+            return NaN;
+        }
+
+        that = cloneWithOffset(input, this);
+
+        if (!that.isValid()) {
+            return NaN;
+        }
+
+        zoneDelta = (that.utcOffset() - this.utcOffset()) * 6e4;
+
+        units = normalizeUnits(units);
+
+        switch (units) {
+            case 'year':
+                output = monthDiff(this, that) / 12;
+                break;
+            case 'month':
+                output = monthDiff(this, that);
+                break;
+            case 'quarter':
+                output = monthDiff(this, that) / 3;
+                break;
+            case 'second':
+                output = (this - that) / 1e3;
+                break; // 1000
+            case 'minute':
+                output = (this - that) / 6e4;
+                break; // 1000 * 60
+            case 'hour':
+                output = (this - that) / 36e5;
+                break; // 1000 * 60 * 60
+            case 'day':
+                output = (this - that - zoneDelta) / 864e5;
+                break; // 1000 * 60 * 60 * 24, negate dst
+            case 'week':
+                output = (this - that - zoneDelta) / 6048e5;
+                break; // 1000 * 60 * 60 * 24 * 7, negate dst
+            default:
+                output = this - that;
+        }
+
+        return asFloat ? output : absFloor(output);
+    }
+
+    function monthDiff(a, b) {
+        if (a.date() < b.date()) {
+            // end-of-month calculations work correct when the start month has more
+            // days than the end month.
+            return -monthDiff(b, a);
+        }
+        // difference in months
+        var wholeMonthDiff = (b.year() - a.year()) * 12 + (b.month() - a.month()),
+            // b is in (anchor - 1 month, anchor + 1 month)
+            anchor = a.clone().add(wholeMonthDiff, 'months'),
+            anchor2,
+            adjust;
+
+        if (b - anchor < 0) {
+            anchor2 = a.clone().add(wholeMonthDiff - 1, 'months');
+            // linear across the month
+            adjust = (b - anchor) / (anchor - anchor2);
+        } else {
+            anchor2 = a.clone().add(wholeMonthDiff + 1, 'months');
+            // linear across the month
+            adjust = (b - anchor) / (anchor2 - anchor);
+        }
+
+        //check for negative zero, return zero if negative zero
+        return -(wholeMonthDiff + adjust) || 0;
+    }
+
+    hooks.defaultFormat = 'YYYY-MM-DDTHH:mm:ssZ';
+    hooks.defaultFormatUtc = 'YYYY-MM-DDTHH:mm:ss[Z]';
+
+    function toString() {
+        return this.clone().locale('en').format('ddd MMM DD YYYY HH:mm:ss [GMT]ZZ');
+    }
+
+    function toISOString(keepOffset) {
+        if (!this.isValid()) {
+            return null;
+        }
+        var utc = keepOffset !== true,
+            m = utc ? this.clone().utc() : this;
+        if (m.year() < 0 || m.year() > 9999) {
+            return formatMoment(
+                m,
+                utc
+                    ? 'YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]'
+                    : 'YYYYYY-MM-DD[T]HH:mm:ss.SSSZ'
+            );
+        }
+        if (isFunction(Date.prototype.toISOString)) {
+            // native implementation is ~50x faster, use it when we can
+            if (utc) {
+                return this.toDate().toISOString();
+            } else {
+                return new Date(this.valueOf() + this.utcOffset() * 60 * 1000)
+                    .toISOString()
+                    .replace('Z', formatMoment(m, 'Z'));
+            }
+        }
+        return formatMoment(
+            m,
+            utc ? 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]' : 'YYYY-MM-DD[T]HH:mm:ss.SSSZ'
+        );
+    }
+
+    /**
+     * Return a human readable representation of a moment that can
+     * also be evaluated to get a new moment which is the same
+     *
+     * @link https://nodejs.org/dist/latest/docs/api/util.html#util_custom_inspect_function_on_objects
+     */
+    function inspect() {
+        if (!this.isValid()) {
+            return 'moment.invalid(/* ' + this._i + ' */)';
+        }
+        var func = 'moment',
+            zone = '',
+            prefix,
+            year,
+            datetime,
+            suffix;
+        if (!this.isLocal()) {
+            func = this.utcOffset() === 0 ? 'moment.utc' : 'moment.parseZone';
+            zone = 'Z';
+        }
+        prefix = '[' + func + '("]';
+        year = 0 <= this.year() && this.year() <= 9999 ? 'YYYY' : 'YYYYYY';
+        datetime = '-MM-DD[T]HH:mm:ss.SSS';
+        suffix = zone + '[")]';
+
+        return this.format(prefix + year + datetime + suffix);
+    }
+
+    function format(inputString) {
+        if (!inputString) {
+            inputString = this.isUtc()
+                ? hooks.defaultFormatUtc
+                : hooks.defaultFormat;
+        }
+        var output = formatMoment(this, inputString);
+        return this.localeData().postformat(output);
+    }
+
+    function from(time, withoutSuffix) {
+        if (
+            this.isValid() &&
+            ((isMoment(time) && time.isValid()) || createLocal(time).isValid())
+        ) {
+            return createDuration({ to: this, from: time })
+                .locale(this.locale())
+                .humanize(!withoutSuffix);
+        } else {
+            return this.localeData().invalidDate();
+        }
+    }
+
+    function fromNow(withoutSuffix) {
+        return this.from(createLocal(), withoutSuffix);
+    }
+
+    function to(time, withoutSuffix) {
+        if (
+            this.isValid() &&
+            ((isMoment(time) && time.isValid()) || createLocal(time).isValid())
+        ) {
+            return createDuration({ from: this, to: time })
+                .locale(this.locale())
+                .humanize(!withoutSuffix);
+        } else {
+            return this.localeData().invalidDate();
+        }
+    }
+
+    function toNow(withoutSuffix) {
+        return this.to(createLocal(), withoutSuffix);
+    }
+
+    // If passed a locale key, it will set the locale for this
+    // instance.  Otherwise, it will return the locale configuration
+    // variables for this instance.
+    function locale(key) {
+        var newLocaleData;
+
+        if (key === undefined) {
+            return this._locale._abbr;
+        } else {
+            newLocaleData = getLocale(key);
+            if (newLocaleData != null) {
+                this._locale = newLocaleData;
+            }
+            return this;
+        }
+    }
+
+    var lang = deprecate(
+        'moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.',
+        function (key) {
+            if (key === undefined) {
+                return this.localeData();
+            } else {
+                return this.locale(key);
+            }
+        }
+    );
+
+    function localeData() {
+        return this._locale;
+    }
+
+    var MS_PER_SECOND = 1000,
+        MS_PER_MINUTE = 60 * MS_PER_SECOND,
+        MS_PER_HOUR = 60 * MS_PER_MINUTE,
+        MS_PER_400_YEARS = (365 * 400 + 97) * 24 * MS_PER_HOUR;
+
+    // actual modulo - handles negative numbers (for dates before 1970):
+    function mod$1(dividend, divisor) {
+        return ((dividend % divisor) + divisor) % divisor;
+    }
+
+    function localStartOfDate(y, m, d) {
+        // the date constructor remaps years 0-99 to 1900-1999
+        if (y < 100 && y >= 0) {
+            // preserve leap years using a full 400 year cycle, then reset
+            return new Date(y + 400, m, d) - MS_PER_400_YEARS;
+        } else {
+            return new Date(y, m, d).valueOf();
+        }
+    }
+
+    function utcStartOfDate(y, m, d) {
+        // Date.UTC remaps years 0-99 to 1900-1999
+        if (y < 100 && y >= 0) {
+            // preserve leap years using a full 400 year cycle, then reset
+            return Date.UTC(y + 400, m, d) - MS_PER_400_YEARS;
+        } else {
+            return Date.UTC(y, m, d);
+        }
+    }
+
+    function startOf(units) {
+        var time, startOfDate;
+        units = normalizeUnits(units);
+        if (units === undefined || units === 'millisecond' || !this.isValid()) {
+            return this;
+        }
+
+        startOfDate = this._isUTC ? utcStartOfDate : localStartOfDate;
+
+        switch (units) {
+            case 'year':
+                time = startOfDate(this.year(), 0, 1);
+                break;
+            case 'quarter':
+                time = startOfDate(
+                    this.year(),
+                    this.month() - (this.month() % 3),
+                    1
+                );
+                break;
+            case 'month':
+                time = startOfDate(this.year(), this.month(), 1);
+                break;
+            case 'week':
+                time = startOfDate(
+                    this.year(),
+                    this.month(),
+                    this.date() - this.weekday()
+                );
+                break;
+            case 'isoWeek':
+                time = startOfDate(
+                    this.year(),
+                    this.month(),
+                    this.date() - (this.isoWeekday() - 1)
+                );
+                break;
+            case 'day':
+            case 'date':
+                time = startOfDate(this.year(), this.month(), this.date());
+                break;
+            case 'hour':
+                time = this._d.valueOf();
+                time -= mod$1(
+                    time + (this._isUTC ? 0 : this.utcOffset() * MS_PER_MINUTE),
+                    MS_PER_HOUR
+                );
+                break;
+            case 'minute':
+                time = this._d.valueOf();
+                time -= mod$1(time, MS_PER_MINUTE);
+                break;
+            case 'second':
+                time = this._d.valueOf();
+                time -= mod$1(time, MS_PER_SECOND);
+                break;
+        }
+
+        this._d.setTime(time);
+        hooks.updateOffset(this, true);
+        return this;
+    }
+
+    function endOf(units) {
+        var time, startOfDate;
+        units = normalizeUnits(units);
+        if (units === undefined || units === 'millisecond' || !this.isValid()) {
+            return this;
+        }
+
+        startOfDate = this._isUTC ? utcStartOfDate : localStartOfDate;
+
+        switch (units) {
+            case 'year':
+                time = startOfDate(this.year() + 1, 0, 1) - 1;
+                break;
+            case 'quarter':
+                time =
+                    startOfDate(
+                        this.year(),
+                        this.month() - (this.month() % 3) + 3,
+                        1
+                    ) - 1;
+                break;
+            case 'month':
+                time = startOfDate(this.year(), this.month() + 1, 1) - 1;
+                break;
+            case 'week':
+                time =
+                    startOfDate(
+                        this.year(),
+                        this.month(),
+                        this.date() - this.weekday() + 7
+                    ) - 1;
+                break;
+            case 'isoWeek':
+                time =
+                    startOfDate(
+                        this.year(),
+                        this.month(),
+                        this.date() - (this.isoWeekday() - 1) + 7
+                    ) - 1;
+                break;
+            case 'day':
+            case 'date':
+                time = startOfDate(this.year(), this.month(), this.date() + 1) - 1;
+                break;
+            case 'hour':
+                time = this._d.valueOf();
+                time +=
+                    MS_PER_HOUR -
+                    mod$1(
+                        time + (this._isUTC ? 0 : this.utcOffset() * MS_PER_MINUTE),
+                        MS_PER_HOUR
+                    ) -
+                    1;
+                break;
+            case 'minute':
+                time = this._d.valueOf();
+                time += MS_PER_MINUTE - mod$1(time, MS_PER_MINUTE) - 1;
+                break;
+            case 'second':
+                time = this._d.valueOf();
+                time += MS_PER_SECOND - mod$1(time, MS_PER_SECOND) - 1;
+                break;
+        }
+
+        this._d.setTime(time);
+        hooks.updateOffset(this, true);
+        return this;
+    }
+
+    function valueOf() {
+        return this._d.valueOf() - (this._offset || 0) * 60000;
+    }
+
+    function unix() {
+        return Math.floor(this.valueOf() / 1000);
+    }
+
+    function toDate() {
+        return new Date(this.valueOf());
+    }
+
+    function toArray() {
+        var m = this;
+        return [
+            m.year(),
+            m.month(),
+            m.date(),
+            m.hour(),
+            m.minute(),
+            m.second(),
+            m.millisecond(),
+        ];
+    }
+
+    function toObject() {
+        var m = this;
+        return {
+            years: m.year(),
+            months: m.month(),
+            date: m.date(),
+            hours: m.hours(),
+            minutes: m.minutes(),
+            seconds: m.seconds(),
+            milliseconds: m.milliseconds(),
+        };
+    }
+
+    function toJSON() {
+        // new Date(NaN).toJSON() === null
+        return this.isValid() ? this.toISOString() : null;
+    }
+
+    function isValid$2() {
+        return isValid(this);
+    }
+
+    function parsingFlags() {
+        return extend({}, getParsingFlags(this));
+    }
+
+    function invalidAt() {
+        return getParsingFlags(this).overflow;
+    }
+
+    function creationData() {
+        return {
+            input: this._i,
+            format: this._f,
+            locale: this._locale,
+            isUTC: this._isUTC,
+            strict: this._strict,
+        };
+    }
+
+    addFormatToken('N', 0, 0, 'eraAbbr');
+    addFormatToken('NN', 0, 0, 'eraAbbr');
+    addFormatToken('NNN', 0, 0, 'eraAbbr');
+    addFormatToken('NNNN', 0, 0, 'eraName');
+    addFormatToken('NNNNN', 0, 0, 'eraNarrow');
+
+    addFormatToken('y', ['y', 1], 'yo', 'eraYear');
+    addFormatToken('y', ['yy', 2], 0, 'eraYear');
+    addFormatToken('y', ['yyy', 3], 0, 'eraYear');
+    addFormatToken('y', ['yyyy', 4], 0, 'eraYear');
+
+    addRegexToken('N', matchEraAbbr);
+    addRegexToken('NN', matchEraAbbr);
+    addRegexToken('NNN', matchEraAbbr);
+    addRegexToken('NNNN', matchEraName);
+    addRegexToken('NNNNN', matchEraNarrow);
+
+    addParseToken(['N', 'NN', 'NNN', 'NNNN', 'NNNNN'], function (
+        input,
+        array,
+        config,
+        token
+    ) {
+        var era = config._locale.erasParse(input, token, config._strict);
+        if (era) {
+            getParsingFlags(config).era = era;
+        } else {
+            getParsingFlags(config).invalidEra = input;
+        }
+    });
+
+    addRegexToken('y', matchUnsigned);
+    addRegexToken('yy', matchUnsigned);
+    addRegexToken('yyy', matchUnsigned);
+    addRegexToken('yyyy', matchUnsigned);
+    addRegexToken('yo', matchEraYearOrdinal);
+
+    addParseToken(['y', 'yy', 'yyy', 'yyyy'], YEAR);
+    addParseToken(['yo'], function (input, array, config, token) {
+        var match;
+        if (config._locale._eraYearOrdinalRegex) {
+            match = input.match(config._locale._eraYearOrdinalRegex);
+        }
+
+        if (config._locale.eraYearOrdinalParse) {
+            array[YEAR] = config._locale.eraYearOrdinalParse(input, match);
+        } else {
+            array[YEAR] = parseInt(input, 10);
+        }
+    });
+
+    function localeEras(m, format) {
+        var i,
+            l,
+            date,
+            eras = this._eras || getLocale('en')._eras;
+        for (i = 0, l = eras.length; i < l; ++i) {
+            switch (typeof eras[i].since) {
+                case 'string':
+                    // truncate time
+                    date = hooks(eras[i].since).startOf('day');
+                    eras[i].since = date.valueOf();
+                    break;
+            }
+
+            switch (typeof eras[i].until) {
+                case 'undefined':
+                    eras[i].until = +Infinity;
+                    break;
+                case 'string':
+                    // truncate time
+                    date = hooks(eras[i].until).startOf('day').valueOf();
+                    eras[i].until = date.valueOf();
+                    break;
+            }
+        }
+        return eras;
+    }
+
+    function localeErasParse(eraName, format, strict) {
+        var i,
+            l,
+            eras = this.eras(),
+            name,
+            abbr,
+            narrow;
+        eraName = eraName.toUpperCase();
+
+        for (i = 0, l = eras.length; i < l; ++i) {
+            name = eras[i].name.toUpperCase();
+            abbr = eras[i].abbr.toUpperCase();
+            narrow = eras[i].narrow.toUpperCase();
+
+            if (strict) {
+                switch (format) {
+                    case 'N':
+                    case 'NN':
+                    case 'NNN':
+                        if (abbr === eraName) {
+                            return eras[i];
+                        }
+                        break;
+
+                    case 'NNNN':
+                        if (name === eraName) {
+                            return eras[i];
+                        }
+                        break;
+
+                    case 'NNNNN':
+                        if (narrow === eraName) {
+                            return eras[i];
+                        }
+                        break;
+                }
+            } else if ([name, abbr, narrow].indexOf(eraName) >= 0) {
+                return eras[i];
+            }
+        }
+    }
+
+    function localeErasConvertYear(era, year) {
+        var dir = era.since <= era.until ? +1 : -1;
+        if (year === undefined) {
+            return hooks(era.since).year();
+        } else {
+            return hooks(era.since).year() + (year - era.offset) * dir;
+        }
+    }
+
+    function getEraName() {
+        var i,
+            l,
+            val,
+            eras = this.localeData().eras();
+        for (i = 0, l = eras.length; i < l; ++i) {
+            // truncate time
+            val = this.clone().startOf('day').valueOf();
+
+            if (eras[i].since <= val && val <= eras[i].until) {
+                return eras[i].name;
+            }
+            if (eras[i].until <= val && val <= eras[i].since) {
+                return eras[i].name;
+            }
+        }
+
+        return '';
+    }
+
+    function getEraNarrow() {
+        var i,
+            l,
+            val,
+            eras = this.localeData().eras();
+        for (i = 0, l = eras.length; i < l; ++i) {
+            // truncate time
+            val = this.clone().startOf('day').valueOf();
+
+            if (eras[i].since <= val && val <= eras[i].until) {
+                return eras[i].narrow;
+            }
+            if (eras[i].until <= val && val <= eras[i].since) {
+                return eras[i].narrow;
+            }
+        }
+
+        return '';
+    }
+
+    function getEraAbbr() {
+        var i,
+            l,
+            val,
+            eras = this.localeData().eras();
+        for (i = 0, l = eras.length; i < l; ++i) {
+            // truncate time
+            val = this.clone().startOf('day').valueOf();
+
+            if (eras[i].since <= val && val <= eras[i].until) {
+                return eras[i].abbr;
+            }
+            if (eras[i].until <= val && val <= eras[i].since) {
+                return eras[i].abbr;
+            }
+        }
+
+        return '';
+    }
+
+    function getEraYear() {
+        var i,
+            l,
+            dir,
+            val,
+            eras = this.localeData().eras();
+        for (i = 0, l = eras.length; i < l; ++i) {
+            dir = eras[i].since <= eras[i].until ? +1 : -1;
+
+            // truncate time
+            val = this.clone().startOf('day').valueOf();
+
+            if (
+                (eras[i].since <= val && val <= eras[i].until) ||
+                (eras[i].until <= val && val <= eras[i].since)
+            ) {
+                return (
+                    (this.year() - hooks(eras[i].since).year()) * dir +
+                    eras[i].offset
+                );
+            }
+        }
+
+        return this.year();
+    }
+
+    function erasNameRegex(isStrict) {
+        if (!hasOwnProp(this, '_erasNameRegex')) {
+            computeErasParse.call(this);
+        }
+        return isStrict ? this._erasNameRegex : this._erasRegex;
+    }
+
+    function erasAbbrRegex(isStrict) {
+        if (!hasOwnProp(this, '_erasAbbrRegex')) {
+            computeErasParse.call(this);
+        }
+        return isStrict ? this._erasAbbrRegex : this._erasRegex;
+    }
+
+    function erasNarrowRegex(isStrict) {
+        if (!hasOwnProp(this, '_erasNarrowRegex')) {
+            computeErasParse.call(this);
+        }
+        return isStrict ? this._erasNarrowRegex : this._erasRegex;
+    }
+
+    function matchEraAbbr(isStrict, locale) {
+        return locale.erasAbbrRegex(isStrict);
+    }
+
+    function matchEraName(isStrict, locale) {
+        return locale.erasNameRegex(isStrict);
+    }
+
+    function matchEraNarrow(isStrict, locale) {
+        return locale.erasNarrowRegex(isStrict);
+    }
+
+    function matchEraYearOrdinal(isStrict, locale) {
+        return locale._eraYearOrdinalRegex || matchUnsigned;
+    }
+
+    function computeErasParse() {
+        var abbrPieces = [],
+            namePieces = [],
+            narrowPieces = [],
+            mixedPieces = [],
+            i,
+            l,
+            eras = this.eras();
+
+        for (i = 0, l = eras.length; i < l; ++i) {
+            namePieces.push(regexEscape(eras[i].name));
+            abbrPieces.push(regexEscape(eras[i].abbr));
+            narrowPieces.push(regexEscape(eras[i].narrow));
+
+            mixedPieces.push(regexEscape(eras[i].name));
+            mixedPieces.push(regexEscape(eras[i].abbr));
+            mixedPieces.push(regexEscape(eras[i].narrow));
+        }
+
+        this._erasRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i');
+        this._erasNameRegex = new RegExp('^(' + namePieces.join('|') + ')', 'i');
+        this._erasAbbrRegex = new RegExp('^(' + abbrPieces.join('|') + ')', 'i');
+        this._erasNarrowRegex = new RegExp(
+            '^(' + narrowPieces.join('|') + ')',
+            'i'
+        );
+    }
+
+    // FORMATTING
+
+    addFormatToken(0, ['gg', 2], 0, function () {
+        return this.weekYear() % 100;
+    });
+
+    addFormatToken(0, ['GG', 2], 0, function () {
+        return this.isoWeekYear() % 100;
+    });
+
+    function addWeekYearFormatToken(token, getter) {
+        addFormatToken(0, [token, token.length], 0, getter);
+    }
+
+    addWeekYearFormatToken('gggg', 'weekYear');
+    addWeekYearFormatToken('ggggg', 'weekYear');
+    addWeekYearFormatToken('GGGG', 'isoWeekYear');
+    addWeekYearFormatToken('GGGGG', 'isoWeekYear');
+
+    // ALIASES
+
+    addUnitAlias('weekYear', 'gg');
+    addUnitAlias('isoWeekYear', 'GG');
+
+    // PRIORITY
+
+    addUnitPriority('weekYear', 1);
+    addUnitPriority('isoWeekYear', 1);
+
+    // PARSING
+
+    addRegexToken('G', matchSigned);
+    addRegexToken('g', matchSigned);
+    addRegexToken('GG', match1to2, match2);
+    addRegexToken('gg', match1to2, match2);
+    addRegexToken('GGGG', match1to4, match4);
+    addRegexToken('gggg', match1to4, match4);
+    addRegexToken('GGGGG', match1to6, match6);
+    addRegexToken('ggggg', match1to6, match6);
+
+    addWeekParseToken(['gggg', 'ggggg', 'GGGG', 'GGGGG'], function (
+        input,
+        week,
+        config,
+        token
+    ) {
+        week[token.substr(0, 2)] = toInt(input);
+    });
+
+    addWeekParseToken(['gg', 'GG'], function (input, week, config, token) {
+        week[token] = hooks.parseTwoDigitYear(input);
+    });
+
+    // MOMENTS
+
+    function getSetWeekYear(input) {
+        return getSetWeekYearHelper.call(
+            this,
+            input,
+            this.week(),
+            this.weekday(),
+            this.localeData()._week.dow,
+            this.localeData()._week.doy
+        );
+    }
+
+    function getSetISOWeekYear(input) {
+        return getSetWeekYearHelper.call(
+            this,
+            input,
+            this.isoWeek(),
+            this.isoWeekday(),
+            1,
+            4
+        );
+    }
+
+    function getISOWeeksInYear() {
+        return weeksInYear(this.year(), 1, 4);
+    }
+
+    function getISOWeeksInISOWeekYear() {
+        return weeksInYear(this.isoWeekYear(), 1, 4);
+    }
+
+    function getWeeksInYear() {
+        var weekInfo = this.localeData()._week;
+        return weeksInYear(this.year(), weekInfo.dow, weekInfo.doy);
+    }
+
+    function getWeeksInWeekYear() {
+        var weekInfo = this.localeData()._week;
+        return weeksInYear(this.weekYear(), weekInfo.dow, weekInfo.doy);
+    }
+
+    function getSetWeekYearHelper(input, week, weekday, dow, doy) {
+        var weeksTarget;
+        if (input == null) {
+            return weekOfYear(this, dow, doy).year;
+        } else {
+            weeksTarget = weeksInYear(input, dow, doy);
+            if (week > weeksTarget) {
+                week = weeksTarget;
+            }
+            return setWeekAll.call(this, input, week, weekday, dow, doy);
+        }
+    }
+
+    function setWeekAll(weekYear, week, weekday, dow, doy) {
+        var dayOfYearData = dayOfYearFromWeeks(weekYear, week, weekday, dow, doy),
+            date = createUTCDate(dayOfYearData.year, 0, dayOfYearData.dayOfYear);
+
+        this.year(date.getUTCFullYear());
+        this.month(date.getUTCMonth());
+        this.date(date.getUTCDate());
+        return this;
+    }
+
+    // FORMATTING
+
+    addFormatToken('Q', 0, 'Qo', 'quarter');
+
+    // ALIASES
+
+    addUnitAlias('quarter', 'Q');
+
+    // PRIORITY
+
+    addUnitPriority('quarter', 7);
+
+    // PARSING
+
+    addRegexToken('Q', match1);
+    addParseToken('Q', function (input, array) {
+        array[MONTH] = (toInt(input) - 1) * 3;
+    });
+
+    // MOMENTS
+
+    function getSetQuarter(input) {
+        return input == null
+            ? Math.ceil((this.month() + 1) / 3)
+            : this.month((input - 1) * 3 + (this.month() % 3));
+    }
+
+    // FORMATTING
+
+    addFormatToken('D', ['DD', 2], 'Do', 'date');
+
+    // ALIASES
+
+    addUnitAlias('date', 'D');
+
+    // PRIORITY
+    addUnitPriority('date', 9);
+
+    // PARSING
+
+    addRegexToken('D', match1to2);
+    addRegexToken('DD', match1to2, match2);
+    addRegexToken('Do', function (isStrict, locale) {
+        // TODO: Remove "ordinalParse" fallback in next major release.
+        return isStrict
+            ? locale._dayOfMonthOrdinalParse || locale._ordinalParse
+            : locale._dayOfMonthOrdinalParseLenient;
+    });
+
+    addParseToken(['D', 'DD'], DATE);
+    addParseToken('Do', function (input, array) {
+        array[DATE] = toInt(input.match(match1to2)[0]);
+    });
+
+    // MOMENTS
+
+    var getSetDayOfMonth = makeGetSet('Date', true);
+
+    // FORMATTING
+
+    addFormatToken('DDD', ['DDDD', 3], 'DDDo', 'dayOfYear');
+
+    // ALIASES
+
+    addUnitAlias('dayOfYear', 'DDD');
+
+    // PRIORITY
+    addUnitPriority('dayOfYear', 4);
+
+    // PARSING
+
+    addRegexToken('DDD', match1to3);
+    addRegexToken('DDDD', match3);
+    addParseToken(['DDD', 'DDDD'], function (input, array, config) {
+        config._dayOfYear = toInt(input);
+    });
+
+    // HELPERS
+
+    // MOMENTS
+
+    function getSetDayOfYear(input) {
+        var dayOfYear =
+            Math.round(
+                (this.clone().startOf('day') - this.clone().startOf('year')) / 864e5
+            ) + 1;
+        return input == null ? dayOfYear : this.add(input - dayOfYear, 'd');
+    }
+
+    // FORMATTING
+
+    addFormatToken('m', ['mm', 2], 0, 'minute');
+
+    // ALIASES
+
+    addUnitAlias('minute', 'm');
+
+    // PRIORITY
+
+    addUnitPriority('minute', 14);
+
+    // PARSING
+
+    addRegexToken('m', match1to2);
+    addRegexToken('mm', match1to2, match2);
+    addParseToken(['m', 'mm'], MINUTE);
+
+    // MOMENTS
+
+    var getSetMinute = makeGetSet('Minutes', false);
+
+    // FORMATTING
+
+    addFormatToken('s', ['ss', 2], 0, 'second');
+
+    // ALIASES
+
+    addUnitAlias('second', 's');
+
+    // PRIORITY
+
+    addUnitPriority('second', 15);
+
+    // PARSING
+
+    addRegexToken('s', match1to2);
+    addRegexToken('ss', match1to2, match2);
+    addParseToken(['s', 'ss'], SECOND);
+
+    // MOMENTS
+
+    var getSetSecond = makeGetSet('Seconds', false);
+
+    // FORMATTING
+
+    addFormatToken('S', 0, 0, function () {
+        return ~~(this.millisecond() / 100);
+    });
+
+    addFormatToken(0, ['SS', 2], 0, function () {
+        return ~~(this.millisecond() / 10);
+    });
+
+    addFormatToken(0, ['SSS', 3], 0, 'millisecond');
+    addFormatToken(0, ['SSSS', 4], 0, function () {
+        return this.millisecond() * 10;
+    });
+    addFormatToken(0, ['SSSSS', 5], 0, function () {
+        return this.millisecond() * 100;
+    });
+    addFormatToken(0, ['SSSSSS', 6], 0, function () {
+        return this.millisecond() * 1000;
+    });
+    addFormatToken(0, ['SSSSSSS', 7], 0, function () {
+        return this.millisecond() * 10000;
+    });
+    addFormatToken(0, ['SSSSSSSS', 8], 0, function () {
+        return this.millisecond() * 100000;
+    });
+    addFormatToken(0, ['SSSSSSSSS', 9], 0, function () {
+        return this.millisecond() * 1000000;
+    });
+
+    // ALIASES
+
+    addUnitAlias('millisecond', 'ms');
+
+    // PRIORITY
+
+    addUnitPriority('millisecond', 16);
+
+    // PARSING
+
+    addRegexToken('S', match1to3, match1);
+    addRegexToken('SS', match1to3, match2);
+    addRegexToken('SSS', match1to3, match3);
+
+    var token, getSetMillisecond;
+    for (token = 'SSSS'; token.length <= 9; token += 'S') {
+        addRegexToken(token, matchUnsigned);
+    }
+
+    function parseMs(input, array) {
+        array[MILLISECOND] = toInt(('0.' + input) * 1000);
+    }
+
+    for (token = 'S'; token.length <= 9; token += 'S') {
+        addParseToken(token, parseMs);
+    }
+
+    getSetMillisecond = makeGetSet('Milliseconds', false);
+
+    // FORMATTING
+
+    addFormatToken('z', 0, 0, 'zoneAbbr');
+    addFormatToken('zz', 0, 0, 'zoneName');
+
+    // MOMENTS
+
+    function getZoneAbbr() {
+        return this._isUTC ? 'UTC' : '';
+    }
+
+    function getZoneName() {
+        return this._isUTC ? 'Coordinated Universal Time' : '';
+    }
+
+    var proto = Moment.prototype;
+
+    proto.add = add;
+    proto.calendar = calendar$1;
+    proto.clone = clone;
+    proto.diff = diff;
+    proto.endOf = endOf;
+    proto.format = format;
+    proto.from = from;
+    proto.fromNow = fromNow;
+    proto.to = to;
+    proto.toNow = toNow;
+    proto.get = stringGet;
+    proto.invalidAt = invalidAt;
+    proto.isAfter = isAfter;
+    proto.isBefore = isBefore;
+    proto.isBetween = isBetween;
+    proto.isSame = isSame;
+    proto.isSameOrAfter = isSameOrAfter;
+    proto.isSameOrBefore = isSameOrBefore;
+    proto.isValid = isValid$2;
+    proto.lang = lang;
+    proto.locale = locale;
+    proto.localeData = localeData;
+    proto.max = prototypeMax;
+    proto.min = prototypeMin;
+    proto.parsingFlags = parsingFlags;
+    proto.set = stringSet;
+    proto.startOf = startOf;
+    proto.subtract = subtract;
+    proto.toArray = toArray;
+    proto.toObject = toObject;
+    proto.toDate = toDate;
+    proto.toISOString = toISOString;
+    proto.inspect = inspect;
+    if (typeof Symbol !== 'undefined' && Symbol.for != null) {
+        proto[Symbol.for('nodejs.util.inspect.custom')] = function () {
+            return 'Moment<' + this.format() + '>';
+        };
+    }
+    proto.toJSON = toJSON;
+    proto.toString = toString;
+    proto.unix = unix;
+    proto.valueOf = valueOf;
+    proto.creationData = creationData;
+    proto.eraName = getEraName;
+    proto.eraNarrow = getEraNarrow;
+    proto.eraAbbr = getEraAbbr;
+    proto.eraYear = getEraYear;
+    proto.year = getSetYear;
+    proto.isLeapYear = getIsLeapYear;
+    proto.weekYear = getSetWeekYear;
+    proto.isoWeekYear = getSetISOWeekYear;
+    proto.quarter = proto.quarters = getSetQuarter;
+    proto.month = getSetMonth;
+    proto.daysInMonth = getDaysInMonth;
+    proto.week = proto.weeks = getSetWeek;
+    proto.isoWeek = proto.isoWeeks = getSetISOWeek;
+    proto.weeksInYear = getWeeksInYear;
+    proto.weeksInWeekYear = getWeeksInWeekYear;
+    proto.isoWeeksInYear = getISOWeeksInYear;
+    proto.isoWeeksInISOWeekYear = getISOWeeksInISOWeekYear;
+    proto.date = getSetDayOfMonth;
+    proto.day = proto.days = getSetDayOfWeek;
+    proto.weekday = getSetLocaleDayOfWeek;
+    proto.isoWeekday = getSetISODayOfWeek;
+    proto.dayOfYear = getSetDayOfYear;
+    proto.hour = proto.hours = getSetHour;
+    proto.minute = proto.minutes = getSetMinute;
+    proto.second = proto.seconds = getSetSecond;
+    proto.millisecond = proto.milliseconds = getSetMillisecond;
+    proto.utcOffset = getSetOffset;
+    proto.utc = setOffsetToUTC;
+    proto.local = setOffsetToLocal;
+    proto.parseZone = setOffsetToParsedOffset;
+    proto.hasAlignedHourOffset = hasAlignedHourOffset;
+    proto.isDST = isDaylightSavingTime;
+    proto.isLocal = isLocal;
+    proto.isUtcOffset = isUtcOffset;
+    proto.isUtc = isUtc;
+    proto.isUTC = isUtc;
+    proto.zoneAbbr = getZoneAbbr;
+    proto.zoneName = getZoneName;
+    proto.dates = deprecate(
+        'dates accessor is deprecated. Use date instead.',
+        getSetDayOfMonth
+    );
+    proto.months = deprecate(
+        'months accessor is deprecated. Use month instead',
+        getSetMonth
+    );
+    proto.years = deprecate(
+        'years accessor is deprecated. Use year instead',
+        getSetYear
+    );
+    proto.zone = deprecate(
+        'moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/',
+        getSetZone
+    );
+    proto.isDSTShifted = deprecate(
+        'isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information',
+        isDaylightSavingTimeShifted
+    );
+
+    function createUnix(input) {
+        return createLocal(input * 1000);
+    }
+
+    function createInZone() {
+        return createLocal.apply(null, arguments).parseZone();
+    }
+
+    function preParsePostFormat(string) {
+        return string;
+    }
+
+    var proto$1 = Locale.prototype;
+
+    proto$1.calendar = calendar;
+    proto$1.longDateFormat = longDateFormat;
+    proto$1.invalidDate = invalidDate;
+    proto$1.ordinal = ordinal;
+    proto$1.preparse = preParsePostFormat;
+    proto$1.postformat = preParsePostFormat;
+    proto$1.relativeTime = relativeTime;
+    proto$1.pastFuture = pastFuture;
+    proto$1.set = set;
+    proto$1.eras = localeEras;
+    proto$1.erasParse = localeErasParse;
+    proto$1.erasConvertYear = localeErasConvertYear;
+    proto$1.erasAbbrRegex = erasAbbrRegex;
+    proto$1.erasNameRegex = erasNameRegex;
+    proto$1.erasNarrowRegex = erasNarrowRegex;
+
+    proto$1.months = localeMonths;
+    proto$1.monthsShort = localeMonthsShort;
+    proto$1.monthsParse = localeMonthsParse;
+    proto$1.monthsRegex = monthsRegex;
+    proto$1.monthsShortRegex = monthsShortRegex;
+    proto$1.week = localeWeek;
+    proto$1.firstDayOfYear = localeFirstDayOfYear;
+    proto$1.firstDayOfWeek = localeFirstDayOfWeek;
+
+    proto$1.weekdays = localeWeekdays;
+    proto$1.weekdaysMin = localeWeekdaysMin;
+    proto$1.weekdaysShort = localeWeekdaysShort;
+    proto$1.weekdaysParse = localeWeekdaysParse;
+
+    proto$1.weekdaysRegex = weekdaysRegex;
+    proto$1.weekdaysShortRegex = weekdaysShortRegex;
+    proto$1.weekdaysMinRegex = weekdaysMinRegex;
+
+    proto$1.isPM = localeIsPM;
+    proto$1.meridiem = localeMeridiem;
+
+    function get$1(format, index, field, setter) {
+        var locale = getLocale(),
+            utc = createUTC().set(setter, index);
+        return locale[field](utc, format);
+    }
+
+    function listMonthsImpl(format, index, field) {
+        if (isNumber(format)) {
+            index = format;
+            format = undefined;
+        }
+
+        format = format || '';
+
+        if (index != null) {
+            return get$1(format, index, field, 'month');
+        }
+
+        var i,
+            out = [];
+        for (i = 0; i < 12; i++) {
+            out[i] = get$1(format, i, field, 'month');
+        }
+        return out;
+    }
+
+    // ()
+    // (5)
+    // (fmt, 5)
+    // (fmt)
+    // (true)
+    // (true, 5)
+    // (true, fmt, 5)
+    // (true, fmt)
+    function listWeekdaysImpl(localeSorted, format, index, field) {
+        if (typeof localeSorted === 'boolean') {
+            if (isNumber(format)) {
+                index = format;
+                format = undefined;
+            }
+
+            format = format || '';
+        } else {
+            format = localeSorted;
+            index = format;
+            localeSorted = false;
+
+            if (isNumber(format)) {
+                index = format;
+                format = undefined;
+            }
+
+            format = format || '';
+        }
+
+        var locale = getLocale(),
+            shift = localeSorted ? locale._week.dow : 0,
+            i,
+            out = [];
+
+        if (index != null) {
+            return get$1(format, (index + shift) % 7, field, 'day');
+        }
+
+        for (i = 0; i < 7; i++) {
+            out[i] = get$1(format, (i + shift) % 7, field, 'day');
+        }
+        return out;
+    }
+
+    function listMonths(format, index) {
+        return listMonthsImpl(format, index, 'months');
+    }
+
+    function listMonthsShort(format, index) {
+        return listMonthsImpl(format, index, 'monthsShort');
+    }
+
+    function listWeekdays(localeSorted, format, index) {
+        return listWeekdaysImpl(localeSorted, format, index, 'weekdays');
+    }
+
+    function listWeekdaysShort(localeSorted, format, index) {
+        return listWeekdaysImpl(localeSorted, format, index, 'weekdaysShort');
+    }
+
+    function listWeekdaysMin(localeSorted, format, index) {
+        return listWeekdaysImpl(localeSorted, format, index, 'weekdaysMin');
+    }
+
+    getSetGlobalLocale('en', {
+        eras: [
+            {
+                since: '0001-01-01',
+                until: +Infinity,
+                offset: 1,
+                name: 'Anno Domini',
+                narrow: 'AD',
+                abbr: 'AD',
+            },
+            {
+                since: '0000-12-31',
+                until: -Infinity,
+                offset: 1,
+                name: 'Before Christ',
+                narrow: 'BC',
+                abbr: 'BC',
+            },
+        ],
+        dayOfMonthOrdinalParse: /\d{1,2}(th|st|nd|rd)/,
+        ordinal: function (number) {
+            var b = number % 10,
+                output =
+                    toInt((number % 100) / 10) === 1
+                        ? 'th'
+                        : b === 1
+                        ? 'st'
+                        : b === 2
+                        ? 'nd'
+                        : b === 3
+                        ? 'rd'
+                        : 'th';
+            return number + output;
+        },
+    });
+
+    // Side effect imports
+
+    hooks.lang = deprecate(
+        'moment.lang is deprecated. Use moment.locale instead.',
+        getSetGlobalLocale
+    );
+    hooks.langData = deprecate(
+        'moment.langData is deprecated. Use moment.localeData instead.',
+        getLocale
+    );
+
+    var mathAbs = Math.abs;
+
+    function abs() {
+        var data = this._data;
+
+        this._milliseconds = mathAbs(this._milliseconds);
+        this._days = mathAbs(this._days);
+        this._months = mathAbs(this._months);
+
+        data.milliseconds = mathAbs(data.milliseconds);
+        data.seconds = mathAbs(data.seconds);
+        data.minutes = mathAbs(data.minutes);
+        data.hours = mathAbs(data.hours);
+        data.months = mathAbs(data.months);
+        data.years = mathAbs(data.years);
+
+        return this;
+    }
+
+    function addSubtract$1(duration, input, value, direction) {
+        var other = createDuration(input, value);
+
+        duration._milliseconds += direction * other._milliseconds;
+        duration._days += direction * other._days;
+        duration._months += direction * other._months;
+
+        return duration._bubble();
+    }
+
+    // supports only 2.0-style add(1, 's') or add(duration)
+    function add$1(input, value) {
+        return addSubtract$1(this, input, value, 1);
+    }
+
+    // supports only 2.0-style subtract(1, 's') or subtract(duration)
+    function subtract$1(input, value) {
+        return addSubtract$1(this, input, value, -1);
+    }
+
+    function absCeil(number) {
+        if (number < 0) {
+            return Math.floor(number);
+        } else {
+            return Math.ceil(number);
+        }
+    }
+
+    function bubble() {
+        var milliseconds = this._milliseconds,
+            days = this._days,
+            months = this._months,
+            data = this._data,
+            seconds,
+            minutes,
+            hours,
+            years,
+            monthsFromDays;
+
+        // if we have a mix of positive and negative values, bubble down first
+        // check: https://github.com/moment/moment/issues/2166
+        if (
+            !(
+                (milliseconds >= 0 && days >= 0 && months >= 0) ||
+                (milliseconds <= 0 && days <= 0 && months <= 0)
+            )
+        ) {
+            milliseconds += absCeil(monthsToDays(months) + days) * 864e5;
+            days = 0;
+            months = 0;
+        }
+
+        // The following code bubbles up values, see the tests for
+        // examples of what that means.
+        data.milliseconds = milliseconds % 1000;
+
+        seconds = absFloor(milliseconds / 1000);
+        data.seconds = seconds % 60;
+
+        minutes = absFloor(seconds / 60);
+        data.minutes = minutes % 60;
+
+        hours = absFloor(minutes / 60);
+        data.hours = hours % 24;
+
+        days += absFloor(hours / 24);
+
+        // convert days to months
+        monthsFromDays = absFloor(daysToMonths(days));
+        months += monthsFromDays;
+        days -= absCeil(monthsToDays(monthsFromDays));
+
+        // 12 months -> 1 year
+        years = absFloor(months / 12);
+        months %= 12;
+
+        data.days = days;
+        data.months = months;
+        data.years = years;
+
+        return this;
+    }
+
+    function daysToMonths(days) {
+        // 400 years have 146097 days (taking into account leap year rules)
+        // 400 years have 12 months === 4800
+        return (days * 4800) / 146097;
+    }
+
+    function monthsToDays(months) {
+        // the reverse of daysToMonths
+        return (months * 146097) / 4800;
+    }
+
+    function as(units) {
+        if (!this.isValid()) {
+            return NaN;
+        }
+        var days,
+            months,
+            milliseconds = this._milliseconds;
+
+        units = normalizeUnits(units);
+
+        if (units === 'month' || units === 'quarter' || units === 'year') {
+            days = this._days + milliseconds / 864e5;
+            months = this._months + daysToMonths(days);
+            switch (units) {
+                case 'month':
+                    return months;
+                case 'quarter':
+                    return months / 3;
+                case 'year':
+                    return months / 12;
+            }
+        } else {
+            // handle milliseconds separately because of floating point math errors (issue #1867)
+            days = this._days + Math.round(monthsToDays(this._months));
+            switch (units) {
+                case 'week':
+                    return days / 7 + milliseconds / 6048e5;
+                case 'day':
+                    return days + milliseconds / 864e5;
+                case 'hour':
+                    return days * 24 + milliseconds / 36e5;
+                case 'minute':
+                    return days * 1440 + milliseconds / 6e4;
+                case 'second':
+                    return days * 86400 + milliseconds / 1000;
+                // Math.floor prevents floating point math errors here
+                case 'millisecond':
+                    return Math.floor(days * 864e5) + milliseconds;
+                default:
+                    throw new Error('Unknown unit ' + units);
+            }
+        }
+    }
+
+    // TODO: Use this.as('ms')?
+    function valueOf$1() {
+        if (!this.isValid()) {
+            return NaN;
+        }
+        return (
+            this._milliseconds +
+            this._days * 864e5 +
+            (this._months % 12) * 2592e6 +
+            toInt(this._months / 12) * 31536e6
+        );
+    }
+
+    function makeAs(alias) {
+        return function () {
+            return this.as(alias);
+        };
+    }
+
+    var asMilliseconds = makeAs('ms'),
+        asSeconds = makeAs('s'),
+        asMinutes = makeAs('m'),
+        asHours = makeAs('h'),
+        asDays = makeAs('d'),
+        asWeeks = makeAs('w'),
+        asMonths = makeAs('M'),
+        asQuarters = makeAs('Q'),
+        asYears = makeAs('y');
+
+    function clone$1() {
+        return createDuration(this);
+    }
+
+    function get$2(units) {
+        units = normalizeUnits(units);
+        return this.isValid() ? this[units + 's']() : NaN;
+    }
+
+    function makeGetter(name) {
+        return function () {
+            return this.isValid() ? this._data[name] : NaN;
+        };
+    }
+
+    var milliseconds = makeGetter('milliseconds'),
+        seconds = makeGetter('seconds'),
+        minutes = makeGetter('minutes'),
+        hours = makeGetter('hours'),
+        days = makeGetter('days'),
+        months = makeGetter('months'),
+        years = makeGetter('years');
+
+    function weeks() {
+        return absFloor(this.days() / 7);
+    }
+
+    var round = Math.round,
+        thresholds = {
+            ss: 44, // a few seconds to seconds
+            s: 45, // seconds to minute
+            m: 45, // minutes to hour
+            h: 22, // hours to day
+            d: 26, // days to month/week
+            w: null, // weeks to month
+            M: 11, // months to year
+        };
+
+    // helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize
+    function substituteTimeAgo(string, number, withoutSuffix, isFuture, locale) {
+        return locale.relativeTime(number || 1, !!withoutSuffix, string, isFuture);
+    }
+
+    function relativeTime$1(posNegDuration, withoutSuffix, thresholds, locale) {
+        var duration = createDuration(posNegDuration).abs(),
+            seconds = round(duration.as('s')),
+            minutes = round(duration.as('m')),
+            hours = round(duration.as('h')),
+            days = round(duration.as('d')),
+            months = round(duration.as('M')),
+            weeks = round(duration.as('w')),
+            years = round(duration.as('y')),
+            a =
+                (seconds <= thresholds.ss && ['s', seconds]) ||
+                (seconds < thresholds.s && ['ss', seconds]) ||
+                (minutes <= 1 && ['m']) ||
+                (minutes < thresholds.m && ['mm', minutes]) ||
+                (hours <= 1 && ['h']) ||
+                (hours < thresholds.h && ['hh', hours]) ||
+                (days <= 1 && ['d']) ||
+                (days < thresholds.d && ['dd', days]);
+
+        if (thresholds.w != null) {
+            a =
+                a ||
+                (weeks <= 1 && ['w']) ||
+                (weeks < thresholds.w && ['ww', weeks]);
+        }
+        a = a ||
+            (months <= 1 && ['M']) ||
+            (months < thresholds.M && ['MM', months]) ||
+            (years <= 1 && ['y']) || ['yy', years];
+
+        a[2] = withoutSuffix;
+        a[3] = +posNegDuration > 0;
+        a[4] = locale;
+        return substituteTimeAgo.apply(null, a);
+    }
+
+    // This function allows you to set the rounding function for relative time strings
+    function getSetRelativeTimeRounding(roundingFunction) {
+        if (roundingFunction === undefined) {
+            return round;
+        }
+        if (typeof roundingFunction === 'function') {
+            round = roundingFunction;
+            return true;
+        }
+        return false;
+    }
+
+    // This function allows you to set a threshold for relative time strings
+    function getSetRelativeTimeThreshold(threshold, limit) {
+        if (thresholds[threshold] === undefined) {
+            return false;
+        }
+        if (limit === undefined) {
+            return thresholds[threshold];
+        }
+        thresholds[threshold] = limit;
+        if (threshold === 's') {
+            thresholds.ss = limit - 1;
+        }
+        return true;
+    }
+
+    function humanize(argWithSuffix, argThresholds) {
+        if (!this.isValid()) {
+            return this.localeData().invalidDate();
+        }
+
+        var withSuffix = false,
+            th = thresholds,
+            locale,
+            output;
+
+        if (typeof argWithSuffix === 'object') {
+            argThresholds = argWithSuffix;
+            argWithSuffix = false;
+        }
+        if (typeof argWithSuffix === 'boolean') {
+            withSuffix = argWithSuffix;
+        }
+        if (typeof argThresholds === 'object') {
+            th = Object.assign({}, thresholds, argThresholds);
+            if (argThresholds.s != null && argThresholds.ss == null) {
+                th.ss = argThresholds.s - 1;
+            }
+        }
+
+        locale = this.localeData();
+        output = relativeTime$1(this, !withSuffix, th, locale);
+
+        if (withSuffix) {
+            output = locale.pastFuture(+this, output);
+        }
+
+        return locale.postformat(output);
+    }
+
+    var abs$1 = Math.abs;
+
+    function sign(x) {
+        return (x > 0) - (x < 0) || +x;
+    }
+
+    function toISOString$1() {
+        // for ISO strings we do not use the normal bubbling rules:
+        //  * milliseconds bubble up until they become hours
+        //  * days do not bubble at all
+        //  * months bubble up until they become years
+        // This is because there is no context-free conversion between hours and days
+        // (think of clock changes)
+        // and also not between days and months (28-31 days per month)
+        if (!this.isValid()) {
+            return this.localeData().invalidDate();
+        }
+
+        var seconds = abs$1(this._milliseconds) / 1000,
+            days = abs$1(this._days),
+            months = abs$1(this._months),
+            minutes,
+            hours,
+            years,
+            s,
+            total = this.asSeconds(),
+            totalSign,
+            ymSign,
+            daysSign,
+            hmsSign;
+
+        if (!total) {
+            // this is the same as C#'s (Noda) and python (isodate)...
+            // but not other JS (goog.date)
+            return 'P0D';
+        }
+
+        // 3600 seconds -> 60 minutes -> 1 hour
+        minutes = absFloor(seconds / 60);
+        hours = absFloor(minutes / 60);
+        seconds %= 60;
+        minutes %= 60;
+
+        // 12 months -> 1 year
+        years = absFloor(months / 12);
+        months %= 12;
+
+        // inspired by https://github.com/dordille/moment-isoduration/blob/master/moment.isoduration.js
+        s = seconds ? seconds.toFixed(3).replace(/\.?0+$/, '') : '';
+
+        totalSign = total < 0 ? '-' : '';
+        ymSign = sign(this._months) !== sign(total) ? '-' : '';
+        daysSign = sign(this._days) !== sign(total) ? '-' : '';
+        hmsSign = sign(this._milliseconds) !== sign(total) ? '-' : '';
+
+        return (
+            totalSign +
+            'P' +
+            (years ? ymSign + years + 'Y' : '') +
+            (months ? ymSign + months + 'M' : '') +
+            (days ? daysSign + days + 'D' : '') +
+            (hours || minutes || seconds ? 'T' : '') +
+            (hours ? hmsSign + hours + 'H' : '') +
+            (minutes ? hmsSign + minutes + 'M' : '') +
+            (seconds ? hmsSign + s + 'S' : '')
+        );
+    }
+
+    var proto$2 = Duration.prototype;
+
+    proto$2.isValid = isValid$1;
+    proto$2.abs = abs;
+    proto$2.add = add$1;
+    proto$2.subtract = subtract$1;
+    proto$2.as = as;
+    proto$2.asMilliseconds = asMilliseconds;
+    proto$2.asSeconds = asSeconds;
+    proto$2.asMinutes = asMinutes;
+    proto$2.asHours = asHours;
+    proto$2.asDays = asDays;
+    proto$2.asWeeks = asWeeks;
+    proto$2.asMonths = asMonths;
+    proto$2.asQuarters = asQuarters;
+    proto$2.asYears = asYears;
+    proto$2.valueOf = valueOf$1;
+    proto$2._bubble = bubble;
+    proto$2.clone = clone$1;
+    proto$2.get = get$2;
+    proto$2.milliseconds = milliseconds;
+    proto$2.seconds = seconds;
+    proto$2.minutes = minutes;
+    proto$2.hours = hours;
+    proto$2.days = days;
+    proto$2.weeks = weeks;
+    proto$2.months = months;
+    proto$2.years = years;
+    proto$2.humanize = humanize;
+    proto$2.toISOString = toISOString$1;
+    proto$2.toString = toISOString$1;
+    proto$2.toJSON = toISOString$1;
+    proto$2.locale = locale;
+    proto$2.localeData = localeData;
+
+    proto$2.toIsoString = deprecate(
+        'toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)',
+        toISOString$1
+    );
+    proto$2.lang = lang;
+
+    // FORMATTING
+
+    addFormatToken('X', 0, 0, 'unix');
+    addFormatToken('x', 0, 0, 'valueOf');
+
+    // PARSING
+
+    addRegexToken('x', matchSigned);
+    addRegexToken('X', matchTimestamp);
+    addParseToken('X', function (input, array, config) {
+        config._d = new Date(parseFloat(input) * 1000);
+    });
+    addParseToken('x', function (input, array, config) {
+        config._d = new Date(toInt(input));
+    });
+
+    //! moment.js
+
+    hooks.version = '2.29.1';
+
+    setHookCallback(createLocal);
+
+    hooks.fn = proto;
+    hooks.min = min;
+    hooks.max = max;
+    hooks.now = now;
+    hooks.utc = createUTC;
+    hooks.unix = createUnix;
+    hooks.months = listMonths;
+    hooks.isDate = isDate;
+    hooks.locale = getSetGlobalLocale;
+    hooks.invalid = createInvalid;
+    hooks.duration = createDuration;
+    hooks.isMoment = isMoment;
+    hooks.weekdays = listWeekdays;
+    hooks.parseZone = createInZone;
+    hooks.localeData = getLocale;
+    hooks.isDuration = isDuration;
+    hooks.monthsShort = listMonthsShort;
+    hooks.weekdaysMin = listWeekdaysMin;
+    hooks.defineLocale = defineLocale;
+    hooks.updateLocale = updateLocale;
+    hooks.locales = listLocales;
+    hooks.weekdaysShort = listWeekdaysShort;
+    hooks.normalizeUnits = normalizeUnits;
+    hooks.relativeTimeRounding = getSetRelativeTimeRounding;
+    hooks.relativeTimeThreshold = getSetRelativeTimeThreshold;
+    hooks.calendarFormat = getCalendarFormat;
+    hooks.prototype = proto;
+
+    // currently HTML5 input type only supports 24-hour formats
+    hooks.HTML5_FMT = {
+        DATETIME_LOCAL: 'YYYY-MM-DDTHH:mm', // <input type="datetime-local" />
+        DATETIME_LOCAL_SECONDS: 'YYYY-MM-DDTHH:mm:ss', // <input type="datetime-local" step="1" />
+        DATETIME_LOCAL_MS: 'YYYY-MM-DDTHH:mm:ss.SSS', // <input type="datetime-local" step="0.001" />
+        DATE: 'YYYY-MM-DD', // <input type="date" />
+        TIME: 'HH:mm', // <input type="time" />
+        TIME_SECONDS: 'HH:mm:ss', // <input type="time" step="1" />
+        TIME_MS: 'HH:mm:ss.SSS', // <input type="time" step="0.001" />
+        WEEK: 'GGGG-[W]WW', // <input type="week" />
+        MONTH: 'YYYY-MM', // <input type="month" />
+    };
+
+    return hooks;
+
+})));

+ 638 - 0
core/unpkg/qs.js

xqd
@@ -0,0 +1,638 @@
+(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Qs = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
+'use strict';
+
+var replace = String.prototype.replace;
+var percentTwenties = /%20/g;
+
+module.exports = {
+    'default': 'RFC3986',
+    formatters: {
+        RFC1738: function (value) {
+            return replace.call(value, percentTwenties, '+');
+        },
+        RFC3986: function (value) {
+            return value;
+        }
+    },
+    RFC1738: 'RFC1738',
+    RFC3986: 'RFC3986'
+};
+
+},{}],2:[function(require,module,exports){
+'use strict';
+
+var stringify = require('./stringify');
+var parse = require('./parse');
+var formats = require('./formats');
+
+module.exports = {
+    formats: formats,
+    parse: parse,
+    stringify: stringify
+};
+
+},{"./formats":1,"./parse":3,"./stringify":4}],3:[function(require,module,exports){
+'use strict';
+
+var utils = require('./utils');
+
+var has = Object.prototype.hasOwnProperty;
+
+var defaults = {
+    allowDots: false,
+    allowPrototypes: false,
+    arrayLimit: 20,
+    decoder: utils.decode,
+    delimiter: '&',
+    depth: 5,
+    parameterLimit: 1000,
+    plainObjects: false,
+    strictNullHandling: false
+};
+
+var parseValues = function parseQueryStringValues(str, options) {
+    var obj = {};
+    var cleanStr = options.ignoreQueryPrefix ? str.replace(/^\?/, '') : str;
+    var limit = options.parameterLimit === Infinity ? undefined : options.parameterLimit;
+    var parts = cleanStr.split(options.delimiter, limit);
+
+    for (var i = 0; i < parts.length; ++i) {
+        var part = parts[i];
+
+        var bracketEqualsPos = part.indexOf(']=');
+        var pos = bracketEqualsPos === -1 ? part.indexOf('=') : bracketEqualsPos + 1;
+
+        var key, val;
+        if (pos === -1) {
+            key = options.decoder(part, defaults.decoder);
+            val = options.strictNullHandling ? null : '';
+        } else {
+            key = options.decoder(part.slice(0, pos), defaults.decoder);
+            val = options.decoder(part.slice(pos + 1), defaults.decoder);
+        }
+        if (has.call(obj, key)) {
+            obj[key] = [].concat(obj[key]).concat(val);
+        } else {
+            obj[key] = val;
+        }
+    }
+
+    return obj;
+};
+
+var parseObject = function (chain, val, options) {
+    var leaf = val;
+
+    for (var i = chain.length - 1; i >= 0; --i) {
+        var obj;
+        var root = chain[i];
+
+        if (root === '[]') {
+            obj = [];
+            obj = obj.concat(leaf);
+        } else {
+            obj = options.plainObjects ? Object.create(null) : {};
+            var cleanRoot = root.charAt(0) === '[' && root.charAt(root.length - 1) === ']' ? root.slice(1, -1) : root;
+            var index = parseInt(cleanRoot, 10);
+            if (
+                !isNaN(index)
+                && root !== cleanRoot
+                && String(index) === cleanRoot
+                && index >= 0
+                && (options.parseArrays && index <= options.arrayLimit)
+            ) {
+                obj = [];
+                obj[index] = leaf;
+            } else {
+                obj[cleanRoot] = leaf;
+            }
+        }
+
+        leaf = obj;
+    }
+
+    return leaf;
+};
+
+var parseKeys = function parseQueryStringKeys(givenKey, val, options) {
+    if (!givenKey) {
+        return;
+    }
+
+    // Transform dot notation to bracket notation
+    var key = options.allowDots ? givenKey.replace(/\.([^.[]+)/g, '[$1]') : givenKey;
+
+    // The regex chunks
+
+    var brackets = /(\[[^[\]]*])/;
+    var child = /(\[[^[\]]*])/g;
+
+    // Get the parent
+
+    var segment = brackets.exec(key);
+    var parent = segment ? key.slice(0, segment.index) : key;
+
+    // Stash the parent if it exists
+
+    var keys = [];
+    if (parent) {
+        // If we aren't using plain objects, optionally prefix keys
+        // that would overwrite object prototype properties
+        if (!options.plainObjects && has.call(Object.prototype, parent)) {
+            if (!options.allowPrototypes) {
+                return;
+            }
+        }
+
+        keys.push(parent);
+    }
+
+    // Loop through children appending to the array until we hit depth
+
+    var i = 0;
+    while ((segment = child.exec(key)) !== null && i < options.depth) {
+        i += 1;
+        if (!options.plainObjects && has.call(Object.prototype, segment[1].slice(1, -1))) {
+            if (!options.allowPrototypes) {
+                return;
+            }
+        }
+        keys.push(segment[1]);
+    }
+
+    // If there's a remainder, just add whatever is left
+
+    if (segment) {
+        keys.push('[' + key.slice(segment.index) + ']');
+    }
+
+    return parseObject(keys, val, options);
+};
+
+module.exports = function (str, opts) {
+    var options = opts ? utils.assign({}, opts) : {};
+
+    if (options.decoder !== null && options.decoder !== undefined && typeof options.decoder !== 'function') {
+        throw new TypeError('Decoder has to be a function.');
+    }
+
+    options.ignoreQueryPrefix = options.ignoreQueryPrefix === true;
+    options.delimiter = typeof options.delimiter === 'string' || utils.isRegExp(options.delimiter) ? options.delimiter : defaults.delimiter;
+    options.depth = typeof options.depth === 'number' ? options.depth : defaults.depth;
+    options.arrayLimit = typeof options.arrayLimit === 'number' ? options.arrayLimit : defaults.arrayLimit;
+    options.parseArrays = options.parseArrays !== false;
+    options.decoder = typeof options.decoder === 'function' ? options.decoder : defaults.decoder;
+    options.allowDots = typeof options.allowDots === 'boolean' ? options.allowDots : defaults.allowDots;
+    options.plainObjects = typeof options.plainObjects === 'boolean' ? options.plainObjects : defaults.plainObjects;
+    options.allowPrototypes = typeof options.allowPrototypes === 'boolean' ? options.allowPrototypes : defaults.allowPrototypes;
+    options.parameterLimit = typeof options.parameterLimit === 'number' ? options.parameterLimit : defaults.parameterLimit;
+    options.strictNullHandling = typeof options.strictNullHandling === 'boolean' ? options.strictNullHandling : defaults.strictNullHandling;
+
+    if (str === '' || str === null || typeof str === 'undefined') {
+        return options.plainObjects ? Object.create(null) : {};
+    }
+
+    var tempObj = typeof str === 'string' ? parseValues(str, options) : str;
+    var obj = options.plainObjects ? Object.create(null) : {};
+
+    // Iterate over the keys and setup the new object
+
+    var keys = Object.keys(tempObj);
+    for (var i = 0; i < keys.length; ++i) {
+        var key = keys[i];
+        var newObj = parseKeys(key, tempObj[key], options);
+        obj = utils.merge(obj, newObj, options);
+    }
+
+    return utils.compact(obj);
+};
+
+},{"./utils":5}],4:[function(require,module,exports){
+'use strict';
+
+var utils = require('./utils');
+var formats = require('./formats');
+
+var arrayPrefixGenerators = {
+    brackets: function brackets(prefix) { // eslint-disable-line func-name-matching
+        return prefix + '[]';
+    },
+    indices: function indices(prefix, key) { // eslint-disable-line func-name-matching
+        return prefix + '[' + key + ']';
+    },
+    repeat: function repeat(prefix) { // eslint-disable-line func-name-matching
+        return prefix;
+    }
+};
+
+var toISO = Date.prototype.toISOString;
+
+var defaults = {
+    delimiter: '&',
+    encode: true,
+    encoder: utils.encode,
+    encodeValuesOnly: false,
+    serializeDate: function serializeDate(date) { // eslint-disable-line func-name-matching
+        return toISO.call(date);
+    },
+    skipNulls: false,
+    strictNullHandling: false
+};
+
+var stringify = function stringify( // eslint-disable-line func-name-matching
+    object,
+    prefix,
+    generateArrayPrefix,
+    strictNullHandling,
+    skipNulls,
+    encoder,
+    filter,
+    sort,
+    allowDots,
+    serializeDate,
+    formatter,
+    encodeValuesOnly
+) {
+    var obj = object;
+    if (typeof filter === 'function') {
+        obj = filter(prefix, obj);
+    } else if (obj instanceof Date) {
+        obj = serializeDate(obj);
+    } else if (obj === null) {
+        if (strictNullHandling) {
+            return encoder && !encodeValuesOnly ? encoder(prefix, defaults.encoder) : prefix;
+        }
+
+        obj = '';
+    }
+
+    if (typeof obj === 'string' || typeof obj === 'number' || typeof obj === 'boolean' || utils.isBuffer(obj)) {
+        if (encoder) {
+            var keyValue = encodeValuesOnly ? prefix : encoder(prefix, defaults.encoder);
+            return [formatter(keyValue) + '=' + formatter(encoder(obj, defaults.encoder))];
+        }
+        return [formatter(prefix) + '=' + formatter(String(obj))];
+    }
+
+    var values = [];
+
+    if (typeof obj === 'undefined') {
+        return values;
+    }
+
+    var objKeys;
+    if (Array.isArray(filter)) {
+        objKeys = filter;
+    } else {
+        var keys = Object.keys(obj);
+        objKeys = sort ? keys.sort(sort) : keys;
+    }
+
+    for (var i = 0; i < objKeys.length; ++i) {
+        var key = objKeys[i];
+
+        if (skipNulls && obj[key] === null) {
+            continue;
+        }
+
+        if (Array.isArray(obj)) {
+            values = values.concat(stringify(
+                obj[key],
+                generateArrayPrefix(prefix, key),
+                generateArrayPrefix,
+                strictNullHandling,
+                skipNulls,
+                encoder,
+                filter,
+                sort,
+                allowDots,
+                serializeDate,
+                formatter,
+                encodeValuesOnly
+            ));
+        } else {
+            values = values.concat(stringify(
+                obj[key],
+                prefix + (allowDots ? '.' + key : '[' + key + ']'),
+                generateArrayPrefix,
+                strictNullHandling,
+                skipNulls,
+                encoder,
+                filter,
+                sort,
+                allowDots,
+                serializeDate,
+                formatter,
+                encodeValuesOnly
+            ));
+        }
+    }
+
+    return values;
+};
+
+module.exports = function (object, opts) {
+    var obj = object;
+    var options = opts ? utils.assign({}, opts) : {};
+
+    if (options.encoder !== null && options.encoder !== undefined && typeof options.encoder !== 'function') {
+        throw new TypeError('Encoder has to be a function.');
+    }
+
+    var delimiter = typeof options.delimiter === 'undefined' ? defaults.delimiter : options.delimiter;
+    var strictNullHandling = typeof options.strictNullHandling === 'boolean' ? options.strictNullHandling : defaults.strictNullHandling;
+    var skipNulls = typeof options.skipNulls === 'boolean' ? options.skipNulls : defaults.skipNulls;
+    var encode = typeof options.encode === 'boolean' ? options.encode : defaults.encode;
+    var encoder = typeof options.encoder === 'function' ? options.encoder : defaults.encoder;
+    var sort = typeof options.sort === 'function' ? options.sort : null;
+    var allowDots = typeof options.allowDots === 'undefined' ? false : options.allowDots;
+    var serializeDate = typeof options.serializeDate === 'function' ? options.serializeDate : defaults.serializeDate;
+    var encodeValuesOnly = typeof options.encodeValuesOnly === 'boolean' ? options.encodeValuesOnly : defaults.encodeValuesOnly;
+    if (typeof options.format === 'undefined') {
+        options.format = formats['default'];
+    } else if (!Object.prototype.hasOwnProperty.call(formats.formatters, options.format)) {
+        throw new TypeError('Unknown format option provided.');
+    }
+    var formatter = formats.formatters[options.format];
+    var objKeys;
+    var filter;
+
+    if (typeof options.filter === 'function') {
+        filter = options.filter;
+        obj = filter('', obj);
+    } else if (Array.isArray(options.filter)) {
+        filter = options.filter;
+        objKeys = filter;
+    }
+
+    var keys = [];
+
+    if (typeof obj !== 'object' || obj === null) {
+        return '';
+    }
+
+    var arrayFormat;
+    if (options.arrayFormat in arrayPrefixGenerators) {
+        arrayFormat = options.arrayFormat;
+    } else if ('indices' in options) {
+        arrayFormat = options.indices ? 'indices' : 'repeat';
+    } else {
+        arrayFormat = 'indices';
+    }
+
+    var generateArrayPrefix = arrayPrefixGenerators[arrayFormat];
+
+    if (!objKeys) {
+        objKeys = Object.keys(obj);
+    }
+
+    if (sort) {
+        objKeys.sort(sort);
+    }
+
+    for (var i = 0; i < objKeys.length; ++i) {
+        var key = objKeys[i];
+
+        if (skipNulls && obj[key] === null) {
+            continue;
+        }
+
+        keys = keys.concat(stringify(
+            obj[key],
+            key,
+            generateArrayPrefix,
+            strictNullHandling,
+            skipNulls,
+            encode ? encoder : null,
+            filter,
+            sort,
+            allowDots,
+            serializeDate,
+            formatter,
+            encodeValuesOnly
+        ));
+    }
+
+    var joined = keys.join(delimiter);
+    var prefix = options.addQueryPrefix === true ? '?' : '';
+
+    return joined.length > 0 ? prefix + joined : '';
+};
+
+},{"./formats":1,"./utils":5}],5:[function(require,module,exports){
+'use strict';
+
+var has = Object.prototype.hasOwnProperty;
+
+var hexTable = (function () {
+    var array = [];
+    for (var i = 0; i < 256; ++i) {
+        array.push('%' + ((i < 16 ? '0' : '') + i.toString(16)).toUpperCase());
+    }
+
+    return array;
+}());
+
+var compactQueue = function compactQueue(queue) {
+    var obj;
+
+    while (queue.length) {
+        var item = queue.pop();
+        obj = item.obj[item.prop];
+
+        if (Array.isArray(obj)) {
+            var compacted = [];
+
+            for (var j = 0; j < obj.length; ++j) {
+                if (typeof obj[j] !== 'undefined') {
+                    compacted.push(obj[j]);
+                }
+            }
+
+            item.obj[item.prop] = compacted;
+        }
+    }
+
+    return obj;
+};
+
+var arrayToObject = function arrayToObject(source, options) {
+    var obj = options && options.plainObjects ? Object.create(null) : {};
+    for (var i = 0; i < source.length; ++i) {
+        if (typeof source[i] !== 'undefined') {
+            obj[i] = source[i];
+        }
+    }
+
+    return obj;
+};
+
+var merge = function merge(target, source, options) {
+    if (!source) {
+        return target;
+    }
+
+    if (typeof source !== 'object') {
+        if (Array.isArray(target)) {
+            target.push(source);
+        } else if (typeof target === 'object') {
+            if (options.plainObjects || options.allowPrototypes || !has.call(Object.prototype, source)) {
+                target[source] = true;
+            }
+        } else {
+            return [target, source];
+        }
+
+        return target;
+    }
+
+    if (typeof target !== 'object') {
+        return [target].concat(source);
+    }
+
+    var mergeTarget = target;
+    if (Array.isArray(target) && !Array.isArray(source)) {
+        mergeTarget = arrayToObject(target, options);
+    }
+
+    if (Array.isArray(target) && Array.isArray(source)) {
+        source.forEach(function (item, i) {
+            if (has.call(target, i)) {
+                if (target[i] && typeof target[i] === 'object') {
+                    target[i] = merge(target[i], item, options);
+                } else {
+                    target.push(item);
+                }
+            } else {
+                target[i] = item;
+            }
+        });
+        return target;
+    }
+
+    return Object.keys(source).reduce(function (acc, key) {
+        var value = source[key];
+
+        if (has.call(acc, key)) {
+            acc[key] = merge(acc[key], value, options);
+        } else {
+            acc[key] = value;
+        }
+        return acc;
+    }, mergeTarget);
+};
+
+var assign = function assignSingleSource(target, source) {
+    return Object.keys(source).reduce(function (acc, key) {
+        acc[key] = source[key];
+        return acc;
+    }, target);
+};
+
+var decode = function (str) {
+    try {
+        return decodeURIComponent(str.replace(/\+/g, ' '));
+    } catch (e) {
+        return str;
+    }
+};
+
+var encode = function encode(str) {
+    // This code was originally written by Brian White (mscdex) for the io.js core querystring library.
+    // It has been adapted here for stricter adherence to RFC 3986
+    if (str.length === 0) {
+        return str;
+    }
+
+    var string = typeof str === 'string' ? str : String(str);
+
+    var out = '';
+    for (var i = 0; i < string.length; ++i) {
+        var c = string.charCodeAt(i);
+
+        if (
+            c === 0x2D // -
+            || c === 0x2E // .
+            || c === 0x5F // _
+            || c === 0x7E // ~
+            || (c >= 0x30 && c <= 0x39) // 0-9
+            || (c >= 0x41 && c <= 0x5A) // a-z
+            || (c >= 0x61 && c <= 0x7A) // A-Z
+        ) {
+            out += string.charAt(i);
+            continue;
+        }
+
+        if (c < 0x80) {
+            out = out + hexTable[c];
+            continue;
+        }
+
+        if (c < 0x800) {
+            out = out + (hexTable[0xC0 | (c >> 6)] + hexTable[0x80 | (c & 0x3F)]);
+            continue;
+        }
+
+        if (c < 0xD800 || c >= 0xE000) {
+            out = out + (hexTable[0xE0 | (c >> 12)] + hexTable[0x80 | ((c >> 6) & 0x3F)] + hexTable[0x80 | (c & 0x3F)]);
+            continue;
+        }
+
+        i += 1;
+        c = 0x10000 + (((c & 0x3FF) << 10) | (string.charCodeAt(i) & 0x3FF));
+        out += hexTable[0xF0 | (c >> 18)]
+            + hexTable[0x80 | ((c >> 12) & 0x3F)]
+            + hexTable[0x80 | ((c >> 6) & 0x3F)]
+            + hexTable[0x80 | (c & 0x3F)];
+    }
+
+    return out;
+};
+
+var compact = function compact(value) {
+    var queue = [{ obj: { o: value }, prop: 'o' }];
+    var refs = [];
+
+    for (var i = 0; i < queue.length; ++i) {
+        var item = queue[i];
+        var obj = item.obj[item.prop];
+
+        var keys = Object.keys(obj);
+        for (var j = 0; j < keys.length; ++j) {
+            var key = keys[j];
+            var val = obj[key];
+            if (typeof val === 'object' && val !== null && refs.indexOf(val) === -1) {
+                queue.push({ obj: obj, prop: key });
+                refs.push(val);
+            }
+        }
+    }
+
+    return compactQueue(queue);
+};
+
+var isRegExp = function isRegExp(obj) {
+    return Object.prototype.toString.call(obj) === '[object RegExp]';
+};
+
+var isBuffer = function isBuffer(obj) {
+    if (obj === null || typeof obj === 'undefined') {
+        return false;
+    }
+
+    return !!(obj.constructor && obj.constructor.isBuffer && obj.constructor.isBuffer(obj));
+};
+
+module.exports = {
+    arrayToObject: arrayToObject,
+    assign: assign,
+    compact: compact,
+    decode: decode,
+    encode: encode,
+    isBuffer: isBuffer,
+    isRegExp: isRegExp,
+    merge: merge
+};
+
+},{}]},{},[2])(2)
+});

+ 110 - 0
core/util.js

xqd
@@ -0,0 +1,110 @@
+const ksort = obj => {
+    let keys = Object.keys(obj).sort()
+        , sortedObj = {};
+
+    for (let i in keys) {
+        sortedObj[keys[i]] = obj[keys[i]];
+    }
+
+    return sortedObj;
+}
+const checkNullObj = (obj) => {
+    if (Object.keys(obj).length === 0) {
+        return false
+    }
+    return true
+}
+
+
+const checkArrayNotNullNumber = (array,notNullNumber = 2) => {
+    let num = 0
+    for (const item in array) {
+       if(array[item]) num++;
+    }
+    return num >= notNullNumber;
+}
+
+// 正切
+const tan = angle => {
+    return Math.tan(angle * Math.PI / 180)
+}
+// 余切
+const cot = angle => {
+    return 1 / tan(angle)
+}
+// 余弦
+const cos = angle => {
+    return Math.cos(angle * Math.PI / 180)
+}
+// 余割
+const csc = angle => {
+    return 1 / sin(angle)
+}
+// 正弦
+const sin = angle => {
+    return Math.sin(angle * Math.PI / 180)
+}
+// 正割
+const sec = angle => {
+    return 1 / cos(angle)
+}
+
+// 反正弦
+const asin = value => {
+    return Math.asin(value) * 180 / Math.PI;
+}
+// 反余弦
+const acos = value => {
+    return Math.acos(value) * 180 / Math.PI;
+}
+// 反正切
+const atan = value => {
+    return Math.atan(value) * 180 / Math.PI;
+}
+
+const checkTriangle = (a,b,c) => {
+    a = parseInt(a);
+    b= parseInt(b);
+    c = parseInt(c);
+    return a + b > c && a + c > b && b + c > a;
+}
+
+const round = (number,percent) => {
+    if(!number) return  number
+    let flag = number < 0 ? '-' : '';
+    let arr = number.toString().split(".");
+    if(arr.length > 1 && arr[1].length === 2)  return  number
+    let i = Number('1'.padEnd(percent+1,'0'))
+    number = Math.floor(Math.abs(Number(number)) * i) / i
+    if(arr.length > 1 ){
+        let str = arr[1]
+        if(str.length > 2 && str[2] >= 5){
+            number = Math.ceil(number * i)
+            number += 1;
+            number = number / i;
+        }
+    }
+    if(number.toString().indexOf(".") === -1) number += ".0"
+    arr = number.toString().split(".");
+    arr[1] = arr[1].toString().padEnd(2,'0')
+    number = arr.join(".")
+    return flag+number;
+}
+
+
+export {
+    ksort,
+    checkNullObj,
+    checkArrayNotNullNumber,
+    tan,
+    cot,
+    cos,
+    csc,
+    sin,
+    sec,
+    asin,
+    acos,
+    atan,
+    checkTriangle,
+    round,
+}

+ 54 - 0
main.js

xqd
@@ -0,0 +1,54 @@
+import Vue from 'vue';
+import App from './App';
+
+Vue.config.productionTip = false;
+App.mpType = 'app';
+// 引入全局uView
+import uView from 'uview-ui';
+Vue.use(uView);
+
+// 此处为演示vuex使用,非uView的功能部分
+import store from '@/store';
+
+// 引入uView提供的对vuex的简写法文件
+let vuexStore = require('@/store/$u.mixin.js');
+Vue.mixin(vuexStore);
+
+// 引入uView对小程序分享的mixin封装
+let mpShare = require('uview-ui/libs/mixin/mpShare.js');
+Vue.mixin(mpShare);
+
+import * as util from './core/util.js';
+import moment from './core/unpkg/moment.js';
+import site from './core/site.js';
+import $const from './core/constant.js';
+import jump from './core/jump.js';
+
+Vue.use({
+	install(Vue, options) {
+		// 配置
+		Vue.prototype.$site = site;
+		Vue.prototype.$moment = moment;
+		//
+		Vue.prototype.$util = util;
+		// 常量
+		Vue.prototype.$const = $const;
+		// 路由
+		Vue.prototype.$jump = jump;
+	}
+});
+
+
+const app = new Vue({
+	store,
+	...App
+});
+// http拦截器,将此部分放在new Vue()和app.$mount()之间,才能App.vue中正常使用
+import httpInterceptor from '@/core/http.interceptor.js';
+Vue.use(httpInterceptor, app);
+
+// http接口API抽离,免于写url或者一些固定的参数
+import httpApi from '@/core/http.api.js';
+Vue.use(httpApi, app);
+
+app.$mount();

+ 139 - 0
manifest.json

xqd
@@ -0,0 +1,139 @@
+{
+    "name" : "sange-bridge",
+    "appid" : "__UNI__1E0F131",
+    "description" : "多平台快速开发的UI框架",
+    "versionName" : "1.8.4",
+    "versionCode" : "100",
+    "transformPx" : false,
+    "app-plus" : {
+        // APP-VUE分包,可提APP升启动速度,2.7.12开始支持,兼容微信小程序分包方案,默认关闭
+        "optimization" : {
+            "subPackages" : true
+        },
+        "safearea" : {
+            "bottom" : {
+                "offset" : "none"
+            }
+        },
+        "splashscreen" : {
+            "alwaysShowBeforeRender" : true,
+            "waiting" : true,
+            "autoclose" : true,
+            "delay" : 0
+        },
+        "usingComponents" : true,
+        "nvueCompiler" : "uni-app",
+        "compilerVersion" : 3,
+        "modules" : {},
+        "distribute" : {
+            "android" : {
+                "permissions" : [
+                    "<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
+                    "<uses-permission android:name=\"android.permission.READ_CONTACTS\"/>",
+                    "<uses-permission android:name=\"android.permission.VIBRATE\"/>",
+                    "<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
+                    "<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
+                    "<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
+                    "<uses-permission android:name=\"android.permission.WRITE_CONTACTS\"/>",
+                    "<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.CAMERA\"/>",
+                    "<uses-permission android:name=\"android.permission.RECORD_AUDIO\"/>",
+                    "<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
+                    "<uses-permission android:name=\"android.permission.MODIFY_AUDIO_SETTINGS\"/>",
+                    "<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
+                    "<uses-permission android:name=\"android.permission.CALL_PHONE\"/>",
+                    "<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
+                    "<uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\"/>",
+                    "<uses-feature android:name=\"android.hardware.camera\"/>",
+                    "<uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\"/>",
+                    "<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
+                ],
+                "abiFilters" : [ "armeabi-v7a", "arm64-v8a" ]
+            },
+            "ios" : {},
+            "sdkConfigs" : {
+                "ad" : {}
+            },
+            "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" : ""
+                    }
+                }
+            }
+        }
+    },
+    "quickapp" : {},
+    "mp-weixin" : {
+        "appid" : "wx8f7e81fa26e70da8",
+        "setting" : {
+            "urlCheck" : true,
+            "es6" : false,
+            "minified" : true,
+            "postcss" : true
+        },
+        "optimization" : {
+            "subPackages" : true
+        },
+        "usingComponents" : true
+    },
+    "mp-alipay" : {
+        "usingComponents" : true,
+        "component2" : true
+    },
+    "mp-qq" : {
+        "optimization" : {
+            "subPackages" : true
+        },
+        "appid" : ""
+    },
+    "mp-baidu" : {
+        "usingComponents" : true,
+        "appid" : ""
+    },
+    "mp-toutiao" : {
+        "usingComponents" : true,
+        "appid" : ""
+    },
+    "h5" : {
+        "template" : "template.h5.html",
+        "router" : {
+            "mode" : "hash",
+            "base" : ""
+        },
+        "optimization" : {
+            "treeShaking" : {
+                "enable" : false
+            }
+        },
+        "title" : "uView UI"
+    }
+}

+ 13 - 0
package-lock.json

xqd
@@ -0,0 +1,13 @@
+{
+  "name": "uView",
+  "version": "1.0.0",
+  "lockfileVersion": 1,
+  "requires": true,
+  "dependencies": {
+    "vue-i18n": {
+      "version": "8.20.0",
+      "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-8.20.0.tgz",
+      "integrity": "sha512-ZiAOoeR4d/JtKpbjipx3I80ey7cYG1ki5gQ7HwzWm4YFio9brA15BEYHjalEoBaEfzF5OBEZP+Y2MvAaWnyXXg=="
+    }
+  }
+}

+ 23 - 0
package.json

xqd
@@ -0,0 +1,23 @@
+{
+  "name": "uView",
+  "version": "1.0.0",
+  "description": "<p align=\"center\">\r     <img alt=\"logo\" src=\"https://uviewui.com/common/logo.png\" width=\"120\" height=\"120\" style=\"margin-bottom: 10px;\">\r </p>\r <h3 align=\"center\" style=\"margin: 30px 0 30px;font-weight: bold;font-size:40px;\">uView</h3>\r <h3 align=\"center\">多平台快速开发的UI框架</h3>",
+  "main": "main.js",
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1"
+  },
+  "repository": {
+    "type": "git",
+    "url": "git+https://github.com/YanxinNet/uView.git"
+  },
+  "keywords": [],
+  "author": "",
+  "license": "ISC",
+  "bugs": {
+    "url": "https://github.com/YanxinNet/uView/issues"
+  },
+  "homepage": "https://github.com/YanxinNet/uView#readme",
+  "dependencies": {
+    "vue-i18n": "^8.20.0"
+  }
+}

+ 270 - 0
pages.json

xqd
@@ -0,0 +1,270 @@
+{
+  "easycom": {
+    "^u-(.*)": "@/uview-ui/components/u-$1/u-$1.vue"
+  },
+  "pages": [
+    {
+      "path": "pages/index/index",
+      "style": {
+        "navigationBarTitleText": "桥架计算",
+        "enablePullDownRefresh": false
+      }
+    },
+    {
+      "path": "pages/math/index",
+      "style": {
+        "navigationBarTitleText": "公式表",
+        "enablePullDownRefresh": false
+      }
+    },
+    {
+      "path": "pages/my/index",
+      "style": {
+        "navigationBarTitleText": "我的",
+        "enablePullDownRefresh": false
+      }
+    },
+    {
+      "path": "pages/my/member",
+      "style": {
+        "navigationBarTitleText": "桥架会员",
+        "enablePullDownRefresh": false
+      }
+    },
+    {
+      "path": "pages/my/member-record",
+      "style": {
+        "navigationBarTitleText": "会员购买记录",
+        "enablePullDownRefresh": false
+      }
+    },
+    {
+      "path": "pages/share/index",
+      "style": {
+        "navigationBarTitleText": "分销中心",
+        "enablePullDownRefresh": false
+      }
+    },
+    {
+      "path": "pages/share/list",
+      "style": {
+        "navigationBarTitleText": "推广列表",
+        "enablePullDownRefresh": false
+      }
+    },
+    {
+      "path": "pages/price/index",
+      "style": {
+        "navigationBarTitleText": "佣金",
+        "enablePullDownRefresh": false
+      }
+    },
+    {
+      "path": "pages/price/apply",
+      "style": {
+        "navigationBarTitleText": "提现",
+        "enablePullDownRefresh": false
+      }
+    },
+    {
+      "path": "pages/withdraw/index",
+      "style": {
+        "navigationBarTitleText": "佣金提现明细",
+        "enablePullDownRefresh": false
+      }
+    },
+    {
+      "path": "pages/income/index",
+      "style": {
+        "navigationBarTitleText": "收益列表",
+        "enablePullDownRefresh": false
+      }
+    }
+  ],
+  "subPackages": [
+    {
+      "root": "pages/formula",
+      "pages": [
+        {
+          "path": "normal",
+          "style": {
+            "navigationBarTitleText": "万能公式",
+            "enablePullDownRefresh": false
+          }
+        },
+        {
+          "path": "slice",
+          "style": {
+            "navigationBarTitleText": "切口计算",
+            "enablePullDownRefresh": false
+          }
+        },
+        {
+          "path": "angle",
+          "style": {
+            "navigationBarTitleText": "角度计算",
+            "enablePullDownRefresh": false
+          }
+        },
+        {
+          "path": "anytriangle",
+          "style": {
+            "navigationBarTitleText": "任意三角形计算",
+            "enablePullDownRefresh": false
+          }
+        },
+        {
+          "path": "anyangle",
+          "style": {
+            "navigationBarTitleText": "任意角度计算",
+            "enablePullDownRefresh": false
+          }
+        },
+        {
+          "path": "rightAngleHorizontal",
+          "style": {
+            "navigationBarTitleText": "直角弯计算(水平)",
+            "enablePullDownRefresh": false
+          }
+        },
+        {
+          "path": "rightAngleVertical",
+          "style": {
+            "navigationBarTitleText": "直角弯计算(垂直)",
+            "enablePullDownRefresh": false
+          }
+        },
+        {
+          "path": "rightAngle45",
+          "style": {
+            "navigationBarTitleText": "直角弯计算(2*45°)",
+            "enablePullDownRefresh": false
+          }
+        },
+        {
+          "path": "rightAngle30",
+          "style": {
+            "navigationBarTitleText": " 直角弯计算(3*30°)",
+            "enablePullDownRefresh": false
+          }
+        },
+        {
+          "path": "rightAngle22",
+          "style": {
+            "navigationBarTitleText": "直角弯计算(4*22.5°)",
+            "enablePullDownRefresh": false
+          }
+        },
+        {
+          "path": "uphill",
+          "style": {
+            "navigationBarTitleText": "坡度计算(上坡)",
+            "enablePullDownRefresh": false
+          }
+        },
+        {
+          "path": "downhill",
+          "style": {
+            "navigationBarTitleText": "坡度计算(下坡)",
+            "enablePullDownRefresh": false
+          }
+        },
+        {
+          "path": "duochengzhijiao",
+          "style": {
+            "navigationBarTitleText": "多层直角弯计算",
+            "enablePullDownRefresh": false
+          }
+        },
+        {
+          "path": "duochengdengwan",
+          "style": {
+            "navigationBarTitleText": "多层等差弯计算",
+            "enablePullDownRefresh": false
+          }
+        },
+        {
+          "path": "guozhuwan",
+          "style": {
+            "navigationBarTitleText": "过柱弯计算",
+            "enablePullDownRefresh": false
+          }
+        },
+        {
+          "path": "baoliangwan",
+          "style": {
+            "navigationBarTitleText": "抱梁弯计算",
+            "enablePullDownRefresh": false
+          }
+        },
+        {
+          "path": "guoqiangwan",
+          "style": {
+            "navigationBarTitleText": "过墙弯计算",
+            "enablePullDownRefresh": false
+          }
+        },
+        {
+          "path": "santongwanHorizontal",
+          "style": {
+            "navigationBarTitleText": "三通弯计算(水平)",
+            "enablePullDownRefresh": false
+          }
+        },
+        {
+          "path": "santongwan",
+          "style": {
+            "navigationBarTitleText": "三通弯计算(垂直)",
+            "enablePullDownRefresh": false
+          }
+        },
+        {
+          "path": "bianjindanbian",
+          "style": {
+            "navigationBarTitleText": "变径计算(单边)",
+            "enablePullDownRefresh": false
+          }
+        },
+        {
+          "path": "bianjinshuangbian",
+          "style": {
+            "navigationBarTitleText": "变径计算(双边)",
+            "enablePullDownRefresh": false
+          }
+        }
+      ]
+    }
+  ],
+  "globalStyle": {
+    "navigationBarTextStyle": "white",
+    "navigationBarTitleText": "三哥桥梁",
+    "navigationBarBackgroundColor": "#097268",
+    "backgroundColor": "#FFFFFF"
+  },
+  "tabBar": {
+    "color": "#A0A0A0",
+    "selectedColor": "#069589",
+    "backgroundColor": "#FFFFFF",
+    "borderStyle": "black",
+    "list": [
+      {
+        "pagePath": "pages/index/index",
+        "iconPath": "static/images/icons/tabs/bridge.png",
+        "selectedIconPath": "static/images/icons/tabs/bridge-HL.png",
+        "text": "桥梁计算"
+      },
+      {
+        "pagePath": "pages/math/index",
+        "iconPath": "static/images/icons/tabs/math.png",
+        "selectedIconPath": "static/images/icons/tabs/math-HL.png",
+        "text": "公示表"
+      },
+      {
+        "pagePath": "pages/my/index",
+        "iconPath": "static/images/icons/tabs/my.png",
+        "selectedIconPath": "static/images/icons/tabs/my-HL.png",
+        "text": "我的"
+      }
+    ]
+  }
+}

+ 136 - 0
pages/formula/angle.vue

xqd
@@ -0,0 +1,136 @@
+<template>
+    <view class="math-container">
+        <view class="header">
+            <u-image width="100%" height="300rpx" :src="mathImgs[name].big" mode="aspectFit" @click="handleBigImage"></u-image>
+        </view>
+        <view class="main dir-top-wrap cross-center">
+            <view class="form">
+                <u-form-item label="高(cm)" label-width="160">
+                    <u-input type="number" v-model="formData.height" placeholder="请输入高"/>
+                </u-form-item>
+                <u-form-item label="底边(cm)" label-width="160">
+                    <u-input type="number" v-model="formData.dibian" placeholder="请输入底边"/>
+                </u-form-item>
+                <u-form-item label="边高(cm)" label-width="160">
+                    <u-input type="number" v-model="formData.biangao" placeholder="选填"/>
+                </u-form-item>
+            </view>
+            <view class="btn-group main-left cross-center">
+                <u-button :ripple="true" @click="handelCalc" :custom-style="{backgroundColor: $u.color['mainBgColor'],color:'#ffffff',width: '260rpx',marginRight:'30rpx'}">计算</u-button>
+                <u-button :ripple="true" @click="handleClear">清空</u-button>
+            </view>
+        </view>
+        <div class="footer">
+            <view class="result dir-top-wrap cross-center" v-if="showResult">
+                <view v-for="item in rules" v-if="item.value && item.show">
+                    <text>{{item.name}}=</text>{{item.value}}{{item.unit}}
+                    <text v-if="item.isHalf">{{$util.round(item.value/2,2)}}{{item.unit}}</text>
+                </view>
+            </view>
+            <view class="title">计算图</view>
+            <view class="calc-img">
+                <text class="height">{{formData.height?parseFloat(formData.height):'**'}}cm</text>
+                <text class="xiebian">{{rules.xiebian.value?rules.xiebian.value:'**'}}{{rules.xiebian.unit}}</text>
+                <text class="angle">{{rules.angle.value?rules.angle.value:'**'}}{{rules.angle.unit}}</text>
+                <text class="dibian">{{formData.dibian?parseFloat(formData.dibian):'**'}}cm</text>
+                <u-image width="100%" height="300rpx" :src="mathImgs[name].calc" mode="aspectFit"></u-image>
+            </view>
+            <view class="title">切割图</view>
+            <u-image width="100%" height="300rpx" :src="mathImgs[name].slice" mode="aspectFit"></u-image>
+
+        </div>
+    </view>
+</template>
+
+<script>
+    import mathImgs from "@/core/math-imgs"
+    // 万能公式
+    export default {
+        data() {
+            return {
+                name: 'angle',
+                mathImgs: mathImgs,
+                formData: {
+                    height: '',
+                    dibian: '',
+                    biangao: '',
+                },
+                // 用来验证 输入
+                rules: {
+                    xiebian:{name:'斜边', value:'',unit:'cm',show: true},
+                    angle: {name:'角度', value:'',unit:'°',show: true},
+                    slice:{name:'切口', value:'',unit:'cm',show: true,isHalf: true},
+                }
+            }
+        },
+        methods: {
+            handleBigImage(){
+                uni.previewImage({
+                    urls: [mathImgs[this.name].big],
+                });
+            },
+            handelCalc(){
+                this.initRules();
+                /*1:斜边²=高²+底边²*/
+                /*2、tan角度°=高÷底边*/
+                if(this.formData.height && this.formData.dibian){
+                    this.rules.xiebian.value = Math.sqrt(Math.pow(this.formData.height,2) + Math.pow(this.formData.dibian,2));
+                    this.rules.angle.value = this.$util.atan(this.formData.height / this.formData.dibian);
+                }
+                /*1、 切口=边高×tan(角度°÷2)×2*/
+                if(this.formData.biangao){
+                    this.rules.slice.value = this.formData.biangao * this.$util.tan(this.rules.angle.value / 2) * 2;
+                }
+                this.roundRules();
+                this.$u.toast("请参考计算示意图")
+            },
+            roundRules(){
+                for (const itemKey in this.rules) {
+                    this.rules[itemKey].value = this.$util.round(this.rules[itemKey].value,2);
+                }
+            },
+            initRules(){
+                for (const itemKey in this.rules) {
+                    this.rules[itemKey].value = "";
+                }
+            },
+            handleClear() {
+                this.initRules();
+                this.formData = {
+                    height: '',
+                    dibian: '',
+                    biangao: '',
+                };
+            }
+        },
+        computed:{
+            showResult(){
+                let validate = [];
+                for (const itemKey in this.rules) {
+                    validate.push(this.rules[itemKey].value);
+                }
+                return this.$util.checkArrayNotNullNumber(validate,1)
+            }
+        }
+    }
+</script>
+
+<style lang="scss" scoped>
+    @import "@/static/css/math.scss";
+    .height{
+        top: 210rpx;
+        left: 410rpx;
+    }
+    .xiebian{
+        left: 320rpx;
+        top: 180rpx;
+    }
+    .angle{
+        left: 320rpx;
+        top: 246rpx;
+    }
+    .dibian{
+        left: 350rpx;
+        top: 280rpx;
+    }
+</style>

+ 173 - 0
pages/formula/anyangle.vue

xqd
@@ -0,0 +1,173 @@
+<template>
+    <view class="math-container">
+        <view class="header">
+            <u-image width="100%" height="300rpx" :src="mathImgs[name].big" mode="aspectFit" @click="handleBigImage"></u-image>
+        </view>
+        <view class="main dir-top-wrap cross-center">
+            <view class="form">
+                <u-form-item label="请选择角度" label-width="180">
+                    <text @click="angleShow = true" class="select">{{angleTxt}}</text>
+                    <u-select v-model="angleShow" :list="angleList" @confirm="handleSelectAngle"></u-select>
+                </u-form-item>
+                <u-form-item label="左斜边(cm)" label-width="180">
+                    <u-input type="number" v-model="formData.leftxiebian" placeholder="请输入左斜边"/>
+                </u-form-item>
+                <u-form-item label="右斜边(cm)" label-width="180">
+                    <u-input type="number" v-model="formData.rightxiebian" placeholder="请输入右斜边"/>
+                </u-form-item>
+                <u-form-item label="底边(cm)" label-width="180">
+                    <u-input type="number" v-model="formData.dibian" placeholder="请输入底边"/>
+                </u-form-item>
+                <u-form-item label="边高(cm)" label-width="180">
+                    <u-input type="number" v-model="formData.biangao" placeholder="选填"/>
+                </u-form-item>
+            </view>
+            <view class="btn-group main-left cross-center">
+                <u-button :ripple="true" @click="handelCalc" :custom-style="{backgroundColor: $u.color['mainBgColor'],color:'#ffffff',width: '260rpx',marginRight:'30rpx'}">计算</u-button>
+                <u-button :ripple="true" @click="handleClear">清空</u-button>
+            </view>
+        </view>
+        <div class="footer">
+            <view class="result dir-top-wrap cross-center" v-if="showResult">
+                <view v-for="item in rules" v-if="item.value && item.show">
+                    <text>{{item.name}}=</text>{{item.value}}{{item.unit}}
+                    <text v-if="item.isHalf">{{$util.round(item.value/2,2)}}{{item.unit}}</text>
+                </view>
+            </view>
+        </div>
+    </view>
+</template>
+
+<script>
+    import mathImgs from "@/core/math-imgs"
+    // 万能公式
+    export default {
+        data() {
+            return {
+                name: 'anyangle',
+                mathImgs: mathImgs,
+                formData: {
+                    leftxiebian: '',
+                    rightxiebian: '',
+                    dibian: '',
+                    biangao: '',
+                    angle: '1'
+                },
+                // 用来验证 输入
+                rules: {
+                    left:{name:'左底角', value:'',unit:'°',show: true},
+                    right: {name:'右底角', value:'',unit:'°',show: true},
+                    top:{name:'顶角', value:'',unit:'°',show: true},
+                    slice:{name:'切口', value:'',unit:'cm',show: true,isHalf:true},
+                },
+                angleShow: false,
+                angleList: [
+                    {value: '1',label: '顶角'},
+                    {value: '2',label: '左底角'},
+                    {value: '3',label: '右底角'},
+                ],
+                angleTxt: "顶角",
+            }
+        },
+        methods: {
+            handleBigImage(){
+                uni.previewImage({
+                    urls: [mathImgs[this.name].big],
+                });
+            },
+            handelCalc(){
+                if(!this.formData.leftxiebian){
+                    this.$u.toast('请输入左斜边');
+                    return
+                }
+                if(!this.formData.rightxiebian){
+                    this.$u.toast('请输入右斜边');
+                    return
+                }
+                if(!this.formData.dibian){
+                    this.$u.toast('请输入底边');
+                    return
+                }
+                this.initRules();
+                /**
+                 * 1、 已知左斜边(a)、右斜边(b)、底边(c),求左底度(A)、右底度(B)、顶角			(C)
+                 * 左底角:cosA=(b²+c²-a²)÷(2bc)		当A>90°时,A=|180°-A|
+                 右底度:cosB=(a²+c²-b²)÷(2ac)		当B>90°时,A=|180°-B|
+                 顶角:cosC =(a²+b²-c²)÷(2ab)		当C>90°时,A=|180°-C|
+                 2、 ①已知顶角:切口=边高×cosec顶角°
+                 ②已知左底角:切口=边高×cosec左底角°
+                 ③已知右底角:切口=边高×cosec右底角°
+                 */
+                let left = 0;
+                let right = 0;
+                let top = 0;
+
+                let a = this.formData.leftxiebian;
+                let b = this.formData.rightxiebian;
+                let c = this.formData.dibian;
+                let slice = 0;
+                let biangao = this.formData.biangao;
+                if(!this.$util.checkTriangle(a, b, c)){
+                    this.$u.toast('两边之后要大于第三边');
+                    return;
+                }
+                left = (Math.pow(a,2) + Math.pow(c,2) - Math.pow(b,2)) / (2 * a * c)
+                right = (Math.pow(b,2) + Math.pow(c,2) - Math.pow(a,2)) / (2 * b * c)
+                this.rules.left.value = this.$util.acos(left)
+                this.rules.right.value = this.$util.acos(right)
+                this.rules.top.value = top = 180 - this.rules.left.value - this.rules.right.value
+
+                if(biangao){
+                    if(this.formData.angle === '1'){ // 顶角
+                        slice = biangao * this.$util.csc(this.rules.top.value)
+                    }else if(this.formData.angle === '2'){ // 左底角
+                        slice = biangao * this.$util.csc(this.rules.left.value)
+                    }else{
+                        slice = biangao * this.$util.csc(this.rules.right.value)
+                    }
+                }
+
+                this.rules.slice.value = slice
+                this.roundRules();
+            },
+            roundRules(){
+                for (const itemKey in this.rules) {
+                    this.rules[itemKey].value = this.$util.round(this.rules[itemKey].value,2);
+                }
+            },
+            initRules(){
+                for (const itemKey in this.rules) {
+                    this.rules[itemKey].value = "";
+                }
+            },
+            handleSelectAngle(e){
+                this.formData.angle = e[0].value;
+                this.angleTxt = e[0].label;
+                this.handelCalc()
+            },
+            handleClear() {
+                this.initRules();
+                this.formData = {
+                    leftxiebian: '',
+                    rightxiebian: '',
+                    dibian: '',
+                    biangao: '',
+                    angle: '2'
+                };
+            }
+        },
+        computed:{
+            showResult(){
+                let validate = [];
+                for (const itemKey in this.rules) {
+                    validate.push(this.rules[itemKey].value);
+                }
+                return this.$util.checkArrayNotNullNumber(validate,1)
+            },
+        }
+    }
+</script>
+
+<style lang="scss" scoped>
+    @import "@/static/css/math.scss";
+</style>

+ 315 - 0
pages/formula/anytriangle.vue

xqd
@@ -0,0 +1,315 @@
+<template>
+    <view class="math-container">
+        <view class="header">
+            <u-image width="100%" height="300rpx" :src="mathImgs[name].big" mode="aspectFit" @click="handleBigImage"></u-image>
+        </view>
+        <view class="main dir-top-wrap cross-center">
+            <view class="form">
+                <u-form-item label="边长a" label-width="160">
+                    <u-input type="number" v-model="formData.lengtha" :placeholder="lengthAInput.placeholder" :disabled="lengthAInput.disabled"/>
+                </u-form-item>
+                <u-form-item label="边长b" label-width="160">
+                    <u-input type="number" v-model="formData.lengthb" :placeholder="lengthBInput.placeholder" :disabled="lengthBInput.disabled"/>
+                </u-form-item>
+                <u-form-item label="边长c" label-width="160">
+                    <u-input type="number" v-model="formData.lengthc" :placeholder="lengthCInput.placeholder" :disabled="lengthCInput.disabled"/>
+                </u-form-item>
+                <u-form-item label="角度A" label-width="160">
+                    <u-input type="number" v-model="formData.anglea" :placeholder="angleAInput.placeholder" :disabled="angleAInput.disabled"/>
+                </u-form-item>
+                <u-form-item label="角度B" label-width="160">
+                    <u-input type="number" v-model="formData.angleb" :placeholder="angleBInput.placeholder" :disabled="angleBInput.disabled"/>
+                </u-form-item>
+                <u-form-item label="角度C" label-width="160">
+                    <u-input type="number" v-model="formData.anglec" :placeholder="angleCInput.placeholder" :disabled="angleCInput.disabled"/>
+                </u-form-item>
+            </view>
+            <view class="btn-group main-left cross-center">
+                <u-button :ripple="true" @click="handelCalc" :custom-style="{backgroundColor: $u.color['mainBgColor'],color:'#ffffff',width: '260rpx',marginRight:'30rpx'}">计算</u-button>
+                <u-button :ripple="true" @click="handleClear">清空</u-button>
+            </view>
+        </view>
+        <div class="footer">
+            <view class="result dir-top-wrap cross-center" v-if="showResult">
+                <view v-for="item in rules" v-if="item.value && item.show">
+                    <text>{{item.name}}=</text>{{item.value}}{{item.unit}}
+                </view>
+            </view>
+        </div>
+    </view>
+</template>
+
+<script>
+    import mathImgs from "@/core/math-imgs"
+    // 万能公式
+    export default {
+        data() {
+            return {
+                name: 'anytriangle',
+                mathImgs: mathImgs,
+                formData: {
+                    lengtha: '',
+                    lengthb: '',
+                    lengthc: '',
+                    anglea: '',
+                    angleb: '',
+                    anglec: '',
+                },
+                // 用来验证 输入
+                rules: {
+                    lengtha: {name:'a', value:'',unit:'cm',show: true},
+                    lengthb:{name:'b', value:'',unit:'cm',show: true},
+                    lengthc: {name:'c', value:'',unit:'cm',show: true},
+                    anglea:{name:'角度A', value:'',unit:'°',show: true},
+                    angleb:{name:'角度B', value:'',unit:'°',show: true},
+                    anglec:{name:'角度C', value:'',unit:'°',show: true},
+                }
+            }
+        },
+        methods: {
+            handleBigImage(){
+                uni.previewImage({
+                    urls: [mathImgs[this.name].big],
+                });
+            },
+            handelCalc(){
+                this.initRules();
+                let validate = [];
+                for (const itemKey in this.formData) {
+                    validate.push(this.formData[itemKey]);
+                }
+                if(!this.$util.checkArrayNotNullNumber(validate,3)){
+                    this.$u.toast('至少输入三个参数');
+                    return;
+                }
+                let lengtha = 0;
+                let lengthb = 0;
+                let lengthc = 0;
+                let anglea = 0;
+                let angleb = 0;
+                let anglec = 0;
+
+                /**1、 已知a、b、c
+                 * cosA=(b²+c²-a²)÷(2bc)
+                 * cosB=(a²+c²-b²)÷(2ac)
+                 * cosC =(a²+b²-c²)÷(2ab)
+                 */
+                lengtha = parseFloat( this.formData.lengtha);
+                lengthb = parseFloat(this.formData.lengthb);
+                lengthc = parseFloat(this.formData.lengthc);
+                anglea = parseFloat(this.formData.anglea);
+                angleb = parseFloat(this.formData.angleb);
+                anglec = parseFloat(this.formData.anglec);
+                if(lengtha && lengthb && lengthc){
+                    if(!this.$util.checkTriangle(lengtha, lengthb, lengthc)){
+                        this.$u.toast('两边之后要大于第三边');
+                        return;
+                    }
+                    anglea = this.$util.acos((Math.pow(lengthb,2) + Math.pow(lengthc,2) - Math.pow(lengtha,2)) / (2 * lengthb * lengthc))
+                    angleb = this.$util.acos((Math.pow(lengtha,2) + Math.pow(lengthc,2) - Math.pow(lengthb,2)) / (2 * lengtha * lengthc))
+                    anglec = this.$util.acos((Math.pow(lengtha,2) + Math.pow(lengthb,2) - Math.pow(lengthc,2)) / (2 * lengtha * lengthb))
+                }
+                /**	2、 已知a、b、A(a、b、B;a、c、A;a、c、C;b、c、B;b、c、C):
+                 * sinB=(b×sinA)÷a
+                 * c:a²=b²+c²-2bccosA
+                 * sinC=(c×sinA)÷a
+                 *
+                 */
+                else if(lengtha && lengthb){
+                    //let c1 = ((2 * b * Math.cos(A)) + Math.sqrt(Math.pow(2 * b * Math.cos(A), 2) - 4 * (Math.pow(b, 2) - Math.pow(a, 2)))) / 2
+                    if(anglea){
+                        angleb = this.$util.asin((lengthb * this.$util.sin(anglea)) / lengtha)
+                        lengthc = ((2 * lengthb * this.$util.cos(anglea)) + Math.sqrt(Math.pow(2 * lengthb * this.$util.cos(anglea), 2) - 4 * (Math.pow(lengthb, 2) - Math.pow(lengtha, 2)))) / 2
+                        if(!this.$util.checkTriangle(lengtha, lengthb, lengthc)){
+                            lengthc = ((2 * lengthb * this.$util.cos(anglea)) - Math.sqrt(Math.pow(2 * lengthb * this.$util.cos(anglea), 2) - 4 * (Math.pow(lengthb, 2) - Math.pow(lengtha, 2)))) / 2
+                        }
+                        anglec = 180 - angleb - anglea
+                    }else if(angleb){
+                        anglea = this.$util.asin((lengtha * this.$util.sin(angleb)) / lengthb)
+                        lengthc = ((2 * lengtha * this.$util.cos(angleb)) + Math.sqrt(Math.pow(2 * lengtha * this.$util.cos(angleb), 2) - 4 * (Math.pow(lengtha, 2) - Math.pow(lengthb, 2)))) / 2
+                        if(!this.$util.checkTriangle(lengtha, lengthb, lengthc)){
+                            lengthc = ((2 * lengtha * this.$util.cos(angleb)) - Math.sqrt(Math.pow(2 * lengtha * this.$util.cos(angleb), 2) - 4 * (Math.pow(lengtha, 2) - Math.pow(lengthb, 2)))) / 2
+                        }
+                        anglec = 180 - angleb - anglea
+                    }else if(anglec){
+                        // sinA=(a×sinC)÷c
+                        // c²=a²+b²-2ab cosC
+                        lengthc = Math.sqrt(Math.pow(lengtha, 2) + Math.pow(lengthb, 2) - 2 * lengtha * lengthb * this.$util.cos(anglec))
+                        anglea = this.$util.asin((lengtha * this.$util.sin(anglec)) / lengthc)
+                        angleb = 180 - anglec - anglea
+                    }
+                }else if(lengtha && lengthc){
+                    if(anglea){
+                        anglec = this.$util.asin((lengthc * this.$util.sin(anglea)) / lengtha)
+                        lengthb = ((2 * lengthc * this.$util.cos(anglea)) + Math.sqrt(Math.pow(2 * lengthc * this.$util.cos(anglea), 2) - 4 * (Math.pow(lengthc, 2) - Math.pow(lengtha, 2)))) / 2
+                        if(!this.$util.checkTriangle(lengtha, lengthb, lengthc)){
+                            lengthb = ((2 * lengthc * this.$util.cos(anglea)) - Math.sqrt(Math.pow(2 * lengthc * this.$util.cos(anglea), 2) - 4 * (Math.pow(lengthc, 2) - Math.pow(lengtha, 2)))) / 2
+                        }
+                        angleb = 180 - anglec - anglea
+                    }else if(angleb){
+                        // sinA=(a×sinB)÷b
+                        // b²=a²+c²-2ac cosC
+                        lengthb = Math.sqrt(Math.pow(lengtha, 2) + Math.pow(lengthc, 2) - 2 * lengtha * lengthc * this.$util.cos(angleb))
+                        anglea = this.$util.asin((lengtha * this.$util.sin(angleb)) / lengthb)
+                        anglec = 180 - anglea - angleb
+                    }else if(anglec){
+                        anglea = this.$util.asin((lengtha * this.$util.sin(anglec)) / lengthc)
+                        lengthb = ((2 * lengthc * this.$util.cos(anglea)) + Math.sqrt(Math.pow(2 * lengthc * this.$util.cos(anglea), 2) - 4 * (Math.pow(lengthc, 2) - Math.pow(lengtha, 2)))) / 2
+                        if(!this.$util.checkTriangle(lengtha, lengthb, lengthc)){
+                            lengthb = ((2 * lengthc * this.$util.cos(anglea)) - Math.sqrt(Math.pow(2 * lengthc * this.$util.cos(anglea), 2) - 4 * (Math.pow(lengthc, 2) - Math.pow(lengtha, 2)))) / 2
+                        }
+                        angleb = 180 - anglec - anglea
+                    }
+                }else if(lengthb && lengthc){
+                    if(anglea){
+                        lengtha = Math.sqrt(Math.pow(lengthb, 2) + Math.pow(lengthc, 2) - 2 * lengthb * lengthc * this.$util.cos(anglea))
+                        angleb = this.$util.asin((lengthb * this.$util.sin(anglea)) / lengtha)
+                        anglec = 180 - anglea - angleb
+                    }else if(angleb){
+                        // sinA=(a×sinC)÷c
+                        anglec = this.$util.asin((lengthc * this.$util.sin(angleb)) / lengthb)
+                        lengtha = ((2 * lengthc * this.$util.cos(angleb)) + Math.sqrt(Math.pow(2 * lengthc * this.$util.cos(angleb), 2) - 4 * (Math.pow(lengthc, 2) - Math.pow(lengthb, 2)))) / 2
+                        if(!this.$util.checkTriangle(lengtha, lengthb, lengthc)){
+                            lengtha = ((2 * lengthc * this.$util.cos(angleb)) - Math.sqrt(Math.pow(2 * lengthc * this.$util.cos(angleb), 2) - 4 * (Math.pow(lengthc, 2) - Math.pow(lengthb, 2)))) / 2
+                        }
+                        anglea = 180 - angleb - anglec
+                    }else if(anglec){
+                        lengtha = ((2 * lengthb * this.$util.cos(anglec)) + Math.sqrt(Math.pow(2 * lengthb * this.$util.cos(anglec), 2) - 4 * (Math.pow(lengthb, 2) - Math.pow(lengthc, 2)))) / 2
+                        if(!this.$util.checkTriangle(lengtha, lengthb, lengthc)){
+                            lengtha = ((2 * lengthb * this.$util.cos(anglec)) - Math.sqrt(Math.pow(2 * lengthb * this.$util.cos(anglec), 2) - 4 * (Math.pow(lengthb, 2) - Math.pow(lengthc, 2)))) / 2
+                        }
+                        angleb = this.$util.asin((lengthb * this.$util.sin(anglec)) / lengthc)
+                        anglea = 180 - anglec - angleb
+                    }
+                } else if(anglea && angleb){
+                    anglec = 180 - anglea - angleb
+                    if(lengtha){
+                        lengthb = (lengtha * this.$util.sin(angleb)) / this.$util.sin(anglea)
+                        lengthc = (lengtha * this.$util.sin(anglec)) / this.$util.sin(anglea)
+                    }else if(lengthb){
+                        lengtha = (lengthb * this.$util.sin(anglea)) / this.$util.sin(angleb)
+                        lengthc = (lengthb * this.$util.sin(anglec)) / this.$util.sin(angleb)
+                    }else if(lengthc){
+                        lengtha = (lengthc * this.$util.sin(anglea)) / this.$util.sin(anglec)
+                        lengthb = (lengthc * this.$util.sin(angleb)) / this.$util.sin(anglec)
+                    }
+                }else if(anglea && anglec){
+                    angleb = 180 - anglea - anglec
+                    if(lengtha){
+                        lengthb = (lengtha * this.$util.sin(angleb)) / this.$util.sin(anglea)
+                        lengthc = (lengtha * this.$util.sin(anglec)) / this.$util.sin(anglea)
+                    }else if(lengthb){
+                        lengtha = (lengthb * this.$util.sin(anglea)) / this.$util.sin(angleb)
+                        lengthc = (lengthb * this.$util.sin(anglec)) / this.$util.sin(angleb)
+                    }else if(lengthc){
+                        lengtha = (lengthc * this.$util.sin(anglea)) / this.$util.sin(anglec)
+                        lengthb = (lengthc * this.$util.sin(angleb)) / this.$util.sin(anglec)
+                    }
+                }else if(angleb && anglec){
+                    anglea = 180 - angleb - anglec
+                    if(lengtha){
+                        lengthb = (lengtha * this.$util.sin(angleb)) / this.$util.sin(anglea)
+                        lengthc = (lengtha * this.$util.sin(anglec)) / this.$util.sin(anglea)
+                    }else if(lengthb){
+                        lengtha = (lengthb * this.$util.sin(anglea)) / this.$util.sin(angleb)
+                        lengthc = (lengthb * this.$util.sin(anglec)) / this.$util.sin(angleb)
+                    }else if(lengthc){
+                        lengtha = (lengthc * this.$util.sin(anglea)) / this.$util.sin(anglec)
+                        lengthb = (lengthc * this.$util.sin(angleb)) / this.$util.sin(anglec)
+                    }
+                }
+
+
+                this.rules.lengtha.value = lengtha;
+                this.rules.lengthb.value = lengthb;
+                this.rules.lengthc.value = lengthc;
+                this.rules.anglea.value = anglea;
+                this.rules.angleb.value = angleb;
+                this.rules.anglec.value = anglec;
+                this.roundRules()
+            },
+            roundRules(){
+                for (const itemKey in this.rules) {
+                    this.rules[itemKey].value = this.$util.round(this.rules[itemKey].value,2);
+                }
+            },
+            initRules(){
+                for (const itemKey in this.rules) {
+                    this.rules[itemKey].value = "";
+                }
+            },
+            getInputItem(item){
+                let validate = [];
+                let placeholder = `请输入${this.rules[item].name}`;
+                let disabled = false;
+                for (const itemKey in this.rules) {
+                    if(item !== itemKey  && typeof this.formData[itemKey] !== "undefined")
+                        validate.push(this.formData[itemKey]);
+                }
+
+                if(item.indexOf('angle') !== -1 && !this.$util.checkArrayNotNullNumber(validate,3)){
+                    validate = [];
+                    for (const itemKey in this.rules) {
+                        if(itemKey.indexOf('angle') !== -1 && itemKey !== item && typeof this.formData[itemKey] !== "undefined"){
+                            validate.push(this.formData[itemKey]);
+                        }
+                    }
+                    if(this.$util.checkArrayNotNullNumber(validate)){
+                        disabled = true;
+                        placeholder = '无需输入';
+                    }
+                }else{
+                    if(this.$util.checkArrayNotNullNumber(validate,3)){
+                        disabled = true;
+                        placeholder = '无需输入';
+                    }
+                }
+
+                return {placeholder: placeholder,disabled:disabled};
+
+            },
+            handleClear() {
+                this.initRules();
+                this.formData = {
+                    lengtha: '',
+                    lengthb: '',
+                    lengthc: '',
+                    anglea: '',
+                    angleb: '',
+                    anglec: '',
+                };
+            }
+        },
+        computed:{
+            lengthAInput(){
+                return this.getInputItem('lengtha')
+            },
+            lengthBInput(){
+                return this.getInputItem('lengthb')
+            },
+            lengthCInput(){
+                return this.getInputItem('lengthc')
+            },
+            angleAInput(){
+                return this.getInputItem('anglea')
+            },
+            angleBInput(){
+                return this.getInputItem('angleb')
+            },
+            angleCInput(){
+                return this.getInputItem('anglec')
+            },
+            showResult(){
+                let validate = [];
+                for (const itemKey in this.rules) {
+                    validate.push(this.rules[itemKey].value);
+                }
+                return this.$util.checkArrayNotNullNumber(validate,3)
+            }
+        }
+    }
+</script>
+
+<style lang="scss" scoped>
+    @import "@/static/css/math.scss";
+</style>

+ 185 - 0
pages/formula/baoliangwan.vue

xqd
@@ -0,0 +1,185 @@
+<template>
+    <view class="math-container">
+        <view class="header">
+            <u-image width="100%" height="300rpx" :src="mathImgs[name].big" mode="aspectFit" @click="handleBigImage"></u-image>
+        </view>
+        <view class="main dir-top-wrap cross-center">
+            <view class="form">
+                <u-form-item label="左高(cm)" label-width="160">
+                    <u-input type="number" v-model="formData.leftHeight" placeholder="请输入左侧高"/>
+                </u-form-item>
+                <u-form-item label="右高(cm)" label-width="160">
+                    <u-input type="number" v-model="formData.rightHeight" placeholder="请输入右侧高"/>
+                </u-form-item>
+                <u-form-item label="左角度(°)" label-width="160">
+                    <u-input type="number" v-model="formData.leftAngle" placeholder="请输入左角度"/>
+                </u-form-item>
+                <u-form-item label="右角度(°)" label-width="160">
+                    <u-input type="number" v-model="formData.rightAngle" placeholder="请输入右角度"/>
+                </u-form-item>
+                <u-form-item label="边高(cm)" label-width="160">
+                    <u-input type="number" v-model="formData.biangao" placeholder="请输入边高"/>
+                </u-form-item>
+                <u-form-item label="柱宽(cm)" label-width="160">
+                    <u-input type="number" v-model="formData.zhukuan" placeholder="请输入柱宽"/>
+                </u-form-item>
+                <u-form-item label="长(cm)" label-width="160">
+                    <u-input type="number" v-model="formData.length" placeholder="选填"/>
+                </u-form-item>
+            </view>
+            <view class="btn-group main-left cross-center">
+                <u-button :ripple="true" @click="handelCalc" :custom-style="{backgroundColor: $u.color['mainBgColor'],color:'#ffffff',width: '260rpx',marginRight:'30rpx'}">计算</u-button>
+                <u-button :ripple="true" @click="handleClear">清空</u-button>
+            </view>
+        </view>
+        <div class="footer">
+            <view class="result dir-top-wrap cross-center" v-if="showResult">
+                <view v-for="item in rules" v-if="item.value && item.show">
+                    <text>{{item.name}}=</text>{{item.value}}{{item.unit}}
+                    <text v-if="item.isHalf">{{$util.round(item.value/2,2)}}{{item.unit}}</text>
+                </view>
+            </view>
+            <view class="title">计算图</view>
+            <view class="calc-img">
+                <text class="qiwan top">{{rules.qiwan.value?rules.qiwan.value:'**'}}{{rules.qiwan.unit}}</text>
+                <text class="leftxiebian top">{{rules.leftxiebian.value?rules.leftxiebian.value:'**'}}{{rules.leftxiebian.unit}}</text>
+                <text class="rightxiebian top">{{rules.rightxiebian.value?rules.rightxiebian.value:'**'}}{{rules.rightxiebian.unit}}</text>
+                <text class="leftxiekou1 bottom">{{rules.leftxiekou.value?rules.leftxiekou.value:'**'}}{{rules.leftxiekou.unit}}</text>
+                <text class="leftxiekou2 bottom">{{rules.leftxiekou.value?rules.leftxiekou.value:'**'}}{{rules.leftxiekou.unit}}</text>
+                <text class="zhukuan">{{formData.zhukuan?$util.round(formData.zhukuan,2):'**'}}cm</text>
+                <text class="rightxiekou1 bottom">{{rules.rightxiekou.value?rules.rightxiekou.value:'**'}}{{rules.rightxiekou.unit}}</text>
+                <text class="rightxiekou2 bottom">{{rules.rightxiekou.value?rules.rightxiekou.value:'**'}}{{rules.rightxiekou.unit}}</text>
+                <u-image width="100%" height="300rpx" :src="mathImgs[name].calc" mode="aspectFit"></u-image>
+            </view>
+            <view class="title">切割图</view>
+            <u-image width="100%" height="300rpx" :src="mathImgs[name].slice" mode="aspectFit"></u-image>
+
+        </div>
+    </view>
+</template>
+
+<script>
+    import mathImgs from "@/core/math-imgs"
+    // 万能公式
+    export default {
+        data() {
+            return {
+                name: 'baoliangwan',
+                mathImgs: mathImgs,
+                formData: {
+                    leftHeight: '',
+                    rightHeight: '',
+                    leftAngle: '',
+                    rightAngle: '',
+                    biangao: '',
+                    zhukuan: '',
+                    lenght: '',
+                },
+                // 用来验证 输入
+                rules: {
+                    leftxiebian: {name:'左斜边', value:'',unit:'cm',show: true},
+                    leftxiekou:{name:'左切口', value:'',unit:'cm',show: true,isHalf:true},
+                    rightxiebian: {name:'右斜边', value:'',unit:'cm',show: true},
+                    rightxiekou:{name:'右切口', value:'',unit:'cm',show: true,isHalf:true},
+                    qiwan:{name:'起弯', value:'',unit:'cm',show: false},
+                }
+            }
+        },
+        methods: {
+            handleBigImage(){
+                uni.previewImage({
+                    urls: [mathImgs[this.name].big],
+                });
+            },
+            handelCalc(){
+                if(!this.formData.leftHeight ||  !this.formData.rightHeight || !this.formData.leftAngle || !this.formData.rightAngle
+                    || !this.formData.biangao || !this.formData.zhukuan ){
+                    this.$u.toast('请填写全部参数');
+                    return
+                }
+                if( this.formData.leftAngle > 90 ||  this.formData.rightHeight > 90){
+                    this.$u.toast('角度不能大于90');
+                    return
+                }
+                this.initRules();
+                /**
+                 * 5、 左斜边=左高×csc左角度°
+                 6、 左切口=边高×tan(左角度°÷2)×2
+                 7、 右斜边=右高×csc右角度°
+                 8、 右切口=边高×tan(右角度°÷2)×2
+                 */
+                this.rules.leftxiebian.value = this.formData.leftHeight * this.$util.csc(this.formData.leftAngle)
+                this.rules.leftxiekou.value = this.formData.biangao * this.$util.tan(this.formData.leftAngle / 2) * 2
+                this.rules.rightxiebian.value = this.formData.rightHeight * this.$util.csc(this.formData.rightAngle)
+                this.rules.rightxiekou.value =  this.formData.biangao * this.$util.tan(this.formData.rightAngle / 2) * 2
+                // 1、 起弯=长-左高×cot左角度°
+                if(this.formData.length){
+                    this.rules.qiwan.value = this.formData.length - this.formData.leftHeight * this.$util.cot(this.formData.leftAngle)
+                }
+
+                this.roundRules();
+                this.$u.toast("请参考计算示意图")
+            },
+            roundRules(){
+                for (const itemKey in this.rules) {
+                    this.rules[itemKey].value = this.$util.round(this.rules[itemKey].value,2);
+                }
+            },
+            initRules(){
+                for (const itemKey in this.rules) {
+                    this.rules[itemKey].value = "";
+                }
+            },
+            handleClear() {
+                this.initRules();
+                this.formData = {
+                    leftHeight: '',
+                    rightHeight: '',
+                    leftAngle: '',
+                    rightAngle: '',
+                    biangao: '',
+                    zhukuan: '',
+                    lenght: '',
+                };
+            }
+        },
+        computed:{
+            showResult(){
+                let validate = [];
+                for (const itemKey in this.rules) {
+                    validate.push(this.rules[itemKey].value);
+                }
+                return this.$util.checkArrayNotNullNumber(validate)
+            }
+        }
+    }
+</script>
+
+<style lang="scss" scoped>
+    @import "@/static/css/math.scss";
+    .qiwan{
+        left: 80rpx;
+    }
+    .leftxiebian{
+        left: 200rpx;
+    }
+    .rightxiebian{
+        left: 440rpx;
+    }
+    .leftxiekou1{
+        left: 160rpx;
+    }
+    .leftxiekou2{
+        left: 260rpx;
+    }
+    .zhukuan{
+        top: 200rpx;
+        left: 320rpx;
+    }
+    .rightxiekou1{
+        left: 380rpx;
+    }
+    .rightxiekou2{
+        left: 490rpx;
+    }
+</style>

+ 130 - 0
pages/formula/bianjindanbian.vue

xqd
@@ -0,0 +1,130 @@
+<template>
+    <view class="math-container">
+        <view class="header">
+            <u-image width="100%" height="300rpx" :src="mathImgs[name].big" mode="aspectFit" @click="handleBigImage"></u-image>
+        </view>
+        <view class="main dir-top-wrap cross-center">
+            <view class="form">
+                <u-form-item label="底宽(cm)" label-width="160">
+                    <u-input type="number" v-model="formData.dikuan" placeholder="请输入底宽"/>
+                </u-form-item>
+                <u-form-item label="变径(cm)" label-width="160">
+                    <u-input type="number" v-model="formData.bianjin" placeholder="请输入变径"/>
+                </u-form-item>
+            </view>
+            <view class="btn-group main-left cross-center">
+                <u-button :ripple="true" @click="handelCalc" :custom-style="{backgroundColor: $u.color['mainBgColor'],color:'#ffffff',width: '260rpx',marginRight:'30rpx'}">计算</u-button>
+                <u-button :ripple="true" @click="handleClear">清空</u-button>
+            </view>
+        </view>
+        <div class="footer">
+            <view class="result dir-top-wrap cross-center" v-if="showResult">
+                <view v-for="item in rules" v-if="item.value && item.show">
+                    <text>{{item.name}}</text>{{item.value}}{{item.unit}}
+                </view>
+            </view>
+            <view class="title">计算图</view>
+            <view class="calc-img">
+                <text class="result2">{{rules.result2.value?rules.result2.value:'**'}}{{rules.result2.unit}}</text>
+                <text class="slice1">{{rules.result1.value?rules.result1.value:'**'}}{{rules.result1.unit}}</text>
+                <text class="slice2">{{rules.result1.value?rules.result1.value:'**'}}{{rules.result1.unit}}</text>
+                <u-image width="100%" height="300rpx" :src="mathImgs[name].calc" mode="aspectFit"></u-image>
+            </view>
+            <view class="title">切割图</view>
+            <u-image width="100%" height="300rpx" :src="mathImgs[name].slice" mode="aspectFit"></u-image>
+
+        </div>
+    </view>
+</template>
+
+<script>
+    import mathImgs from "@/core/math-imgs"
+    // 万能公式
+    export default {
+        data() {
+            return {
+                name: 'bianjindanbian',
+                mathImgs: mathImgs,
+                formData: {
+                    dikuan: '',
+                    bianjin: '',
+                },
+                // 用来验证 输入
+                rules: {
+                    result1: {name:'', value:'',unit:'cm',show: true},
+                    result2: {name:'', value:'',unit:'cm',show: true},
+                }
+            }
+        },
+        methods: {
+            handleBigImage(){
+                uni.previewImage({
+                    urls: [mathImgs[this.name].big],
+                });
+            },
+            handelCalc(){
+                this.formData.dikuan = parseFloat(this.formData.dikuan);
+                this.formData.bianjin = parseFloat(this.formData.bianjin);
+                let validate = [this.formData.dikuan,this.formData.bianjin]
+                if(!this.$util.checkArrayNotNullNumber(validate)){
+                    this.$u.toast('至少输入两个参数');
+                    return
+                }
+                if(this.formData.dikuan <= this.formData.bianjin){
+                    this.$u.toast('变径不能大于底宽');
+                    return
+                }
+                this.initRules();
+                /**
+                 1、 结果一 =(底宽-半径)×1.414
+                 2、 结果二 = 底宽-半径
+                 */
+                this.rules.result1.value = (this.formData.dikuan - this.formData.bianjin) * 1.414
+                this.rules.result2.value = this.formData.dikuan - this.formData.bianjin
+                this.roundRules();
+                this.$u.toast("请参考计算示意图")
+            },
+            roundRules(){
+                for (const itemKey in this.rules) {
+                    this.rules[itemKey].value = this.$util.round(this.rules[itemKey].value,2);
+                }
+            },
+            initRules(){
+                for (const itemKey in this.rules) {
+                    this.rules[itemKey].value = "";
+                }
+            },
+            handleClear() {
+                this.initRules();
+                this.formData = {
+                    dikuan: '',
+                    bianjin: '',
+                };
+            }
+        },
+        computed:{
+            showResult(){
+                let validate = [];
+                for (const itemKey in this.rules) {
+                    validate.push(this.rules[itemKey].value);
+                }
+                return this.$util.checkArrayNotNullNumber(validate,1)
+            }
+        }
+    }
+</script>
+
+<style lang="scss" scoped>
+    @import "@/static/css/math.scss";
+    .result2{
+        top: 80rpx;
+    }
+    .slice1{
+        top: 20rpx;
+        left: 360rpx;
+    }
+    .slice2{
+        top: 260rpx;
+        left: 330rpx;
+    }
+</style>

+ 140 - 0
pages/formula/bianjinshuangbian.vue

xqd
@@ -0,0 +1,140 @@
+<template>
+    <view class="math-container">
+        <view class="header">
+            <u-image width="100%" height="300rpx" :src="mathImgs[name].big" mode="aspectFit" @click="handleBigImage"></u-image>
+        </view>
+        <view class="main dir-top-wrap cross-center">
+            <view class="form">
+                <u-form-item label="底宽(cm)" label-width="160">
+                    <u-input type="number" v-model="formData.dikuan" placeholder="请输入底宽"/>
+                </u-form-item>
+                <u-form-item label="变径(cm)" label-width="160">
+                    <u-input type="number" v-model="formData.bianjin" placeholder="请输入变径"/>
+                </u-form-item>
+            </view>
+            <view class="btn-group main-left cross-center">
+                <u-button :ripple="true" @click="handelCalc" :custom-style="{backgroundColor: $u.color['mainBgColor'],color:'#ffffff',width: '260rpx',marginRight:'30rpx'}">计算</u-button>
+                <u-button :ripple="true" @click="handleClear">清空</u-button>
+            </view>
+        </view>
+        <div class="footer">
+            <view class="result dir-top-wrap cross-center" v-if="showResult">
+                <view v-for="item in rules" v-if="item.value && item.show">
+                    <text>{{item.name}}</text>{{item.value}}{{item.unit}}
+                </view>
+            </view>
+            <view class="title">计算图</view>
+            <view class="calc-img">
+                <text class="result1">{{rules.result2.value?rules.result2.value:'**'}}{{rules.result2.unit}}</text>
+                <text class="result2">{{rules.result2.value?rules.result2.value:'**'}}{{rules.result2.unit}}</text>
+                <text class="slice1">{{rules.result1.value?rules.result1.value:'**'}}{{rules.result1.unit}}</text>
+                <text class="slice2">{{rules.result1.value?rules.result1.value:'**'}}{{rules.result1.unit}}</text>
+                <text class="slice3">{{rules.result1.value?rules.result1.value:'**'}}{{rules.result1.unit}}</text>
+                <u-image width="100%" height="400rpx" :src="mathImgs[name].calc" mode="widthFix"></u-image>
+            </view>
+            <view class="title">切割图</view>
+            <u-image width="100%" height="400rpx" :src="mathImgs[name].slice" mode="widthFix"></u-image>
+
+        </div>
+    </view>
+</template>
+
+<script>
+    import mathImgs from "@/core/math-imgs"
+    // 万能公式
+    export default {
+        data() {
+            return {
+                name: 'bianjinshuangbian',
+                mathImgs: mathImgs,
+                formData: {
+                    dikuan: '',
+                    bianjin: '',
+                },
+                // 用来验证 输入
+                rules: {
+                    result1: {name:'', value:'',unit:'cm',show: true},
+                    result2: {name:'', value:'',unit:'cm',show: true},
+                }
+            }
+        },
+        methods: {
+            handleBigImage(){
+                uni.previewImage({
+                    urls: [mathImgs[this.name].big],
+                });
+            },
+            handelCalc(){
+                this.formData.dikuan = parseFloat(this.formData.dikuan);
+                this.formData.bianjin = parseFloat(this.formData.bianjin);
+                let validate = [this.formData.dikuan,this.formData.bianjin]
+                if(!this.$util.checkArrayNotNullNumber(validate)){
+                    this.$u.toast('至少输入两个参数');
+                    return
+                }
+                if(this.formData.dikuan <= this.formData.bianjin){
+                    this.$u.toast('变径不能大于底宽');
+                    return
+                }
+                this.initRules();
+                /**
+                 3、 结果一 = (底宽-半径)×1.414÷2
+                 4、 结果二 = (底宽-半径)÷2
+                 */
+                this.rules.result1.value = (this.formData.dikuan - this.formData.bianjin) * 1.414 / 2
+                this.rules.result2.value = (this.formData.dikuan - this.formData.bianjin) / 2
+                this.roundRules();
+                this.$u.toast("请参考计算示意图")
+            },
+            roundRules(){
+                for (const itemKey in this.rules) {
+                    this.rules[itemKey].value = this.$util.round(this.rules[itemKey].value,2);
+                }
+            },
+            initRules(){
+                for (const itemKey in this.rules) {
+                    this.rules[itemKey].value = "";
+                }
+            },
+            handleClear() {
+                this.initRules();
+                this.formData = {
+                    dikuan: '',
+                    bianjin: '',
+                };
+            }
+        },
+        computed:{
+            showResult(){
+                let validate = [];
+                for (const itemKey in this.rules) {
+                    validate.push(this.rules[itemKey].value);
+                }
+                return this.$util.checkArrayNotNullNumber(validate,1)
+            }
+        }
+    }
+</script>
+
+<style lang="scss" scoped>
+    @import "@/static/css/math.scss";
+    .result1{
+        left: 630rpx;
+        top: 70rpx;
+    }
+    .result2{
+        left: 630rpx;
+        top: 230rpx;
+    }
+    .slice1{
+        left: 330rpx;
+    }
+    .slice2{
+        top: 160rpx;
+        left: 330rpx;
+    }
+    .slice3{
+        top: 380rpx;
+        left: 350rpx;
+    }
+</style>

+ 146 - 0
pages/formula/downhill.vue

xqd
@@ -0,0 +1,146 @@
+<template>
+    <view class="math-container">
+        <view class="header">
+            <u-image width="100%" height="300rpx" :src="mathImgs[name].big" mode="aspectFit" @click="handleBigImage"></u-image>
+        </view>
+        <view class="main dir-top-wrap cross-center">
+            <view class="form">
+                <u-form-item label="高(cm)" label-width="160">
+                    <u-input type="number" v-model="formData.height" placeholder="请输入高"/>
+                </u-form-item>
+                <u-form-item label="角度(°)" label-width="160">
+                    <u-input type="number" v-model="formData.angle" placeholder="请输入角度"/>
+                </u-form-item>
+                <u-form-item label="边高(cm)" label-width="160">
+                    <u-input type="number" v-model="formData.biangao" placeholder="请输入边高"/>
+                </u-form-item>
+                <u-form-item label="长(cm)" label-width="160">
+                    <u-input type="number" v-model="formData.length" placeholder="选填"/>
+                </u-form-item>
+            </view>
+            <view class="btn-group main-left cross-center">
+                <u-button :ripple="true" @click="handelCalc" :custom-style="{backgroundColor: $u.color['mainBgColor'],color:'#ffffff',width: '260rpx',marginRight:'30rpx'}">计算</u-button>
+                <u-button :ripple="true" @click="handleClear">清空</u-button>
+            </view>
+        </view>
+        <div class="footer">
+            <view class="result dir-top-wrap cross-center" v-if="showResult">
+                <view v-for="item in rules" v-if="item.value && item.show">
+                    <text>{{item.name}}=</text>{{item.value}}{{item.unit}}
+                    <text v-if="item.isHalf">{{$util.round(item.value/2,2)}}{{item.unit}}</text>
+                </view>
+            </view>
+            <view class="title">计算图</view>
+            <view class="calc-img">
+                <text class="qiwan top">{{rules.qiwan.value?rules.qiwan.value:'**'}}{{rules.qiwan.unit}}</text>
+                <text class="xiebian top">{{rules.xiebian.value?rules.xiebian.value:'**'}}{{rules.xiebian.unit}}</text>
+                <text class="slice1 bottom">{{rules.slice.value?rules.slice.value:'**'}}{{rules.slice.unit}}</text>
+                <text class="slice2 bottom">{{rules.slice.value?rules.slice.value:'**'}}{{rules.slice.unit}}</text>
+                <u-image width="100%" height="300rpx" :src="mathImgs[name].calc" mode="aspectFit"></u-image>
+            </view>
+            <view class="title">切割图</view>
+            <u-image width="100%" height="300rpx" :src="mathImgs[name].slice" mode="aspectFit"></u-image>
+
+        </div>
+    </view>
+</template>
+
+<script>
+    import mathImgs from "@/core/math-imgs"
+    // 万能公式
+    export default {
+        data() {
+            return {
+                name: 'downhill',
+                mathImgs: mathImgs,
+                formData: {
+                    height: '',
+                    angle: '',
+                    biangao: '',
+                    lenght: '',
+                },
+                // 用来验证 输入
+                rules: {
+                    xiebian:{name:'斜边', value:'',unit:'cm',show: true},
+                    qiwan:{name:'起弯', value:'',unit:'cm',show: true},
+                    slice:{name:'切口', value:'',unit:'cm',show: true,isHalf:true},
+                }
+            }
+        },
+        methods: {
+            handleBigImage(){
+                uni.previewImage({
+                    urls: [mathImgs[this.name].big],
+                });
+            },
+            handelCalc(){
+                this.initRules();
+                if( this.formData.angle &&  this.formData.angle > 90){
+                    this.$u.toast('角度不能大于90');
+                    return
+                }
+                /**
+                 1、斜边=高×csc角度°
+                 2、切口=边高×tan(角度°÷2)×2
+                 */
+                if(this.formData.height && this.formData.angle && this.formData.biangao){
+                    this.rules.xiebian.value = this.formData.height * this.$util.csc(this.formData.angle);
+                    this.rules.slice.value = this.formData.biangao * this.$util.tan(this.formData.angle / 2) * 2;
+                }else{
+                    this.$u.toast('请输入三个参数');
+                    return;
+                }
+                //  1、 起弯=长 -(高×cot角度°)
+                if(this.formData.length){
+                    this.rules.qiwan.value = this.formData.length - (this.formData.height * this.$util.cot(this.formData.angle))
+                }
+                this.roundRules();
+                this.$u.toast("请参考计算示意图")
+            },
+            roundRules(){
+                for (const itemKey in this.rules) {
+                    this.rules[itemKey].value = this.$util.round(this.rules[itemKey].value,2);
+                }
+            },
+            initRules(){
+                for (const itemKey in this.rules) {
+                    this.rules[itemKey].value = "";
+                }
+            },
+            handleClear() {
+                this.initRules();
+                this.formData = {
+                    height: '',
+                    angle: '',
+                    biangao: '',
+                    lenght: '',
+                };
+            }
+        },
+        computed:{
+            showResult(){
+                let validate = [];
+                for (const itemKey in this.rules) {
+                    validate.push(this.rules[itemKey].value);
+                }
+                return this.$util.checkArrayNotNullNumber(validate)
+            }
+        }
+    }
+</script>
+
+<style lang="scss" scoped>
+    @import "@/static/css/math.scss";
+    .qiwan{
+        left: 120rpx;
+    }
+    .xiebian{
+        left: 300rpx;
+    }
+    .slice1{
+        left: 210rpx;
+    }
+    .slice2{
+        left: 430rpx;
+    }
+</style>

+ 309 - 0
pages/formula/duochengdengwan.vue

xqd
@@ -0,0 +1,309 @@
+<template>
+    <view class="math-container">
+        <view class="header">
+            <u-image width="100%" height="300rpx" :src="mathImgs[name].big" mode="aspectFit" @click="handleBigImage"></u-image>
+        </view>
+        <view class="main dir-top-wrap cross-center">
+            <view class="form">
+                <u-form-item label="角度(°)" label-width="180">
+                    <u-input type="number" v-model="formData.angle"  placeholder="请输入角度"/>
+                </u-form-item>
+                <u-form-item label="层间距(cm)" label-width="180">
+                    <u-input type="number" v-model="formData.cengjianju"  placeholder="请输入层间距"/>
+                </u-form-item>
+                <u-form-item label="已测量层数" label-width="180">
+                    <text @click="cengShow = true" class="select">{{cengTxt}}</text>
+                    <u-select v-model="cengShow" :list="cengList" @confirm="handleSelectCeng"></u-select>
+                </u-form-item>
+                <u-form-item label="边高(cm)" label-width="180">
+                    <u-input type="number" v-model="formData.biangao" placeholder="请输入边高"/>
+                </u-form-item>
+                <u-form-item label="起弯(cm)" label-width="180">
+                    <u-input type="number" v-model="formData.qiwan" placeholder="请输入起弯"/>
+                </u-form-item>
+                <u-form-item label="斜边(cm)" label-width="180">
+                    <u-input type="number" v-model="formData.xiebian"  placeholder="选填"/>
+                </u-form-item>
+
+            </view>
+            <view class="btn-group main-left cross-center">
+                <u-button :ripple="true" @click="handelCalc" :custom-style="{backgroundColor: $u.color['mainBgColor'],color:'#ffffff',width: '260rpx',marginRight:'30rpx'}">计算</u-button>
+                <u-button :ripple="true" @click="handleClear">清空</u-button>
+            </view>
+        </view>
+        <div class="footer">
+            <view class="result dir-top-wrap cross-center" v-if="showResult">
+                <view v-for="item in rules" v-if="item.value && item.show">
+                    <text>{{item.name}}=</text>{{item.value}}{{item.unit}}
+                    <text v-if="item.isHalf">{{$util.round(item.value/2,2)}}{{item.unit}}</text>
+                </view>
+            </view>
+            <view class="title">计算图</view>
+            <view class="calc-img">
+                <text class="diyiceng">{{rules.diyiceng.value?rules.diyiceng.value:'**'}}{{rules.diyiceng.unit}}</text>
+                <text class="diyicengxiebian">{{rules.diyicengxiebian.value?rules.diyicengxiebian.value:'**'}}{{rules.diyicengxiebian.unit}}</text>
+                <text class="slice1">{{rules.slice.value?rules.slice.value:'**'}}{{rules.slice.unit}}</text>
+                <text class="slice2">{{rules.slice.value?rules.slice.value:'**'}}{{rules.slice.unit}}</text>
+                <text class="dierceng">{{rules.dierceng.value?rules.dierceng.value:'**'}}{{rules.dierceng.unit}}</text>
+                <text class="diercengxiebian">{{rules.diercengxiebian.value?rules.diercengxiebian.value:'**'}}{{rules.diercengxiebian.unit}}</text>
+                <text class="slice3">{{rules.slice.value?rules.slice.value:'**'}}{{rules.slice.unit}}</text>
+                <text class="slice4">{{rules.slice.value?rules.slice.value:'**'}}{{rules.slice.unit}}</text>
+                <text class="disanceng">{{rules.disanceng.value?rules.disanceng.value:'**'}}{{rules.disanceng.unit}}</text>
+                <text class="disancengxiebian">{{rules.disancengxiebian.value?rules.disancengxiebian.value:'**'}}{{rules.disancengxiebian.unit}}</text>
+                <text class="slice5">{{rules.slice.value?rules.slice.value:'**'}}{{rules.slice.unit}}</text>
+                <text class="slice6">{{rules.slice.value?rules.slice.value:'**'}}{{rules.slice.unit}}</text>
+                <text class="cengjianju1">{{formData.cengjianju?formData.cengjianju:'**'}}cm</text>
+                <text class="cengjianju2">{{formData.cengjianju?formData.cengjianju:'**'}}cm</text>
+                <u-image width="100%" height="600rpx" :src="mathImgs[name].calc" mode="widthFix"></u-image>
+            </view>
+            <view class="title">切割图</view>
+            <u-image width="100%" height="600rpx" :src="mathImgs[name].slice" mode="widthFix"></u-image>
+
+        </div>
+    </view>
+</template>
+
+<script>
+    import mathImgs from "@/core/math-imgs"
+    // 万能公式
+    export default {
+        data() {
+            return {
+                name: 'duochengdengwan',
+                mathImgs: mathImgs,
+                formData: {
+                    angle: '',
+                    cengjianju: '',
+                    ceng: '1',
+                    biangao: '',
+                    qiwan: '',
+                    xiebian: '',
+                },
+                // 用来验证 输入
+                rules: {
+                    diyiceng: {name:'第一层起弯', value:'',unit:'cm',show: true},
+                    dierceng:{name:'第二层起弯', value:'',unit:'cm',show: true},
+                    disanceng: {name:'第三层起弯', value:'',unit:'cm',show: true},
+                    diyicengxiebian: {name:'第一层斜边', value:'',unit:'cm',show: true},
+                    diercengxiebian:{name:'第二层斜边', value:'',unit:'cm',show: true},
+                    disancengxiebian: {name:'第三层斜边', value:'',unit:'cm',show: true},
+                    slice:{name:'切口', value:'',unit:'cm',show: true,isHalf:true},
+                },
+                cengShow: false,
+                cengList: [
+                    {value: '1',label: '第一层'},
+                    {value: '2',label: '第二层'},
+                    {value: '3',label: '第三层'},
+                ],
+                cengTxt: "第一层",
+            }
+        },
+        methods: {
+            handleBigImage(){
+                uni.previewImage({
+                    urls: [mathImgs[this.name].big],
+                });
+            },
+            handelCalc(){
+                if(!this.formData.angle){
+                    this.$u.toast('请输入角度');
+                    return
+                }
+                if( this.formData.angle &&  this.formData.angle > 90){
+                    this.$u.toast('角度不能大于90');
+                    return
+                }
+                if(!this.formData.cengjianju){
+                    this.$u.toast('请输入层间距');
+                    return
+                }
+                if(!this.formData.biangao){
+                    this.$u.toast('请输入边高');
+                    return
+                }
+                if(!this.formData.xiebian){
+                    this.$u.toast('请输入起弯');
+                    return
+                }
+                this.initRules();
+
+                this.formData.angle = parseFloat(this.formData.angle);
+                this.formData.cengjianju = parseFloat(this.formData.cengjianju);
+                this.formData.biangao = parseFloat(this.formData.biangao);
+                this.formData.xiebian = parseFloat(this.formData.xiebian);
+                this.formData.ceng = parseFloat(this.formData.ceng);
+                this.formData.cengjianju = parseFloat(this.formData.cengjianju);
+                this.formData.qiwan = parseFloat(this.formData.qiwan);
+
+                /**
+                 * 1、 已知第一层:
+                 第二层起弯 = 起弯-(边高+层间数)×tan(角度°÷2)
+                 第三层起弯 = 起弯-(边高+层间数)×tan(角度°÷2)×2
+                 切口 = 边高×tan(角度°÷2)×2
+                 */
+                if(this.formData.ceng === 1){
+                    this.rules.diyiceng.value = this.formData.qiwan
+                    this.rules.diyicengxiebian.value =  this.formData.xiebian
+                    this.rules.diyiceng.show = false;
+                    this.rules.diyicengxiebian.show = false;
+
+                    this.rules.dierceng.value = this.formData.qiwan - (this.formData.biangao + this.formData.cengjianju) * this.$util.tan(this.formData.angle / 2);
+                    this.rules.disanceng.value = this.formData.qiwan - (this.formData.biangao + this.formData.cengjianju) * this.$util.tan(this.formData.angle / 2) * 2;
+                    /**
+                     * 1、 已知第一层起弯:
+                     * 第二层起弯 = 起弯-(边高+层间距)×tan(角度°÷2)
+                     * 第三层起弯 = 起弯-(边高+层间距)×tan(角度°÷2)×2
+                     */
+                    if(this.formData.xiebian){
+                        this.rules.diercengxiebian.value = this.formData.xiebian;
+                        this.rules.disancengxiebian.value = this.formData.xiebian;
+                    }
+                }
+                /**
+                 *  2、 已知第二层:
+                 第一层起弯 = 起弯+(边高+层间数)×tan(角度°÷2)
+                 第三层起弯 = 起弯-(边高+层间数)×tan(角度°÷2)
+                 切口 = 边高×tan(角度°÷2)×2
+
+                 */
+                if(this.formData.ceng === 2){
+                    this.rules.dierceng.value = this.formData.qiwan
+                    this.rules.diercengxiebian.value =  this.formData.xiebian
+                    this.rules.dierceng.show = false;
+                    this.rules.diercengxiebian.show = false;
+
+                    this.rules.diyiceng.value = this.formData.qiwan + (this.formData.biangao + this.formData.cengjianju) * this.$util.tan(this.formData.angle / 2);
+                    this.rules.disanceng.value = this.formData.qiwan - (this.formData.biangao + this.formData.cengjianju) * this.$util.tan(this.formData.angle / 2);
+                    /**
+                     * 1、 已知第二层起弯
+                     * 第一层起弯 = 起弯+(边高+层间数)×tan(角度°÷2)
+                     * 第三层起弯 = 起弯-(边高+层间数)×tan(角度°÷2)
+                     */
+                    if(this.formData.xiebian){
+                        this.rules.diyicengxiebian.value = this.formData.xiebian;
+                        this.rules.disancengxiebian.value = this.formData.xiebian;
+                    }
+                }
+                /**
+                 * 3、 已知第三层:
+                 * 第一层起弯 = 起弯+(边高+层间数)×tan(角度°÷2)×4
+                 第二层起弯 = 起弯+(边高+层间数)×tan(角度°÷2)×2
+                 切口 = 边高×tan(角度°÷2)×2
+                 */
+                if(this.formData.ceng === 3){
+                    this.rules.disanceng.value = this.formData.qiwan
+                    this.rules.disancengxiebian.value =  this.formData.xiebian
+                    this.rules.disanceng.show = false;
+                    this.rules.disancengxiebian.show = false;
+
+                    this.rules.diyiceng.value = this.formData.qiwan + (this.formData.biangao + this.formData.cengjianju) * this.$util.tan(this.formData.angle / 2) * 4;
+                    this.rules.dierceng.value = this.formData.qiwan + (this.formData.biangao + this.formData.cengjianju) * this.$util.tan(this.formData.angle / 2) * 2;
+                    /**
+                     * 3、 已知第三层起弯:
+                     * 第一层起弯 = 起弯+(边高+层间数)×tan(角度°÷2)×4
+                     * 第二层起弯 = 起弯+(边高+层间数)×tan(角度°÷2)×2
+                     */
+                    if(this.formData.xiebian){
+                        this.rules.diyicengxiebian.value = this.formData.xiebian
+                        this.rules.diercengxiebian.value = this.formData.xiebian;
+                    }
+                }
+                this.rules.slice.value = this.formData.biangao * this.$util.tan(this.formData.angle / 2) * 2;
+
+                this.roundRules();
+                this.$u.toast("请参考计算示意图")
+            },
+            roundRules(){
+                for (const itemKey in this.rules) {
+                    this.rules[itemKey].value = this.$util.round(this.rules[itemKey].value,2);
+                }
+            },
+            initRules(){
+                for (const itemKey in this.rules) {
+                    this.rules[itemKey].value = "";
+                    this.rules[itemKey].show = true;
+                }
+            },
+            handleSelectCeng(e){
+                this.formData.ceng = e[0].value;
+                this.cengTxt = e[0].label;
+            },
+            handleClear() {
+                this.initRules();
+                this.formData = {
+                    angle: '',
+                    cengjianju: '',
+                    ceng: '1',
+                    biangao: '',
+                    qiwan: '',
+                    xiebian: '',
+                };
+            }
+        },
+        computed:{
+            showResult(){
+                let validate = [];
+                for (const itemKey in this.rules) {
+                    validate.push(this.rules[itemKey].value);
+                }
+                return this.$util.checkArrayNotNullNumber(validate)
+            }
+        }
+    }
+</script>
+
+<style lang="scss" scoped>
+    @import "@/static/css/math.scss";
+    .diyiceng{
+        left: 150rpx;
+    }
+    .diyicengxiebian{
+        left: 320rpx;
+    }
+    .slice1{
+        top: 170rpx;
+        left: 240rpx;
+    }
+    .slice2{
+        top: 170rpx;
+        left: 445rpx;
+    }
+    .dierceng{
+        top: 190rpx;
+        left: 170rpx;
+    }
+    .diercengxiebian{
+        top: 190rpx;
+        left: 340rpx;
+    }
+    .slice3{
+        top: 350rpx;
+        left: 260rpx;
+    }
+    .slice4{
+        top: 350rpx;
+        left: 445rpx;
+    }
+    .disanceng{
+        top: 360rpx;
+        left: 170rpx;
+    }
+    .disancengxiebian{
+        top: 360rpx;
+        left: 340rpx;
+    }
+    .slice5{
+        top: 520rpx;
+        left: 280rpx;
+    }
+    .slice6{
+        top: 520rpx;
+        left: 445rpx;
+    }
+    .cengjianju1{
+        top: 200rpx;
+    }
+    .cengjianju2{
+        top: 370rpx;
+    }
+</style>

+ 307 - 0
pages/formula/duochengzhijiao.vue

xqd
@@ -0,0 +1,307 @@
+<template>
+    <view class="math-container">
+        <view class="header">
+            <u-image width="100%" height="300rpx" :src="mathImgs[name].big" mode="aspectFit" @click="handleBigImage"></u-image>
+        </view>
+        <view class="main dir-top-wrap cross-center">
+            <view class="form">
+                <u-form-item label="角度(°)" label-width="180">
+                    <u-input type="number" v-model="formData.angle"  placeholder="请输入角度"/>
+                </u-form-item>
+                <u-form-item label="层间距(cm)" label-width="180">
+                    <u-input type="number" v-model="formData.cengjianju"  placeholder="请输入层间距"/>
+                </u-form-item>
+                <u-form-item label="已测量层数" label-width="180">
+                    <text @click="cengShow = true" class="select">{{cengTxt}}</text>
+                    <u-select v-model="cengShow" :list="cengList" @confirm="handleSelectCeng"></u-select>
+                </u-form-item>
+                <u-form-item label="斜边(cm)" label-width="180">
+                    <u-input type="number" v-model="formData.xiebian"  placeholder="请输入斜边"/>
+                </u-form-item>
+                <u-form-item label="边高(cm)" label-width="180">
+                    <u-input type="number" v-model="formData.biangao" placeholder="请输入边高"/>
+                </u-form-item>
+                <u-form-item label="起弯(cm)" label-width="180">
+                    <u-input type="number" v-model="formData.qiwan" placeholder="选填"/>
+                </u-form-item>
+            </view>
+            <view class="btn-group main-left cross-center">
+                <u-button :ripple="true" @click="handelCalc" :custom-style="{backgroundColor: $u.color['mainBgColor'],color:'#ffffff',width: '260rpx',marginRight:'30rpx'}">计算</u-button>
+                <u-button :ripple="true" @click="handleClear">清空</u-button>
+            </view>
+        </view>
+        <div class="footer">
+            <view class="result dir-top-wrap cross-center" v-if="showResult">
+                <view v-for="item in rules" v-if="item.value && item.show">
+                    <text>{{item.name}}=</text>{{item.value}}{{item.unit}}
+                    <text v-if="item.isHalf">{{$util.round(item.value/2,2)}}{{item.unit}}</text>
+                </view>
+            </view>
+            <view class="title">计算图</view>
+            <view class="calc-img">
+                <text class="diyiceng">{{rules.diyiceng.value?rules.diyiceng.value:'**'}}{{rules.diyiceng.unit}}</text>
+                <text class="diyicengqiwan">{{rules.diyicengqiwan.value?rules.diyicengqiwan.value:'**'}}{{rules.diyicengqiwan.unit}}</text>
+                <text class="slice1">{{rules.slice.value?rules.slice.value:'**'}}{{rules.slice.unit}}</text>
+                <text class="slice2">{{rules.slice.value?rules.slice.value:'**'}}{{rules.slice.unit}}</text>
+                <text class="dierceng">{{rules.dierceng.value?rules.dierceng.value:'**'}}{{rules.dierceng.unit}}</text>
+                <text class="diercengqiwan">{{rules.diercengqiwan.value?rules.diercengqiwan.value:'**'}}{{rules.diercengqiwan.unit}}</text>
+                <text class="slice3">{{rules.slice.value?rules.slice.value:'**'}}{{rules.slice.unit}}</text>
+                <text class="slice4">{{rules.slice.value?rules.slice.value:'**'}}{{rules.slice.unit}}</text>
+                <text class="disanceng">{{rules.disanceng.value?rules.disanceng.value:'**'}}{{rules.disanceng.unit}}</text>
+                <text class="disancengqiwan">{{rules.disancengqiwan.value?rules.disancengqiwan.value:'**'}}{{rules.disancengqiwan.unit}}</text>
+                <text class="slice5">{{rules.slice.value?rules.slice.value:'**'}}{{rules.slice.unit}}</text>
+                <text class="slice6">{{rules.slice.value?rules.slice.value:'**'}}{{rules.slice.unit}}</text>
+                <text class="cengjianju1">{{formData.cengjianju?formData.cengjianju:'**'}}cm</text>
+                <text class="cengjianju2">{{formData.cengjianju?formData.cengjianju:'**'}}cm</text>
+                <u-image width="100%" height="600rpx" :src="mathImgs[name].calc" mode="widthFix"></u-image>
+            </view>
+            <view class="title">切割图</view>
+            <u-image width="100%" height="600rpx" :src="mathImgs[name].slice" mode="widthFix"></u-image>
+
+        </div>
+    </view>
+</template>
+
+<script>
+    import mathImgs from "@/core/math-imgs"
+    // 万能公式
+    export default {
+        data() {
+            return {
+                name: 'duochengzhijiao',
+                mathImgs: mathImgs,
+                formData: {
+                    angle: '',
+                    cengjianju: '',
+                    ceng: '1',
+                    xiebian: '',
+                    biangao: '',
+                    qiwan: '',
+                },
+                // 用来验证 输入
+                rules: {
+                    diyiceng: {name:'第一层斜边', value:'',unit:'cm',show: true},
+                    dierceng:{name:'第二层斜边', value:'',unit:'cm',show: true},
+                    disanceng: {name:'第三层斜边', value:'',unit:'cm',show: true},
+                    diyicengqiwan: {name:'第一层起弯', value:'',unit:'cm',show: true},
+                    diercengqiwan:{name:'第二层起弯', value:'',unit:'cm',show: true},
+                    disancengqiwan: {name:'第三层起弯', value:'',unit:'cm',show: true},
+                    slice:{name:'切口', value:'',unit:'cm',show: true,isHalf:true},
+                },
+                cengShow: false,
+                cengList: [
+                    {value: '1',label: '第一层'},
+                    {value: '2',label: '第二层'},
+                    {value: '3',label: '第三层'},
+                ],
+                cengTxt: "第一层",
+            }
+        },
+        methods: {
+            handleBigImage(){
+                uni.previewImage({
+                    urls: [mathImgs[this.name].big],
+                });
+            },
+            handelCalc(){
+                if(!this.formData.angle){
+                    this.$u.toast('请输入角度');
+                    return
+                }
+                if( this.formData.angle &&  this.formData.angle > 90){
+                    this.$u.toast('角度不能大于90');
+                    return
+                }
+                if(!this.formData.cengjianju){
+                    this.$u.toast('请输入层间距');
+                    return
+                }
+                if(!this.formData.xiebian){
+                    this.$u.toast('请输入斜边');
+                    return
+                }
+                if(!this.formData.biangao){
+                    this.$u.toast('请输入边高');
+                    return
+                }
+                this.initRules();
+                /**
+                 * 1、 已知第一层:
+                 * 第二层斜边=斜边-(边高+层间距)×tan(角度°÷2)×2
+                 * 第三层斜边=斜边-(边高+层间距)×tan(角度°÷2)×4
+                 * 切口 = 边高×tan(角度°÷2)×2
+                 */
+                this.formData.angle = parseFloat(this.formData.angle);
+                this.formData.cengjianju = parseFloat(this.formData.cengjianju);
+                this.formData.biangao = parseFloat(this.formData.biangao);
+                this.formData.xiebian = parseFloat(this.formData.xiebian);
+                this.formData.ceng = parseFloat(this.formData.ceng);
+                this.formData.cengjianju = parseFloat(this.formData.cengjianju);
+                this.formData.qiwan = parseFloat(this.formData.qiwan);
+
+
+                if(this.formData.ceng === 1){
+                    this.rules.diyiceng.value = this.formData.xiebian
+                    this.rules.diyicengqiwan.value =  this.formData.qiwan
+                    this.rules.diyiceng.show = false;
+                    this.rules.diyicengqiwan.show = false;
+
+                    this.rules.dierceng.value = this.formData.xiebian - (this.formData.biangao + this.formData.cengjianju) * this.$util.tan(this.formData.angle / 2) * 2;
+                    this.rules.disanceng.value = this.formData.xiebian - (this.formData.biangao + this.formData.cengjianju) * this.$util.tan(this.formData.angle / 2) * 4;
+                    /**
+                     * 1、 已知第一层起弯:
+                     * 第二层起弯 = 起弯-(边高+层间距)×tan(角度°÷2)
+                     * 第三层起弯 = 起弯-(边高+层间距)×tan(角度°÷2)×2
+                     */
+                    if(this.formData.qiwan){
+                        this.rules.diercengqiwan.value = this.formData.qiwan - (this.formData.biangao + this.formData.cengjianju) * this.$util.tan(this.formData.angle / 2);
+                        this.rules.disancengqiwan.value = this.formData.qiwan - (this.formData.biangao + this.formData.cengjianju) * this.$util.tan(this.formData.angle / 2) * 2;
+                    }
+                }
+                /**
+                 *  2、 已知第二层:
+                 *  第一层斜边=斜边+(边高+层间距)×tan(角度°÷2)×2
+                 *  第三层斜边=斜边-(边高+层间距)×tan(角度°÷2)×2
+                 *  切口 = 边高×tan(角度°÷2)×2
+                 */
+                if(this.formData.ceng === 2){
+                    this.rules.dierceng.value = this.formData.xiebian
+                    this.rules.diercengqiwan.value =  this.formData.qiwan
+                    this.rules.dierceng.show = false;
+                    this.rules.diercengqiwan.show = false;
+
+                    this.rules.diyiceng.value = this.formData.xiebian + (this.formData.biangao + this.formData.cengjianju) * this.$util.tan(this.formData.angle / 2) * 2;
+                    this.rules.disanceng.value = this.formData.xiebian - (this.formData.biangao + this.formData.cengjianju) * this.$util.tan(this.formData.angle / 2) * 2;
+                    /**
+                     * 1、 已知第二层起弯
+                     * 第一层起弯 = 起弯+(边高+层间数)×tan(角度°÷2)
+                     * 第三层起弯 = 起弯-(边高+层间数)×tan(角度°÷2)
+                     */
+                    if(this.formData.qiwan){
+                        this.rules.diyicengqiwan.value = this.formData.qiwan + (this.formData.biangao + this.formData.cengjianju) * this.$util.tan(this.formData.angle / 2);
+                        this.rules.disancengqiwan.value = this.formData.qiwan - (this.formData.biangao + this.formData.cengjianju) * this.$util.tan(this.formData.angle / 2);
+                    }
+                }
+                /**
+                 * 3、 已知第三层:
+                 * 第一层斜边=斜边+(边高+层间距)×tan(角度°÷2)×4
+                 * 第二层斜边=斜边+(边高+层间距)×tan(角度°÷2)×2
+                 * 切口 = 边高×tan(角度°÷2)×2
+                 */
+                if(this.formData.ceng === 3){
+                    this.rules.disanceng.value = this.formData.xiebian
+                    this.rules.disancengqiwan.value =  this.formData.qiwan
+                    this.rules.disanceng.show = false;
+                    this.rules.disancengqiwan.show = false;
+
+                    this.rules.diyiceng.value = this.formData.xiebian + (this.formData.biangao + this.formData.cengjianju) * this.$util.tan(this.formData.angle / 2) * 4;
+                    this.rules.dierceng.value = this.formData.xiebian + (this.formData.biangao + this.formData.cengjianju) * this.$util.tan(this.formData.angle / 2) * 2;
+                    /**
+                     * 3、 已知第三层起弯:
+                     * 第一层起弯 = 起弯+(边高+层间数)×tan(角度°÷2)×4
+                     * 第二层起弯 = 起弯+(边高+层间数)×tan(角度°÷2)×2
+                     */
+                    if(this.formData.qiwan){
+                        this.rules.diyicengqiwan.value = this.formData.qiwan + (this.formData.biangao + this.formData.cengjianju) * this.$util.tan(this.formData.angle / 2) * 4
+                        this.rules.diercengqiwan.value = this.formData.qiwan + (this.formData.biangao + this.formData.cengjianju) * this.$util.tan(this.formData.angle / 2) * 2;
+                    }
+                }
+                this.rules.slice.value = this.formData.biangao * this.$util.tan(this.formData.angle / 2) * 2;
+
+                this.roundRules();
+                this.$u.toast("请参考计算示意图")
+            },
+            roundRules(){
+                for (const itemKey in this.rules) {
+                    this.rules[itemKey].value = this.$util.round(this.rules[itemKey].value,2);
+                }
+            },
+            initRules(){
+                for (const itemKey in this.rules) {
+                    this.rules[itemKey].value = "";
+                    this.rules[itemKey].show = true;
+                }
+            },
+            handleSelectCeng(e){
+                this.formData.ceng = e[0].value;
+                this.cengTxt = e[0].label;
+            },
+            handleClear() {
+                this.initRules();
+                this.formData = {
+                    angle: '',
+                    cengjianju: '',
+                    ceng: '1',
+                    xiebian: '',
+                    biangao: '',
+                    qiwan: '',
+                };
+            }
+        },
+        computed:{
+            showResult(){
+                let validate = [];
+                for (const itemKey in this.rules) {
+                    validate.push(this.rules[itemKey].value);
+                }
+                return this.$util.checkArrayNotNullNumber(validate)
+            }
+        }
+    }
+</script>
+
+<style lang="scss" scoped>
+    @import "@/static/css/math.scss";
+    .diyiceng{
+        left: 150rpx;
+    }
+    .diyicengqiwan{
+        left: 320rpx;
+    }
+    .slice1{
+        top: 170rpx;
+        left: 240rpx;
+    }
+    .slice2{
+        top: 170rpx;
+        left: 445rpx;
+    }
+    .dierceng{
+        top: 190rpx;
+        left: 170rpx;
+    }
+    .diercengqiwan{
+        top: 190rpx;
+        left: 340rpx;
+    }
+    .slice3{
+        top: 350rpx;
+        left: 260rpx;
+    }
+    .slice4{
+        top: 350rpx;
+        left: 445rpx;
+    }
+    .disanceng{
+        top: 360rpx;
+        left: 170rpx;
+    }
+    .disancengqiwan{
+        top: 360rpx;
+        left: 340rpx;
+    }
+    .slice5{
+        top: 520rpx;
+        left: 280rpx;
+    }
+    .slice6{
+        top: 520rpx;
+        left: 445rpx;
+    }
+    .cengjianju1{
+        top: 200rpx;
+    }
+    .cengjianju2{
+        top: 370rpx;
+    }
+</style>

+ 137 - 0
pages/formula/guoqiangwan.vue

xqd
@@ -0,0 +1,137 @@
+<template>
+    <view class="math-container">
+        <view class="header">
+            <u-image width="100%" height="300rpx" :src="mathImgs[name].big" mode="aspectFit" @click="handleBigImage"></u-image>
+        </view>
+        <view class="main dir-top-wrap cross-center">
+            <view class="form">
+                <u-form-item label="底边(cm)" label-width="160">
+                    <u-input type="number" v-model="formData.dibian" placeholder="请输入左侧高"/>
+                </u-form-item>
+                <u-form-item label="边高(cm)" label-width="160">
+                    <u-input type="number" v-model="formData.biangao" placeholder="请输入边高"/>
+                </u-form-item>
+                <u-form-item label="长(cm)" label-width="160">
+                    <u-input type="number" v-model="formData.length" placeholder="请输入长"/>
+                </u-form-item>
+            </view>
+            <view class="btn-group main-left cross-center">
+                <u-button :ripple="true" @click="handelCalc" :custom-style="{backgroundColor: $u.color['mainBgColor'],color:'#ffffff',width: '260rpx',marginRight:'30rpx'}">计算</u-button>
+                <u-button :ripple="true" @click="handleClear">清空</u-button>
+            </view>
+        </view>
+        <div class="footer">
+            <view class="result dir-top-wrap cross-center" v-if="showResult">
+                <view v-for="item in rules" v-if="item.value && item.show">
+                    <text>{{item.name}}=</text>{{item.value}}{{item.unit}}
+                    <text v-if="item.isHalf">{{$util.round(item.value/2,2)}}{{item.unit}}</text>
+                </view>
+            </view>
+            <view class="title">计算图</view>
+            <view class="calc-img">
+                <text class="qiwan top">{{rules.qiwan.value?rules.qiwan.value:'**'}}{{rules.qiwan.unit}}</text>
+                <text class="xiebian top">{{rules.xiebian.value?rules.xiebian.value:'**'}}{{rules.xiebian.unit}}</text>
+                <text class="slice1 bottom">{{rules.slice.value?rules.slice.value:'**'}}{{rules.slice.unit}}</text>
+                <text class="slice2 bottom">{{rules.slice.value?rules.slice.value:'**'}}{{rules.slice.unit}}</text>
+                <u-image width="100%" height="300rpx" :src="mathImgs[name].calc" mode="aspectFit"></u-image>
+            </view>
+            <view class="title">切割图</view>
+            <u-image width="100%" height="300rpx" :src="mathImgs[name].slice" mode="aspectFit"></u-image>
+
+        </div>
+    </view>
+</template>
+
+<script>
+    import mathImgs from "@/core/math-imgs"
+    // 万能公式
+    export default {
+        data() {
+            return {
+                name: 'guoqiangwan',
+                mathImgs: mathImgs,
+                formData: {
+                    dibian: '',
+                    biangao: '',
+                    lenght: '',
+                },
+                // 用来验证 输入
+                rules: {
+                    xiebian: {name:'斜边', value:'',unit:'cm',show: true},
+                    slice:{name:'切口', value:'',unit:'cm',show: true,isHalf:true},
+                    qiwan:{name:'起弯', value:'',unit:'cm',show: false},
+                }
+            }
+        },
+        methods: {
+            handleBigImage(){
+                uni.previewImage({
+                    urls: [mathImgs[this.name].big],
+                });
+            },
+            handelCalc(){
+                let validate = [this.formData.dibian,this.formData.biangao,this.formData.lenght]
+                if(!this.$util.checkArrayNotNullNumber(validate)){
+                    this.$u.toast('至少输入两个参数');
+                    return
+                }
+                this.initRules();
+                /**
+                 1、 斜边=底边×1.414
+                 2、 切口=边高×0.828
+                 */
+                this.rules.xiebian.value = this.formData.dibian * 1.414
+                this.rules.slice.value = this.formData.biangao * 0.828
+                // 1、 起弯=长-底边
+                if(this.formData.length){
+                    this.rules.qiwan.value = this.formData.length - this.formData.dibian
+                }
+                this.roundRules();
+                this.$u.toast("请参考计算示意图")
+            },
+            roundRules(){
+                for (const itemKey in this.rules) {
+                    this.rules[itemKey].value = this.$util.round(this.rules[itemKey].value,2);
+                }
+            },
+            initRules(){
+                for (const itemKey in this.rules) {
+                    this.rules[itemKey].value = "";
+                }
+            },
+            handleClear() {
+                this.initRules();
+                this.formData = {
+                    dibian: '',
+                    biangao: '',
+                    lenght: '',
+                };
+            }
+        },
+        computed:{
+            showResult(){
+                let validate = [];
+                for (const itemKey in this.rules) {
+                    validate.push(this.rules[itemKey].value);
+                }
+                return this.$util.checkArrayNotNullNumber(validate)
+            }
+        }
+    }
+</script>
+
+<style lang="scss" scoped>
+    @import "@/static/css/math.scss";
+    .qiwan{
+        left: 100rpx;
+    }
+    .xiebian{
+        left: 320rpx;
+    }
+    .slice1{
+        left: 220rpx;
+    }
+    .slice2{
+        left: 430rpx;
+    }
+</style>

+ 184 - 0
pages/formula/guozhuwan.vue

xqd
@@ -0,0 +1,184 @@
+<template>
+    <view class="math-container">
+        <view class="header">
+            <u-image width="100%" height="300rpx" :src="mathImgs[name].big" mode="aspectFit" @click="handleBigImage"></u-image>
+        </view>
+        <view class="main dir-top-wrap cross-center">
+            <view class="form">
+                <u-form-item label="左高(cm)" label-width="160">
+                    <u-input type="number" v-model="formData.leftHeight" placeholder="请输入左侧高"/>
+                </u-form-item>
+                <u-form-item label="右高(cm)" label-width="160">
+                    <u-input type="number" v-model="formData.rightHeight" placeholder="请输入右侧高"/>
+                </u-form-item>
+                <u-form-item label="左角度(°)" label-width="160">
+                    <u-input type="number" v-model="formData.leftAngle" placeholder="请输入左角度"/>
+                </u-form-item>
+                <u-form-item label="右角度(°)" label-width="160">
+                    <u-input type="number" v-model="formData.rightAngle" placeholder="请输入右角度"/>
+                </u-form-item>
+                <u-form-item label="边高(cm)" label-width="160">
+                    <u-input type="number" v-model="formData.biangao" placeholder="请输入边高"/>
+                </u-form-item>
+                <u-form-item label="柱宽(cm)" label-width="160">
+                    <u-input type="number" v-model="formData.zhukuan" placeholder="请输入柱宽"/>
+                </u-form-item>
+                <u-form-item label="长(cm)" label-width="160">
+                    <u-input type="number" v-model="formData.length" placeholder="选填"/>
+                </u-form-item>
+            </view>
+            <view class="btn-group main-left cross-center">
+                <u-button :ripple="true" @click="handelCalc" :custom-style="{backgroundColor: $u.color['mainBgColor'],color:'#ffffff',width: '260rpx',marginRight:'30rpx'}">计算</u-button>
+                <u-button :ripple="true" @click="handleClear">清空</u-button>
+            </view>
+        </view>
+        <div class="footer">
+            <view class="result dir-top-wrap cross-center" v-if="showResult">
+                <view v-for="item in rules" v-if="item.value && item.show">
+                    <text>{{item.name}}=</text>{{item.value}}{{item.unit}}
+                    <text v-if="item.isHalf">{{$util.round(item.value/2,2)}}{{item.unit}}</text>
+                </view>
+            </view>
+            <view class="title">计算图</view>
+            <view class="calc-img">
+                <text class="qiwan top">{{rules.qiwan.value?rules.qiwan.value:'**'}}{{rules.qiwan.unit}}</text>
+                <text class="leftxiebian top">{{rules.leftxiebian.value?rules.leftxiebian.value:'**'}}{{rules.leftxiebian.unit}}</text>
+                <text class="rightxiebian top">{{rules.rightxiebian.value?rules.rightxiebian.value:'**'}}{{rules.rightxiebian.unit}}</text>
+                <text class="leftxiekou1 bottom">{{rules.leftxiekou.value?rules.leftxiekou.value:'**'}}{{rules.leftxiekou.unit}}</text>
+                <text class="leftxiekou2 bottom">{{rules.leftxiekou.value?rules.leftxiekou.value:'**'}}{{rules.leftxiekou.unit}}</text>
+                <text class="zhukuan">{{formData.zhukuan?$util.round(formData.zhukuan,2):'**'}}cm</text>
+                <text class="rightxiekou1 bottom">{{rules.rightxiekou.value?rules.rightxiekou.value:'**'}}{{rules.rightxiekou.unit}}</text>
+                <text class="rightxiekou2 bottom">{{rules.rightxiekou.value?rules.rightxiekou.value:'**'}}{{rules.rightxiekou.unit}}</text>
+                <u-image width="100%" height="300rpx" :src="mathImgs[name].calc" mode="aspectFit"></u-image>
+            </view>
+            <view class="title">切割图</view>
+            <u-image width="100%" height="300rpx" :src="mathImgs[name].slice" mode="aspectFit"></u-image>
+
+        </div>
+    </view>
+</template>
+
+<script>
+    import mathImgs from "@/core/math-imgs"
+    // 万能公式
+    export default {
+        data() {
+            return {
+                name: 'guozhuwan',
+                mathImgs: mathImgs,
+                formData: {
+                    leftHeight: '',
+                    rightHeight: '',
+                    leftAngle: '',
+                    rightAngle: '',
+                    biangao: '',
+                    zhukuan: '',
+                    lenght: '',
+                },
+                // 用来验证 输入
+                rules: {
+                    leftxiebian: {name:'左斜边', value:'',unit:'cm',show: true},
+                    leftxiekou:{name:'左切口', value:'',unit:'cm',show: true,isHalf:true},
+                    rightxiebian: {name:'右斜边', value:'',unit:'cm',show: true},
+                    rightxiekou:{name:'右切口', value:'',unit:'cm',show: true,isHalf:true},
+                    qiwan:{name:'起弯', value:'',unit:'cm',show: false},
+                }
+            }
+        },
+        methods: {
+            handleBigImage(){
+                uni.previewImage({
+                    urls: [mathImgs[this.name].big],
+                });
+            },
+            handelCalc(){
+                if(!this.formData.leftHeight ||  !this.formData.rightHeight || !this.formData.leftAngle || !this.formData.rightAngle
+                    || !this.formData.biangao || !this.formData.zhukuan ){
+                    this.$u.toast('请填写全部参数');
+                    return
+                }
+                if( this.formData.leftAngle > 90 ||  this.formData.rightHeight > 90){
+                    this.$u.toast('角度不能大于90');
+                    return
+                }
+                this.initRules();
+                /**
+                 * 1、 左斜边=左高×csc左角度°
+                 2、 左切口=边高×tan(左角度°÷2)×2
+                 3、 右斜边=右高×csc右角度°
+                 4、 右切口=边高×tan(右角度°÷2)×2
+                 */
+                this.rules.leftxiebian.value = this.formData.leftHeight * this.$util.csc(this.formData.leftAngle)
+                this.rules.leftxiekou.value = this.formData.biangao * this.$util.tan(this.formData.leftAngle / 2) * 2
+                this.rules.rightxiebian.value = this.formData.rightHeight * this.$util.csc(this.formData.rightAngle)
+                this.rules.rightxiekou.value =  this.formData.biangao * this.$util.tan(this.formData.rightAngle / 2) * 2
+                // 1、 起弯=长-左高×cot左角度°
+                if(this.formData.length){
+                    this.rules.qiwan.value = this.formData.length - this.formData.leftHeight * this.$util.cot(this.formData.leftAngle)
+                }
+                this.roundRules();
+                this.$u.toast("请参考计算示意图")
+            },
+            roundRules(){
+                for (const itemKey in this.rules) {
+                    this.rules[itemKey].value = this.$util.round(this.rules[itemKey].value,2);
+                }
+            },
+            initRules(){
+                for (const itemKey in this.rules) {
+                    this.rules[itemKey].value = "";
+                }
+            },
+            handleClear() {
+                this.initRules();
+                this.formData = {
+                    leftHeight: '',
+                    rightHeight: '',
+                    leftAngle: '',
+                    rightAngle: '',
+                    biangao: '',
+                    zhukuan: '',
+                    lenght: '',
+                };
+            }
+        },
+        computed:{
+            showResult(){
+                let validate = [];
+                for (const itemKey in this.rules) {
+                    validate.push(this.rules[itemKey].value);
+                }
+                return this.$util.checkArrayNotNullNumber(validate)
+            }
+        }
+    }
+</script>
+
+<style lang="scss" scoped>
+    @import "@/static/css/math.scss";
+    .qiwan{
+        left: 80rpx;
+    }
+    .leftxiebian{
+        left: 200rpx;
+    }
+    .rightxiebian{
+        left: 440rpx;
+    }
+    .leftxiekou1{
+        left: 160rpx;
+    }
+    .leftxiekou2{
+        left: 260rpx;
+    }
+    .zhukuan{
+        top: 200rpx;
+        left: 320rpx;
+    }
+    .rightxiekou1{
+        left: 380rpx;
+    }
+    .rightxiekou2{
+        left: 490rpx;
+    }
+</style>

+ 205 - 0
pages/formula/normal.vue

xqd
@@ -0,0 +1,205 @@
+<template>
+    <view class="math-container">
+        <view class="header">
+            <u-image width="100%" height="300rpx" :src="mathImgs[name].big" mode="aspectFit" @click="handleBigImage"></u-image>
+        </view>
+        <view class="main dir-top-wrap cross-center">
+            <view class="form">
+                <u-form-item label="高(cm)" label-width="160">
+                    <u-input type="number" v-model="formData.height" :placeholder="heightInput.placeholder" :disabled="heightInput.disabled"/>
+                </u-form-item>
+                <u-form-item label="角度(°)" label-width="160">
+                    <u-input type="number" v-model="formData.angle" :placeholder="angleInput.placeholder" :disabled="angleInput.disabled"/>
+                </u-form-item>
+                <u-form-item label="底边(cm)" label-width="160">
+                    <u-input type="number" v-model="formData.dibian" :placeholder="dibianInput.placeholder" :disabled="dibianInput.disabled"/>
+                </u-form-item>
+                <u-form-item label="斜边(cm)" label-width="160">
+                    <u-input type="number" v-model="formData.xiebian" :placeholder="xiebianInput.placeholder" :disabled="xiebianInput.disabled"/>
+                </u-form-item>
+                <u-form-item label="边高(cm)" label-width="160">
+                    <u-input type="number" v-model="formData.biangao" placeholder="选填"/>
+                </u-form-item>
+                <u-form-item label="长(cm)" label-width="160">
+                    <u-input type="number" v-model="formData.length" placeholder="选填"/>
+                </u-form-item>
+            </view>
+            <view class="btn-group main-left cross-center">
+                <u-button :ripple="true" @click="handelCalc" :custom-style="{backgroundColor: $u.color['mainBgColor'],color:'#ffffff',width: '260rpx',marginRight:'30rpx'}">计算</u-button>
+                <u-button :ripple="true" @click="handleClear">清空</u-button>
+            </view>
+        </view>
+        <div class="footer">
+            <view class="result dir-top-wrap cross-center" v-if="showResult">
+                <view v-for="item in rules" v-if="item.value && item.show">
+                    <text>{{item.name}}=</text>{{item.value}}{{item.unit}}
+                    <text v-if="item.isHalf">{{$util.round(item.value/2,2)}}{{item.unit}}</text>
+                </view>
+            </view>
+            <view class="title">计算图</view>
+            <view class="calc-img">
+                <text class="qiwan top">{{rules.qiwan.value?rules.qiwan.value:'**'}}{{rules.qiwan.unit}}</text>
+                <text class="xiebian top">{{rules.xiebian.value?rules.xiebian.value:'**'}}{{rules.xiebian.unit}}</text>
+                <text class="slice1 bottom">{{rules.slice.value?rules.slice.value:'**'}}{{rules.slice.unit}}</text>
+                <text class="slice2 bottom">{{rules.slice.value?rules.slice.value:'**'}}{{rules.slice.unit}}</text>
+                <u-image width="100%" height="300rpx" :src="mathImgs[name].calc" mode="aspectFit"></u-image>
+            </view>
+            <view class="title">切割图</view>
+            <u-image width="100%" height="300rpx" :src="mathImgs[name].slice" mode="aspectFit"></u-image>
+
+        </div>
+    </view>
+</template>
+
+<script>
+    import mathImgs from "@/core/math-imgs"
+    // 万能公式
+    export default {
+        data() {
+            return {
+                name: 'normal',
+                mathImgs: mathImgs,
+                formData: {
+                    height: '',
+                    angle: '',
+                    dibian: '',
+                    xiebian: '',
+                    biangao: '',
+                    lenght: '',
+                },
+                // 用来验证 输入
+                rules: {
+                    height: {name:'高', value:'',unit:'cm',show: true},
+                    angle:{name:'角度', value:'',unit:'°',show: true},
+                    dibian: {name:'底边', value:'',unit:'cm',show: true},
+                    xiebian:{name:'斜边', value:'',unit:'cm',show: true},
+                    slice:{name:'切口', value:'',unit:'cm',show: true,isHalf: true},
+                    qiwan:{name:'起弯', value:'',unit:'cm',show: false},
+                }
+            }
+        },
+        methods: {
+            handleBigImage(){
+                uni.previewImage({
+                    urls: [mathImgs[this.name].big],
+                });
+            },
+            handelCalc(){
+                if( this.formData.angle &&  this.formData.angle > 90){
+                    this.$u.toast('角度不能大于90');
+                    return
+                }
+                this.initRules();
+                /*1:已知高、角度:底边=高÷tan角度°		斜边=高÷sin角度°*/
+                if(this.formData.height && this.formData.angle){
+                    this.rules.dibian.value = this.formData.height/this.$util.tan(this.formData.angle);
+                    this.rules.xiebian.value = this.formData.height/this.$util.sin(this.formData.angle);
+                }
+                /* 2:已知高、底边:tan角度°=高÷底边		斜边²=高²+底边²*/
+                if(this.formData.height && this.formData.dibian){
+                    this.rules.angle.value = this.$util.atan(this.formData.height/this.formData.dibian);
+                    this.rules.xiebian.value = Math.sqrt(Math.pow(this.formData.height,2) + Math.pow(this.formData.dibian,2));
+                }
+                /* 3:已知角度、底边:高=底边×tan角度°		斜边=底边÷cos角度°*/
+                if(this.formData.angle && this.formData.dibian){
+                    this.rules.height.value = this.formData.dibian*this.$util.tan(this.formData.angle);
+                    this.rules.xiebian.value = this.formData.dibian/this.$util.cos(this.formData.angle);
+                }
+                /* 4:已知角度、斜边:高=斜边×sin角度°		底边=斜边×cos角度°*/
+                if(this.formData.angle && this.formData.xiebian){
+                    this.rules.height.value = this.formData.xiebian/this.$util.sin(this.formData.angle);
+                    this.rules.dibian.value = this.formData.xiebian/this.$util.cos(this.formData.angle);
+                }
+                /* 5:已知底边、斜边:高²=斜边²-底边²		cos角度°=底边÷斜边*/
+                if(this.formData.dibian && this.formData.xiebian){
+                    this.rules.height.value = Math.sqrt(Math.pow(this.formData.xiebian,2) - Math.pow(this.formData.dibian,2));
+                    this.rules.angle.value = this.$util.acos(this.formData.dibian/this.formData.xiebian);
+                }
+                /*选填(输入边高计算出切口):1、 切口=边高×tan(角度°÷2)×2 2、 起弯=长-底边*/
+                if(this.formData.biangao){
+                    let angle = this.rules.angle.value ? this.rules.angle.value : this.formData.angle
+                    this.rules.slice.value = this.formData.biangao * this.$util.tan(angle / 2) * 2;
+                }
+                if(this.formData.length){
+                    let dibian = this.formData.dibian ? this.formData.dibian : this.rules.dibian.value
+                    this.rules.qiwan.value = this.formData.length - dibian;
+                }
+                this.roundRules();
+                this.$u.toast("请参考计算示意图")
+            },
+            roundRules(){
+                for (const itemKey in this.rules) {
+                    this.rules[itemKey].value = this.$util.round(this.rules[itemKey].value,2);
+                }
+            },
+            initRules(){
+                for (const itemKey in this.rules) {
+                    this.rules[itemKey].value = "";
+                }
+            },
+            getInputItem(item){
+                let validate = [];
+                for (const itemKey in this.rules) {
+                    if(item !== itemKey && typeof this.formData[itemKey] !== "undefined")
+                        validate.push(this.formData[itemKey]);
+                }
+                let placeholder = `请输入${this.rules[item].name}`;
+                let disabled = false;
+                if(this.$util.checkArrayNotNullNumber(validate)){
+                    disabled = true;
+                    placeholder = '无需输入';
+                }
+                return {placeholder: placeholder,disabled:disabled};
+
+            },
+            handleClear() {
+                this.initRules();
+                this.formData = {
+                    height: '',
+                    angle: '',
+                    dibian: '',
+                    xiebian: '',
+                    biangao: '',
+                    lenght: '',
+                };
+            }
+        },
+        computed:{
+            heightInput(){
+                return this.getInputItem('height')
+            },
+            angleInput(){
+                return this.getInputItem('angle')
+            },
+            dibianInput(){
+                return this.getInputItem('dibian')
+            },
+            xiebianInput(){
+                return this.getInputItem('xiebian')
+            },
+            showResult(){
+                let validate = [];
+                for (const itemKey in this.rules) {
+                    validate.push(this.rules[itemKey].value);
+                }
+                return this.$util.checkArrayNotNullNumber(validate)
+            }
+        }
+    }
+</script>
+
+<style lang="scss" scoped>
+    @import "@/static/css/math.scss";
+    .qiwan{
+        left: 100rpx;
+    }
+    .xiebian{
+        left: 300rpx;
+    }
+    .slice1{
+        left: 210rpx;
+    }
+    .slice2{
+        left: 430rpx;
+    }
+</style>

+ 155 - 0
pages/formula/rightAngle22.vue

xqd
@@ -0,0 +1,155 @@
+<template>
+    <view class="math-container">
+        <view class="header">
+            <u-image width="100%" height="300rpx" :src="mathImgs[name].big" mode="aspectFit" @click="handleBigImage"></u-image>
+        </view>
+        <view class="main dir-top-wrap cross-center">
+            <view class="form">
+                <u-form-item label="高/底边(cm)" label-width="180">
+                    <u-input type="number" v-model="formData.dibian" placeholder="请输入高或底边"/>
+                </u-form-item>
+                <u-form-item label="边高(cm)" label-width="180">
+                    <u-input type="number" v-model="formData.biangao" placeholder="请输入边高"/>
+                </u-form-item>
+                <u-form-item label="长(cm)" label-width="180">
+                    <u-input type="number" v-model="formData.length" placeholder="选填"/>
+                </u-form-item>
+            </view>
+            <view class="btn-group main-left cross-center">
+                <u-button :ripple="true" @click="handelCalc" :custom-style="{backgroundColor: $u.color['mainBgColor'],color:'#ffffff',width: '260rpx',marginRight:'30rpx'}">计算</u-button>
+                <u-button :ripple="true" @click="handleClear">清空</u-button>
+            </view>
+        </view>
+        <div class="footer">
+            <view class="result dir-top-wrap cross-center" v-if="showResult">
+                <view v-for="item in rules" v-if="item.value && item.show">
+                    <text>{{item.name}}</text>{{item.value}}{{item.unit}}
+                    <text v-if="item.isHalf">{{$util.round(item.value/2,2)}}{{item.unit}}</text>
+                </view>
+            </view>
+            <view class="title">计算图</view>
+            <view class="calc-img">
+                <text class="qiwan top">{{rules.qiwan.value?rules.qiwan.value:'**'}}{{rules.qiwan.unit}}</text>
+                <text class="xiebian1 top">{{rules.xiebian.value?rules.xiebian.value:'**'}}{{rules.xiebian.unit}}</text>
+                <text class="xiebian2 top">{{rules.xiebian.value?rules.xiebian.value:'**'}}{{rules.xiebian.unit}}</text>
+                <text class="xiebian3 top">{{rules.xiebian.value?rules.xiebian.value:'**'}}{{rules.xiebian.unit}}</text>
+                <text class="slice1 bottom">{{rules.slice.value?rules.slice.value:'**'}}{{rules.slice.unit}}</text>
+                <text class="slice2 bottom">{{rules.slice.value?rules.slice.value:'**'}}{{rules.slice.unit}}</text>
+                <text class="slice3 bottom">{{rules.slice.value?rules.slice.value:'**'}}{{rules.slice.unit}}</text>
+                <text class="slice4 bottom">{{rules.slice.value?rules.slice.value:'**'}}{{rules.slice.unit}}</text>
+                <u-image width="100%" height="300rpx" :src="mathImgs[name].calc" mode="aspectFit"></u-image>
+            </view>
+            <view class="title">切割图</view>
+            <u-image width="100%" height="300rpx" :src="mathImgs[name].slice" mode="aspectFit"></u-image>
+
+        </div>
+    </view>
+</template>
+
+<script>
+    import mathImgs from "@/core/math-imgs"
+    // 万能公式
+    export default {
+        data() {
+            return {
+                name: 'rightAngle22',
+                mathImgs: mathImgs,
+                formData: {
+                    dibian: '',
+                    biangao: '',
+                    length: '',
+                },
+                // 用来验证 输入
+                rules: {
+                    xiebian: {name:'斜边', value:'',unit:'cm',show: true},
+                    slice: {name:'切口', value:'',unit:'cm',show: true,isHalf:true},
+                    qiwan: {name:'起弯', value:'',unit:'cm',show: false},
+                }
+            }
+        },
+        methods: {
+            handleBigImage(){
+                uni.previewImage({
+                    urls: [mathImgs[this.name].big],
+                });
+            },
+            handelCalc(){
+                this.initRules();
+                /**
+                 1、斜边=边高×0.504
+                 2、切口=边高×0.398
+                 */
+                this.formData.dibian = parseFloat(this.formData.dibian)
+                this.formData.biangao = parseFloat(this.formData.biangao)
+                if(this.formData.dibian && this.formData.biangao){
+                    this.rules.xiebian.value = this.formData.dibian * 0.504;
+                    this.rules.slice.value = this.formData.biangao * 0.398
+                }else{
+                    this.$u.toast('请输入两个参数');
+                }
+                // 计算起弯 长-边高-底边
+                if(this.formData.length){
+                    this.rules.qiwan.value = this.formData.length - this.formData.biangao - this.formData.dibian
+                }
+                this.roundRules();
+                this.roundRules();
+                this.$u.toast("请参考计算示意图")
+            },
+            roundRules(){
+                for (const itemKey in this.rules) {
+                    this.rules[itemKey].value = this.$util.round(this.rules[itemKey].value,2);
+                }
+            },
+            initRules(){
+                for (const itemKey in this.rules) {
+                    this.rules[itemKey].value = "";
+                }
+            },
+            handleClear() {
+                this.initRules();
+                this.formData = {
+                    dibian: '',
+                    biangao: '',
+                    length: '',
+                };
+            }
+        },
+        computed:{
+            showResult(){
+                let validate = [];
+                for (const itemKey in this.rules) {
+                    validate.push(this.rules[itemKey].value);
+                }
+                return this.$util.checkArrayNotNullNumber(validate,2)
+            }
+        }
+    }
+</script>
+
+<style lang="scss" scoped>
+    @import "@/static/css/math.scss";
+    .qiwan{
+        left: 80rpx;
+    }
+    .xiebian1{
+        left: 200rpx;
+    }
+    .xiebian2{
+        left: 310rpx;
+    }
+    .xiebian3{
+        left: 420rpx;
+    }
+    .slice1{
+        left: 150rpx;
+    }
+    .slice2{
+        left: 260rpx;
+    }
+    .slice3{
+        left: 370rpx;
+    }
+    .slice4{
+        left: 480rpx;
+    }
+</style>

+ 146 - 0
pages/formula/rightAngle30.vue

xqd
@@ -0,0 +1,146 @@
+<template>
+    <view class="math-container">
+        <view class="header">
+            <u-image width="100%" height="300rpx" :src="mathImgs[name].big" mode="aspectFit" @click="handleBigImage"></u-image>
+        </view>
+        <view class="main dir-top-wrap cross-center">
+            <view class="form">
+                <u-form-item label="底边(cm)" label-width="160">
+                    <u-input type="number" v-model="formData.dibian" placeholder="请输入底边"/>
+                </u-form-item>
+                <u-form-item label="边高(cm)" label-width="160">
+                    <u-input type="number" v-model="formData.biangao" placeholder="请输入边高"/>
+                </u-form-item>
+                <u-form-item label="长(cm)" label-width="160">
+                    <u-input type="number" v-model="formData.length" placeholder="选填"/>
+                </u-form-item>
+            </view>
+            <view class="btn-group main-left cross-center">
+                <u-button :ripple="true" @click="handelCalc" :custom-style="{backgroundColor: $u.color['mainBgColor'],color:'#ffffff',width: '260rpx',marginRight:'30rpx'}">计算</u-button>
+                <u-button :ripple="true" @click="handleClear">清空</u-button>
+            </view>
+        </view>
+        <div class="footer">
+            <view class="result dir-top-wrap cross-center" v-if="showResult">
+                <view v-for="item in rules" v-if="item.value && item.show">
+                    <text>{{item.name}}</text>{{item.value}}{{item.unit}}
+                    <text v-if="item.isHalf">{{$util.round(item.value/2,2)}}{{item.unit}}</text>
+                </view>
+            </view>
+            <view class="title">计算图</view>
+            <view class="calc-img">
+                <text class="qiwan top">{{rules.qiwan.value?rules.qiwan.value:'**'}}{{rules.qiwan.unit}}</text>
+                <text class="xiebian1 top">{{rules.xiebian.value?rules.xiebian.value:'**'}}{{rules.xiebian.unit}}</text>
+                <text class="xiebian2 top">{{rules.xiebian.value?rules.xiebian.value:'**'}}{{rules.xiebian.unit}}</text>
+                <text class="slice1 bottom">{{rules.slice.value?rules.slice.value:'**'}}{{rules.slice.unit}}</text>
+                <text class="slice2 bottom">{{rules.slice.value?rules.slice.value:'**'}}{{rules.slice.unit}}</text>
+                <text class="slice3 bottom">{{rules.slice.value?rules.slice.value:'**'}}{{rules.slice.unit}}</text>
+                <u-image width="100%" height="300rpx" :src="mathImgs[name].calc" mode="aspectFit"></u-image>
+            </view>
+            <view class="title">切割图</view>
+            <u-image width="100%" height="300rpx" :src="mathImgs[name].slice" mode="aspectFit"></u-image>
+
+        </div>
+    </view>
+</template>
+
+<script>
+    import mathImgs from "@/core/math-imgs"
+    // 万能公式
+    export default {
+        data() {
+            return {
+                name: 'rightAngle30',
+                mathImgs: mathImgs,
+                formData: {
+                    dibian: '',
+                    biangao: '',
+                    length: '',
+                },
+                // 用来验证 输入
+                rules: {
+                    xiebian: {name:'斜边', value:'',unit:'cm',show: true},
+                    slice: {name:'切口', value:'',unit:'cm',show: true,isHalf: true},
+                    qiwan: {name:'起弯', value:'',unit:'cm',show: false},
+                }
+            }
+        },
+        methods: {
+            handleBigImage(){
+                uni.previewImage({
+                    urls: [mathImgs[this.name].big],
+                });
+            },
+            handelCalc(){
+                this.initRules();
+                /**
+                 1、斜边=边高×0.732
+                 2、切口=边高×0.536
+                 */
+                this.formData.dibian = parseFloat(this.formData.dibian)
+                this.formData.biangao = parseFloat(this.formData.biangao)
+                if(this.formData.dibian && this.formData.biangao){
+                    this.rules.xiebian.value = this.formData.dibian * 0.732;
+                    this.rules.slice.value = this.formData.biangao * 0.536
+                }else{
+                    this.$u.toast('请输入两个参数');
+                }
+                // 计算起弯 长-边高-底边
+                if(this.formData.length){
+                    this.rules.qiwan.value = this.formData.length - this.formData.biangao - this.formData.dibian
+                }
+                this.roundRules();
+                this.$u.toast("请参考计算示意图")
+            },
+            roundRules(){
+                for (const itemKey in this.rules) {
+                    this.rules[itemKey].value = this.$util.round(this.rules[itemKey].value,2);
+                }
+            },
+            initRules(){
+                for (const itemKey in this.rules) {
+                    this.rules[itemKey].value = "";
+                }
+            },
+            handleClear() {
+                this.initRules();
+                this.formData = {
+                    dibian: '',
+                    biangao: '',
+                    length: '',
+                };
+            }
+        },
+        computed:{
+            showResult(){
+                let validate = [];
+                for (const itemKey in this.rules) {
+                    validate.push(this.rules[itemKey].value);
+                }
+                return this.$util.checkArrayNotNullNumber(validate,2)
+            }
+        }
+    }
+</script>
+
+<style lang="scss" scoped>
+    @import "@/static/css/math.scss";
+    .qiwan{
+        left: 80rpx;
+    }
+    .xiebian1{
+        left: 240rpx;
+    }
+    .xiebian2{
+        left: 390rpx;
+    }
+    .slice1{
+        left: 160rpx;
+    }
+    .slice2{
+        left: 320rpx;
+    }
+    .slice3{
+        left: 490rpx;
+    }
+</style>

+ 138 - 0
pages/formula/rightAngle45.vue

xqd
@@ -0,0 +1,138 @@
+<template>
+    <view class="math-container">
+        <view class="header">
+            <u-image width="100%" height="300rpx" :src="mathImgs[name].big" mode="aspectFit" @click="handleBigImage"></u-image>
+        </view>
+        <view class="main dir-top-wrap cross-center">
+            <view class="form">
+                <u-form-item label="底边(cm)" label-width="160">
+                    <u-input type="number" v-model="formData.dibian" placeholder="请输入底边"/>
+                </u-form-item>
+                <u-form-item label="边高(cm)" label-width="160">
+                    <u-input type="number" v-model="formData.biangao" placeholder="请输入边高"/>
+                </u-form-item>
+                <u-form-item label="长(cm)" label-width="160">
+                    <u-input type="number" v-model="formData.length" placeholder="选填"/>
+                </u-form-item>
+            </view>
+            <view class="btn-group main-left cross-center">
+                <u-button :ripple="true" @click="handelCalc" :custom-style="{backgroundColor: $u.color['mainBgColor'],color:'#ffffff',width: '260rpx',marginRight:'30rpx'}">计算</u-button>
+                <u-button :ripple="true" @click="handleClear">清空</u-button>
+            </view>
+        </view>
+        <div class="footer">
+            <view class="result dir-top-wrap cross-center" v-if="showResult">
+                <view v-for="item in rules" v-if="item.value && item.show">
+                    <text>{{item.name}}</text>{{item.value}}{{item.unit}}
+                    <text v-if="item.isHalf">{{$util.round(item.value/2,2)}}{{item.unit}}</text>
+                </view>
+            </view>
+            <view class="title">计算图</view>
+            <view class="calc-img">
+                <text class="qiwan top">{{rules.qiwan.value?rules.qiwan.value:'**'}}{{rules.qiwan.unit}}</text>
+                <text class="xiebian top">{{rules.xiebian.value?rules.xiebian.value:'**'}}{{rules.xiebian.unit}}</text>
+                <text class="slice1 bottom">{{rules.slice.value?rules.slice.value:'**'}}{{rules.slice.unit}}</text>
+                <text class="slice2 bottom">{{rules.slice.value?rules.slice.value:'**'}}{{rules.slice.unit}}</text>
+                <u-image width="100%" height="300rpx" :src="mathImgs[name].calc" mode="aspectFit"></u-image>
+            </view>
+            <view class="title">切割图</view>
+            <u-image width="100%" height="300rpx" :src="mathImgs[name].slice" mode="aspectFit"></u-image>
+
+        </div>
+    </view>
+</template>
+
+<script>
+    import mathImgs from "@/core/math-imgs"
+    // 万能公式
+    export default {
+        data() {
+            return {
+                name: 'rightAngle45',
+                mathImgs: mathImgs,
+                formData: {
+                    dibian: '',
+                    biangao: '',
+                    length: '',
+                },
+                // 用来验证 输入
+                rules: {
+                    xiebian: {name:'斜边', value:'',unit:'cm',show: true},
+                    slice: {name:'切口', value:'',unit:'cm',show: true,isHalf: true},
+                    qiwan: {name:'起弯', value:'',unit:'cm',show: false},
+                }
+            }
+        },
+        methods: {
+            handleBigImage(){
+                uni.previewImage({
+                    urls: [mathImgs[this.name].big],
+                });
+            },
+            handelCalc(){
+                this.initRules();
+                /**
+                 * 1、 斜边=边高×1.414
+                 * 2、 切口=边高×0.828
+                 */
+                this.formData.dibian = parseFloat(this.formData.dibian)
+                this.formData.biangao = parseFloat(this.formData.biangao)
+                if(this.formData.dibian && this.formData.biangao){
+                    this.rules.xiebian.value = this.formData.dibian * 1.414;
+                    this.rules.slice.value = this.formData.biangao * 0.828
+                }else{
+                    this.$u.toast('请输入两个参数');
+                }
+                // 计算起弯 长-边高-底边
+                if(this.formData.length){
+                    this.rules.qiwan.value = this.formData.length - this.formData.biangao - this.formData.dibian
+                }
+                this.roundRules();
+                this.$u.toast("请参考计算示意图")
+            },
+            roundRules(){
+                for (const itemKey in this.rules) {
+                    this.rules[itemKey].value = this.$util.round(this.rules[itemKey].value,2);
+                }
+            },
+            initRules(){
+                for (const itemKey in this.rules) {
+                    this.rules[itemKey].value = "";
+                }
+            },
+            handleClear() {
+                this.initRules();
+                this.formData = {
+                    dibian: '',
+                    biangao: '',
+                    length: '',
+                };
+            }
+        },
+        computed:{
+            showResult(){
+                let validate = [];
+                for (const itemKey in this.rules) {
+                    validate.push(this.rules[itemKey].value);
+                }
+                return this.$util.checkArrayNotNullNumber(validate,2)
+            }
+        }
+    }
+</script>
+
+<style lang="scss" scoped>
+    @import "@/static/css/math.scss";
+    .qiwan{
+        left: 100rpx;
+    }
+    .xiebian{
+        left: 320rpx;
+    }
+    .slice1{
+        left: 210rpx;
+    }
+    .slice2{
+        left: 430rpx;
+    }
+</style>

+ 131 - 0
pages/formula/rightAngleHorizontal.vue

xqd
@@ -0,0 +1,131 @@
+<template>
+    <view class="math-container">
+        <view class="header">
+            <u-image width="100%" height="300rpx" :src="mathImgs[name].big" mode="aspectFit" @click="handleBigImage"></u-image>
+        </view>
+        <view class="main dir-top-wrap cross-center">
+            <view class="form">
+                <u-form-item label="边高(cm)" label-width="160">
+                    <u-input type="number" v-model="formData.biangao" placeholder="请输入边高"/>
+                </u-form-item>
+                <u-form-item label="底宽(cm)" label-width="160">
+                    <u-input type="number" v-model="formData.dikuan" placeholder="请输入底宽"/>
+                </u-form-item>
+            </view>
+            <view class="btn-group main-left cross-center">
+                <u-button :ripple="true" @click="handelCalc" :custom-style="{backgroundColor: $u.color['mainBgColor'],color:'#ffffff',width: '260rpx',marginRight:'30rpx'}">计算</u-button>
+                <u-button :ripple="true" @click="handleClear">清空</u-button>
+            </view>
+        </view>
+        <div class="footer">
+            <view class="result dir-top-wrap cross-center" v-if="showResult">
+                <view v-for="item in rules" v-if="item.value && item.show">
+                    <text>{{item.name}}</text>{{item.value}}{{item.unit}}
+                </view>
+            </view>
+            <view class="title">计算图</view>
+            <view class="calc-img">
+                <text class="biangao1 top">{{formData.biangao?parseFloat(formData.biangao):'**'}}cm</text>
+                <text class="biangao2 top">{{formData.biangao?parseFloat(formData.biangao):'**'}}cm</text>
+                <text class="dikuan1 top">{{formData.dikuan?parseFloat(formData.dikuan):'**'}}cm</text>
+                <text class="dikuan2 top">{{formData.dikuan?parseFloat(formData.dikuan):'**'}}cm</text>
+                <text class="result bottom">{{rules.result.value?rules.result.value:'**'}}{{rules.result.unit}}</text>
+                <u-image width="100%" height="300rpx" :src="mathImgs[name].calc" mode="aspectFit"></u-image>
+            </view>
+            <view class="title">切割图</view>
+            <u-image width="100%" height="300rpx" :src="mathImgs[name].slice" mode="aspectFit"></u-image>
+
+        </div>
+    </view>
+</template>
+
+<script>
+    import mathImgs from "@/core/math-imgs"
+    // 万能公式
+    export default {
+        data() {
+            return {
+                name: 'rightAngleHorizontal',
+                mathImgs: mathImgs,
+                formData: {
+                    biangao: '',
+                    dikuan: '',
+                },
+                // 用来验证 输入
+                rules: {
+                    result: {name:'', value:'',unit:'cm',show: true},
+                }
+            }
+        },
+        methods: {
+            handleBigImage(){
+                uni.previewImage({
+                    urls: [mathImgs[this.name].big],
+                });
+            },
+            handelCalc(){
+                this.initRules();
+                /*结果=边高×1.414*/
+                if(this.formData.biangao && this.formData.dikuan){
+                    this.rules.result.value = this.formData.biangao * 1.414;
+                }else{
+                    this.$u.toast('请输入两个参数');
+                }
+                this.roundRules();
+                this.$u.toast("请参考计算示意图")
+            },
+            roundRules(){
+                for (const itemKey in this.rules) {
+                    this.rules[itemKey].value = this.$util.round(this.rules[itemKey].value,2);
+                }
+            },
+            initRules(){
+                for (const itemKey in this.rules) {
+                    this.rules[itemKey].value = "";
+                }
+            },
+            handleClear() {
+                this.initRules();
+                this.formData = {
+                    biangao: '',
+                    dikuan: '',
+                };
+            }
+        },
+        computed:{
+            showResult(){
+                let validate = [];
+                for (const itemKey in this.rules) {
+                    validate.push(this.rules[itemKey].value);
+                }
+                return this.$util.checkArrayNotNullNumber(validate,1)
+            }
+        }
+    }
+</script>
+
+<style lang="scss" scoped>
+    @import "@/static/css/math.scss";
+    .biangao1,.biangao2{
+        top: -5rpx !important;
+    }
+    .biangao1{
+        left: 200rpx;
+    }
+    .biangao2{
+        left: 425rpx;
+    }
+    .dikuan1,.dikuan2{
+        top: 50rpx !important;
+    }
+    .dikuan1{
+        left: 280rpx;
+    }
+    .dikuan2{
+        left: 355rpx;
+    }
+    .result{
+        top: 250rpx !important;
+        left: 200rpx;
+    }
+</style>

+ 130 - 0
pages/formula/rightAngleVertical.vue

xqd
@@ -0,0 +1,130 @@
+<template>
+    <view class="math-container">
+        <view class="header">
+            <u-image width="100%" height="300rpx" :src="mathImgs[name].big" mode="aspectFit" @click="handleBigImage"></u-image>
+        </view>
+        <view class="main dir-top-wrap cross-center">
+            <view class="form">
+                <u-form-item label="边高(cm)" label-width="160">
+                    <u-input type="number" v-model="formData.biangao" placeholder="请输入边高"/>
+                </u-form-item>
+                <u-form-item label="底宽(cm)" label-width="160">
+                    <u-input type="number" v-model="formData.dikuan" placeholder="请输入底宽"/>
+                </u-form-item>
+            </view>
+            <view class="btn-group main-left cross-center">
+                <u-button :ripple="true" @click="handelCalc" :custom-style="{backgroundColor: $u.color['mainBgColor'],color:'#ffffff',width: '260rpx',marginRight:'30rpx'}">计算</u-button>
+                <u-button :ripple="true" @click="handleClear">清空</u-button>
+            </view>
+        </view>
+        <div class="footer">
+            <view class="result dir-top-wrap cross-center" v-if="showResult">
+                <view v-for="item in rules" v-if="item.value && item.show">
+                    <text>{{item.name}}</text>{{item.value}}{{item.unit}}
+                </view>
+            </view>
+            <view class="title">计算图</view>
+            <view class="calc-img">
+                <text class="result1 top">{{rules.result1.value?rules.result1.value:'**'}}{{rules.result1.unit}}</text>
+                <text class="result2 top">{{rules.result1.value?rules.result1.value:'**'}}{{rules.result1.unit}}</text>
+                <text class="biangao">{{formData.biangao?parseFloat(formData.biangao):'**'}}cm</text>
+                <text class="result3">{{rules.result2.value?rules.result2.value:'**'}}{{rules.result2.unit}}</text>
+                <u-image width="100%" height="300rpx" :src="mathImgs[name].calc" mode="aspectFit"></u-image>
+            </view>
+            <view class="title">切割图</view>
+            <u-image width="100%" height="300rpx" :src="mathImgs[name].slice" mode="aspectFit"></u-image>
+
+        </div>
+    </view>
+</template>
+
+<script>
+    import mathImgs from "@/core/math-imgs"
+    // 万能公式
+    export default {
+        data() {
+            return {
+                name: 'rightAngleVertical',
+                mathImgs: mathImgs,
+                formData: {
+                    biangao: '',
+                    dikuan: '',
+                },
+                // 用来验证 输入
+                rules: {
+                    result1: {name:'', value:'',unit:'cm',show: true},
+                    result2: {name:'', value:'',unit:'cm',show: true},
+                }
+            }
+        },
+        methods: {
+            handleBigImage(){
+                uni.previewImage({
+                    urls: [mathImgs[this.name].big],
+                });
+            },
+            handelCalc(){
+                this.initRules();
+                /**
+                 * 1、结果一=边高+底宽×0.5
+                 * 2、结果二=底宽×0.707
+                 */
+                this.formData.biangao = parseFloat(this.formData.biangao)
+                this.formData.dikuan = parseFloat(this.formData.dikuan)
+                if(this.formData.biangao && this.formData.dikuan){
+                    this.rules.result1.value = this.formData.biangao + this.formData.dikuan * 0.5;
+                    this.rules.result2.value = this.formData.dikuan * 0.707;
+                }else{
+                    this.$u.toast('请输入两个参数');
+                }
+                this.roundRules();
+                this.$u.toast("请参考计算示意图")
+            },
+            roundRules(){
+                for (const itemKey in this.rules) {
+                    this.rules[itemKey].value = this.$util.round(this.rules[itemKey].value,2);
+                }
+            },
+            initRules(){
+                for (const itemKey in this.rules) {
+                    this.rules[itemKey].value = "";
+                }
+            },
+            handleClear() {
+                this.initRules();
+                this.formData = {
+                    biangao: '',
+                    dikuan: '',
+                };
+            }
+        },
+        computed:{
+            showResult(){
+                let validate = [];
+                for (const itemKey in this.rules) {
+                    validate.push(this.rules[itemKey].value);
+                }
+                return this.$util.checkArrayNotNullNumber(validate,2)
+            }
+        }
+    }
+</script>
+
+<style lang="scss" scoped>
+    @import "@/static/css/math.scss";
+    .result1{
+        left: 240rpx;
+    }
+    .result2{
+        left: 360rpx;
+    }
+    .biangao,.result3{
+        top: 82rpx;
+    }
+    .biangao{
+        left: 275rpx;
+    }
+    .result3{
+        left: 405rpx;
+    }
+</style>

+ 137 - 0
pages/formula/santongwan.vue

xqd
@@ -0,0 +1,137 @@
+<template>
+    <view class="math-container">
+        <view class="header">
+            <u-image width="100%" height="300rpx" :src="mathImgs[name].big" mode="aspectFit" @click="handleBigImage"></u-image>
+        </view>
+        <view class="main dir-top-wrap cross-center">
+            <view class="form">
+                <u-form-item label="次边高(cm)" label-width="160">
+                    <u-input type="number" v-model="formData.cibianheight" placeholder="请输入次底高"/>
+                </u-form-item>
+                <u-form-item label="次底宽(cm)" label-width="160">
+                    <u-input type="number" v-model="formData.cidilength" placeholder="请输入次底宽"/>
+                </u-form-item>
+            </view>
+            <view class="btn-group main-left cross-center">
+                <u-button :ripple="true" @click="handelCalc" :custom-style="{backgroundColor: $u.color['mainBgColor'],color:'#ffffff',width: '260rpx',marginRight:'30rpx'}">计算</u-button>
+                <u-button :ripple="true" @click="handleClear">清空</u-button>
+            </view>
+        </view>
+        <div class="footer">
+            <view class="result dir-top-wrap cross-center" v-if="showResult">
+                <view v-for="item in rules" v-if="item.value && item.show">
+                    <text>{{item.name}}</text>{{item.value}}{{item.unit}}
+                </view>
+            </view>
+            <view class="title">计算图</view>
+            <view class="calc-img">
+                <text class="cibianheight1">{{formData.cibianheight?$util.round(formData.cibianheight,2):'**'}}cm</text>
+                <text class="cidilength">{{formData.cidilength?$util.round(formData.cidilength,2):'**'}}cm</text>
+                <text class="cibianheight2">{{formData.cibianheight?$util.round(formData.cibianheight,2):'**'}}cm</text>
+                <text class="cibianheight3">{{formData.cibianheight?$util.round(formData.cibianheight,2):'**'}}cm</text>
+                <text class="slice1">{{rules.result.value?rules.result.value:'**'}}{{rules.result.unit}}</text>
+                <text class="slice2">{{rules.result.value?rules.result.value:'**'}}{{rules.result.unit}}</text>
+                <u-image width="100%" height="800rpx" :src="mathImgs[name].calc" mode="widthFix"></u-image>
+            </view>
+            <view class="title">切割图</view>
+            <u-image width="100%" height="600rpx" :src="mathImgs[name].slice" mode="widthFix"></u-image>
+
+        </div>
+    </view>
+</template>
+
+<script>
+    import mathImgs from "@/core/math-imgs"
+    // 万能公式
+    export default {
+        data() {
+            return {
+                name: 'santongwan',
+                mathImgs: mathImgs,
+                formData: {
+                    cibianheight: '',
+                    cidilength: '',
+                },
+                // 用来验证 输入
+                rules: {
+                    result: {name:'', value:'',unit:'cm',show: true},
+                }
+            }
+        },
+        methods: {
+            handleBigImage(){
+                uni.previewImage({
+                    urls: [mathImgs[this.name].big],
+                });
+            },
+            handelCalc(){
+                let validate = [this.formData.cibianheight,this.formData.cidilength]
+                if(!this.$util.checkArrayNotNullNumber(validate)){
+                    this.$u.toast('至少输入两个参数');
+                    return
+                }
+                this.initRules();
+                /**
+                 结果=次边高×1.414
+                 */
+                this.rules.result.value = this.formData.cibianheight * 1.414
+                this.roundRules();
+                this.$u.toast("请参考计算示意图")
+            },
+            roundRules(){
+                for (const itemKey in this.rules) {
+                    this.rules[itemKey].value = this.$util.round(this.rules[itemKey].value,2);
+                }
+            },
+            initRules(){
+                for (const itemKey in this.rules) {
+                    this.rules[itemKey].value = "";
+                }
+            },
+            handleClear() {
+                this.initRules();
+                this.formData = {
+                    cibianheight: '',
+                    cidilength: '',
+                };
+            }
+        },
+        computed:{
+            showResult(){
+                let validate = [];
+                for (const itemKey in this.rules) {
+                    validate.push(this.rules[itemKey].value);
+                }
+                return this.$util.checkArrayNotNullNumber(validate,1)
+            }
+        }
+    }
+</script>
+
+<style lang="scss" scoped>
+    @import "@/static/css/math.scss";
+    .cibianheight1{
+        top: 170rpx;
+        left: 80rpx;
+    }
+    .cidilength{
+        top: 290rpx;
+        left: 190rpx;
+    }
+    .cibianheight2{
+        top: 290rpx;
+        left: 310rpx;
+    }
+    .cibianheight3{
+        top: 290rpx;
+        left: 425rpx;
+    }
+    .slice1{
+        top: 360rpx;
+        left: 180rpx;
+    }
+    .slice2{
+        top: 700rpx;
+        left: 160rpx;
+    }
+</style>

+ 137 - 0
pages/formula/santongwanHorizontal.vue

xqd
@@ -0,0 +1,137 @@
+<template>
+    <view class="math-container">
+        <view class="header">
+            <u-image width="100%" height="300rpx" :src="mathImgs[name].big" mode="aspectFit" @click="handleBigImage"></u-image>
+        </view>
+        <view class="main dir-top-wrap cross-center">
+            <view class="form">
+                <u-form-item label="次边高(cm)" label-width="160">
+                    <u-input type="number" v-model="formData.cibianheight" placeholder="请输入次底高"/>
+                </u-form-item>
+                <u-form-item label="次底宽(cm)" label-width="160">
+                    <u-input type="number" v-model="formData.cidilength" placeholder="请输入次底宽"/>
+                </u-form-item>
+            </view>
+            <view class="btn-group main-left cross-center">
+                <u-button :ripple="true" @click="handelCalc" :custom-style="{backgroundColor: $u.color['mainBgColor'],color:'#ffffff',width: '260rpx',marginRight:'30rpx'}">计算</u-button>
+                <u-button :ripple="true" @click="handleClear">清空</u-button>
+            </view>
+        </view>
+        <div class="footer">
+            <view class="result dir-top-wrap cross-center" v-if="showResult">
+                <view v-for="item in rules" v-if="item.value && item.show">
+                    <text>{{item.name}}</text>{{item.value}}{{item.unit}}
+                </view>
+            </view>
+            <view class="title">计算图</view>
+            <view class="calc-img">
+                <text class="cibianheight1">{{formData.cibianheight?$util.round(formData.cibianheight,2):'**'}}cm</text>
+                <text class="cidilength">{{formData.cidilength?$util.round(formData.cidilength,2):'**'}}cm</text>
+                <text class="cibianheight2">{{formData.cibianheight?$util.round(formData.cibianheight,2):'**'}}cm</text>
+                <text class="cibianheight3">{{formData.cibianheight?$util.round(formData.cibianheight,2):'**'}}cm</text>
+                <text class="slice1">{{rules.result.value?rules.result.value:'**'}}{{rules.result.unit}}</text>
+                <text class="slice2">{{rules.result.value?rules.result.value:'**'}}{{rules.result.unit}}</text>
+                <u-image width="100%" height="800rpx" :src="mathImgs[name].calc" mode="widthFix"></u-image>
+            </view>
+            <view class="title">切割图</view>
+            <u-image width="100%" height="800rpx" :src="mathImgs[name].slice" mode="widthFix"></u-image>
+
+        </div>
+    </view>
+</template>
+
+<script>
+    import mathImgs from "@/core/math-imgs"
+    // 万能公式
+    export default {
+        data() {
+            return {
+                name: 'santongwanHorizontal',
+                mathImgs: mathImgs,
+                formData: {
+                    cibianheight: '',
+                    cidilength: '',
+                },
+                // 用来验证 输入
+                rules: {
+                    result: {name:'', value:'',unit:'cm',show: true},
+                }
+            }
+        },
+        methods: {
+            handleBigImage(){
+                uni.previewImage({
+                    urls: [mathImgs[this.name].big],
+                });
+            },
+            handelCalc(){
+                let validate = [this.formData.cibianheight,this.formData.cidilength]
+                if(!this.$util.checkArrayNotNullNumber(validate)){
+                    this.$u.toast('至少输入两个参数');
+                    return
+                }
+                this.initRules();
+                /**
+                 结果=次边高×1.414
+                 */
+                this.rules.result.value = this.formData.cibianheight * 1.414
+                this.roundRules();
+                this.$u.toast("请参考计算示意图")
+            },
+            roundRules(){
+                for (const itemKey in this.rules) {
+                    this.rules[itemKey].value = this.$util.round(this.rules[itemKey].value,2);
+                }
+            },
+            initRules(){
+                for (const itemKey in this.rules) {
+                    this.rules[itemKey].value = "";
+                }
+            },
+            handleClear() {
+                this.initRules();
+                this.formData = {
+                    cibianheight: '',
+                    cidilength: '',
+                };
+            }
+        },
+        computed:{
+            showResult(){
+                let validate = [];
+                for (const itemKey in this.rules) {
+                    validate.push(this.rules[itemKey].value);
+                }
+                return this.$util.checkArrayNotNullNumber(validate,1)
+            }
+        }
+    }
+</script>
+
+<style lang="scss" scoped>
+    @import "@/static/css/math.scss";
+    .cibianheight1{
+        top: 210rpx;
+        left: 10rpx;
+    }
+    .cidilength{
+        top: 290rpx;
+        left: 230rpx;
+    }
+    .cibianheight2{
+        top: 290rpx;
+        left: 340rpx;
+    }
+    .cibianheight3{
+        top: 290rpx;
+        left: 445rpx;
+    }
+    .slice1{
+        top: 390rpx;
+        left: 180rpx;
+    }
+    .slice2{
+        top: 730rpx;
+        left: 160rpx;
+    }
+</style>

+ 162 - 0
pages/formula/slice.vue

xqd
@@ -0,0 +1,162 @@
+<template>
+    <view class="math-container">
+        <view class="header">
+            <u-image width="100%" height="300rpx" :src="mathImgs[name].big" mode="aspectFit" @click="handleBigImage"></u-image>
+        </view>
+        <view class="main dir-top-wrap cross-center">
+            <view class="form">
+                <u-form-item label="边高(cm)" label-width="160">
+                    <u-input type="number" v-model="formData.biangao"  :placeholder="biangaoInput.placeholder" :disabled="biangaoInput.disabled"/>
+                </u-form-item>
+                <u-form-item label="角度(°)" label-width="160">
+                    <u-input type="number" v-model="formData.angle" :placeholder="angleInput.placeholder" :disabled="angleInput.disabled"/>
+                </u-form-item>
+                <u-form-item label="切口(cm)" label-width="160">
+                    <u-input type="number" v-model="formData.slice" :placeholder="sliceInput.placeholder" :disabled="sliceInput.disabled"/>
+                </u-form-item>
+            </view>
+            <view class="btn-group main-left cross-center">
+                <u-button :ripple="true" @click="handelCalc" :custom-style="{backgroundColor: $u.color['mainBgColor'],color:'#ffffff',width: '260rpx',marginRight:'30rpx'}">计算</u-button>
+                <u-button :ripple="true" @click="handleClear">清空</u-button>
+            </view>
+        </view>
+        <div class="footer">
+            <view class="result dir-top-wrap cross-center" v-if="showResult">
+                <view v-for="item in rules" v-if="item.value && item.show">
+                    <text>{{item.name}}=</text>{{item.value}}{{item.unit}}
+                    <text v-if="item.isHalf">{{$util.round(item.value/2,2)}}{{item.unit}}</text>
+                </view>
+            </view>
+            <view class="title">计算图</view>
+            <view class="calc-img">
+                <text class="slice1">{{rules.slice.value?(rules.slice.value/2):'**'}}{{rules.slice.unit}}</text>
+                <text class="slice2">{{rules.slice.value?(rules.slice.value/2):'**'}}{{rules.slice.unit}}</text>
+                <text class="slice3 bottom">{{rules.slice.value?rules.slice.value:'**'}}{{rules.slice.unit}}</text>
+                <u-image width="100%" height="300rpx" :src="mathImgs[name].calc" mode="aspectFit"></u-image>
+            </view>
+            <view class="title">切割图</view>
+            <u-image width="100%" height="300rpx" :src="mathImgs[name].slice" mode="aspectFit"></u-image>
+
+        </div>
+    </view>
+</template>
+
+<script>
+    import mathImgs from "@/core/math-imgs"
+    // 万能公式
+    export default {
+        data() {
+            return {
+                name: 'slice',
+                mathImgs: mathImgs,
+                formData: {
+                    biangao: '',
+                    angle: '',
+                    slice: '',
+                },
+                // 用来验证 输入
+                rules: {
+                    biangao: {name:'边高', value:'',unit:'cm',show: true},
+                    angle:{name:'角度', value:'',unit:'°',show: true},
+                    slice:{name:'切口', value:'',unit:'cm',show: true,isHalf: true},
+                }
+            }
+        },
+        methods: {
+            handleBigImage(){
+                uni.previewImage({
+                    urls: [mathImgs[this.name].big],
+                });
+            },
+            handelCalc(){
+                if( this.formData.angle &&  this.formData.angle > 90){
+                    this.$u.toast('角度不能大于90');
+                    return
+                }
+                this.initRules();
+                /*1、 已知边高、角度:切口=边高×tan(角度°÷2)×2*/
+                if(this.formData.biangao && this.formData.angle){
+                    this.rules.slice.value = this.formData.biangao * this.$util.tan(this.formData.angle / 2) * 2
+                }
+                /*2、 已知边高、切口:tan(角度°÷2)=切口÷(边高×2)*/
+                if(this.formData.biangao && this.formData.slice){
+                    this.rules.angle.value = this.$util.atan(this.formData.slice / (this.formData.biangao * 2)) * 2
+                }
+                /*3、 已知角度、切口:边高=切口÷{tan(角度°÷2)×2}×2*/
+                if(this.formData.angle && this.formData.slice){
+                    this.rules.biangao.value = this.formData.slice * this.$util.tan(this.formData.angle / 2) * 2
+                }
+                this.roundRules();
+                this.$u.toast("请参考计算示意图")
+            },
+            roundRules(){
+                for (const itemKey in this.rules) {
+                    this.rules[itemKey].value = this.$util.round(this.rules[itemKey].value,2);
+                }
+            },
+            initRules(){
+                for (const itemKey in this.rules) {
+                    this.rules[itemKey].value = "";
+                }
+            },
+            getInputItem(item){
+                let validate = [];
+                for (const itemKey in this.rules) {
+                    if(item !== itemKey && typeof this.formData[itemKey] !== "undefined")
+                        validate.push(this.formData[itemKey]);
+                }
+                let placeholder = `请输入${this.rules[item].name}`;
+                let disabled = false;
+                if(this.$util.checkArrayNotNullNumber(validate)){
+                    disabled = true;
+                    placeholder = '无需输入';
+                }
+                return {placeholder: placeholder,disabled:disabled};
+
+            },
+            handleClear() {
+                this.initRules();
+                this.formData = {
+                    biangao: '',
+                    angle: '',
+                    slice: '',
+                };
+            }
+        },
+        computed:{
+            angleInput(){
+                return this.getInputItem('angle')
+            },
+            sliceInput(){
+                return this.getInputItem('slice')
+            },
+            biangaoInput(){
+                return this.getInputItem('biangao')
+            },
+            showResult(){
+                let validate = [];
+                for (const itemKey in this.rules) {
+                    validate.push(this.rules[itemKey].value);
+                }
+                return this.$util.checkArrayNotNullNumber(validate,1)
+            }
+        }
+    }
+</script>
+
+<style lang="scss" scoped>
+    @import "@/static/css/math.scss";
+    .slice1,.slice2{
+        top: -15rpx;
+    }
+    .slice1{
+        left: 270rpx;
+    }
+    .slice2{
+        left: 360rpx;
+    }
+    .slice3{
+        top: 280rpx !important;
+        left: 320rpx;
+    }
+</style>

+ 146 - 0
pages/formula/uphill.vue

xqd
@@ -0,0 +1,146 @@
+<template>
+    <view class="math-container">
+        <view class="header">
+            <u-image width="100%" height="300rpx" :src="mathImgs[name].big" mode="aspectFit" @click="handleBigImage"></u-image>
+        </view>
+        <view class="main dir-top-wrap cross-center">
+            <view class="form">
+                <u-form-item label="高(cm)" label-width="160">
+                    <u-input type="number" v-model="formData.height" placeholder="请输入高"/>
+                </u-form-item>
+                <u-form-item label="角度(°)" label-width="160">
+                    <u-input type="number" v-model="formData.angle" placeholder="请输入角度"/>
+                </u-form-item>
+                <u-form-item label="边高(cm)" label-width="160">
+                    <u-input type="number" v-model="formData.biangao" placeholder="请输入边高"/>
+                </u-form-item>
+                <u-form-item label="长(cm)" label-width="160">
+                    <u-input type="number" v-model="formData.length" placeholder="选填"/>
+                </u-form-item>
+            </view>
+            <view class="btn-group main-left cross-center">
+                <u-button :ripple="true" @click="handelCalc" :custom-style="{backgroundColor: $u.color['mainBgColor'],color:'#ffffff',width: '260rpx',marginRight:'30rpx'}">计算</u-button>
+                <u-button :ripple="true" @click="handleClear">清空</u-button>
+            </view>
+        </view>
+        <div class="footer">
+            <view class="result dir-top-wrap cross-center" v-if="showResult">
+                <view v-for="item in rules" v-if="item.value && item.show">
+                    <text>{{item.name}}=</text>{{item.value}}{{item.unit}}
+                    <text v-if="item.isHalf">{{$util.round(item.value/2,2)}}{{item.unit}}</text>
+                </view>
+            </view>
+            <view class="title">计算图</view>
+            <view class="calc-img">
+                <text class="qiwan top">{{rules.qiwan.value?rules.qiwan.value:'**'}}{{rules.qiwan.unit}}</text>
+                <text class="xiebian top">{{rules.xiebian.value?rules.xiebian.value:'**'}}{{rules.xiebian.unit}}</text>
+                <text class="slice1 bottom">{{rules.slice.value?rules.slice.value:'**'}}{{rules.slice.unit}}</text>
+                <text class="slice2 bottom">{{rules.slice.value?rules.slice.value:'**'}}{{rules.slice.unit}}</text>
+                <u-image width="100%" height="300rpx" :src="mathImgs[name].calc" mode="aspectFit"></u-image>
+            </view>
+            <view class="title">切割图</view>
+            <u-image width="100%" height="300rpx" :src="mathImgs[name].slice" mode="aspectFit"></u-image>
+
+        </div>
+    </view>
+</template>
+
+<script>
+    import mathImgs from "@/core/math-imgs"
+    // 万能公式
+    export default {
+        data() {
+            return {
+                name: 'uphill',
+                mathImgs: mathImgs,
+                formData: {
+                    height: '',
+                    angle: '',
+                    biangao: '',
+                    lenght: '',
+                },
+                // 用来验证 输入
+                rules: {
+                    xiebian:{name:'斜边', value:'',unit:'cm',show: true},
+                    qiwan:{name:'起弯', value:'',unit:'cm',show: true},
+                    slice:{name:'切口', value:'',unit:'cm',show: true,isHalf:true},
+                }
+            }
+        },
+        methods: {
+            handleBigImage(){
+                uni.previewImage({
+                    urls: [mathImgs[this.name].big],
+                });
+            },
+            handelCalc(){
+                this.initRules();
+                if( this.formData.angle &&  this.formData.angle > 90){
+                    this.$u.toast('角度不能大于90');
+                    return
+                }
+                /**
+                 * 1、斜边=高×csc角度°
+                 2、切口=边高×tan(角度°÷2)×2
+                 */
+                if(this.formData.height && this.formData.angle && this.formData.biangao){
+                    this.rules.xiebian.value = this.formData.height * this.$util.csc(this.formData.angle);
+                    this.rules.slice.value = this.formData.biangao * this.$util.tan(this.formData.angle / 2) * 2;
+                }else{
+                    this.$u.toast('请输入三个参数');
+                    return;
+                }
+                //  1、 起弯=长 -(高×cot角度°)
+                if(this.formData.length){
+                    this.rules.qiwan.value = this.formData.length - (this.formData.height * this.$util.cot(this.formData.angle))
+                }
+                this.roundRules();
+                this.$u.toast("请参考计算示意图")
+            },
+            roundRules(){
+                for (const itemKey in this.rules) {
+                    this.rules[itemKey].value = this.$util.round(this.rules[itemKey].value,2);
+                }
+            },
+            initRules(){
+                for (const itemKey in this.rules) {
+                    this.rules[itemKey].value = "";
+                }
+            },
+            handleClear() {
+                this.initRules();
+                this.formData = {
+                    height: '',
+                    angle: '',
+                    biangao: '',
+                    lenght: '',
+                };
+            }
+        },
+        computed:{
+            showResult(){
+                let validate = [];
+                for (const itemKey in this.rules) {
+                    validate.push(this.rules[itemKey].value);
+                }
+                return this.$util.checkArrayNotNullNumber(validate)
+            }
+        }
+    }
+</script>
+
+<style lang="scss" scoped>
+    @import "@/static/css/math.scss";
+    .qiwan{
+        left: 120rpx;
+    }
+    .xiebian{
+        left: 300rpx;
+    }
+    .slice1{
+        left: 210rpx;
+    }
+    .slice2{
+        left: 430rpx;
+    }
+</style>

+ 85 - 0
pages/income/index.vue

xqd
@@ -0,0 +1,85 @@
+<template>
+	<app-layout>
+		<view class="container">
+			<view class="cell-box main-left">
+				<view class="head-img">
+					<u-image
+							width="140"
+							height="140"
+							src="https://xiansin.oss-cn-shenzhen.aliyuncs.com/sange-bridge/images/sample.jpg"
+							shape="circle"></u-image>
+				</view>
+				<view class="nickname main-left cross-center">
+					<text>重置桥架会员</text>
+					<u-image
+							width="35"
+							height="35"
+						src="@/static/images/vip.png"
+						></u-image>
+				</view>
+				<view class="price main-center cross-center">
+					推荐奖励:<text>58</text>元
+				</view>
+			</view>
+			<u-line></u-line>
+			<view class="cell-box main-left">
+				<view class="head-img">
+					<u-image
+							width="140"
+							height="140"
+							src="https://xiansin.oss-cn-shenzhen.aliyuncs.com/sange-bridge/images/sample.jpg"
+							shape="circle"></u-image>
+				</view>
+				<view class="nickname main-left cross-center">
+					<text>重置桥架会员</text>
+					<u-image
+							width="35"
+							height="35"
+							src="@/static/images/vip.png"
+					></u-image>
+				</view>
+				<view class="price main-center cross-center">
+					推荐奖励:<text>58</text>元
+				</view>
+			</view>
+		</view>
+	</app-layout>
+</template>
+
+<script>
+	import appLayout from "@/components/app-layout"
+	export default {
+		components:{
+			appLayout,
+		},
+		data() {
+			return {
+
+			}
+		},
+		methods: {
+
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	.cell-box{
+		padding: 30rpx 0;
+		.head-img{}
+		.nickname{
+			flex: 1;
+			padding: 0 30rpx;
+			color: #333333;
+			font-size: 32rpx;
+			font-weight: 500;
+			text{
+				margin-right: 12rpx;
+			}
+		}
+		.price{
+			color: #666666;
+			font-size: 28rpx;
+		}
+	}
+</style>

+ 74 - 0
pages/index/index.vue

xqd
@@ -0,0 +1,74 @@
+<template>
+	<app-layout>
+		<app-math-card v-for="(item,index) in mathLists"
+					   :key="index"
+					   :cover-image="item.coverImage"
+					   :title="item.title"
+					   :index="index"
+					   :custom-style="{marginLeft:index % 2 === 1 ? '20rpx' : 0}"
+					   @open="handleOpen"
+		>
+		</app-math-card>
+		<u-modal v-model="modal.show"
+				 :show-cancel-button="true"
+				 cancel-text="放弃机会"
+				 cancel-color="#CCCCCC"
+				 confirm-text="购买"
+				 confirm-color="#046E64"
+				 @confirm="handleConfirmBuy"
+		>
+			<view class="slot-content">
+				<view class="title">{{modal.title}}</view>
+				<view class="sub-title">{{modal.subTitle}}</view>
+			</view>
+		</u-modal>
+	</app-layout>
+</template>
+
+<script>
+	import appLayout from "@/components/app-layout"
+	import appMathCard from "@/components/index/app-math-card"
+	import mathLists from "@/core/math-lists.js"
+	export default {
+		components:{
+			appLayout,
+			appMathCard
+		},
+		data() {
+			return {
+				mathLists: mathLists,
+				modal:{
+					show: false,
+					title: '该功能需付费永久使用',
+					subTitle: '做桥架不求人,让你秒变大师',
+				}
+			}
+		},
+		methods: {
+			handleOpen(index){
+				//this.modal.show = true
+				this.$jump({url: mathLists[index].url,type:'to'})
+			},
+			handleConfirmBuy(){
+				this.$jump({url:'/pages/my/member',type:'to'})
+			}
+		},
+	}
+</script>
+
+<style lang="scss" scoped>
+	.slot-content{
+		text-align: center;
+		padding: 36rpx 0;
+		.title{
+			font-size: 36rpx;
+			font-weight: 600;
+			color: #333333;
+			padding: 12rpx 0;
+		}
+		.sub-title{
+			font-size: 28rpx;
+			color: #999999;
+		}
+	}
+</style>

+ 170 - 0
pages/math/index.vue

xqd
@@ -0,0 +1,170 @@
+<template>
+	<app-layout class="app-math">
+		<view class="container">
+			<view class="cover-image">
+				<u-image width="100%" height="400rpx" src="https://xiansin.oss-cn-shenzhen.aliyuncs.com/sange-bridge/images/formula/formula-cover.png"></u-image>
+			</view>
+			<view class="input">
+				<u-input
+						type="number"
+						:custom-style="{backgroundColor: '#ffffff',borderRadius:'10rpx',padding:'8rpx 16rpx'}"
+						placeholder-style="color:#aaa"
+						placeholder="请输入角度(0-90°)"
+						v-model="angle"
+				></u-input>
+			</view>
+			<view class="button">
+				<u-button :ripple="true" @click="handleCalc" shape="circle" :custom-style="{backgroundColor:$u.color['mainBgColor'],color:'#ffffff'}">计算</u-button>
+			</view>
+			<view class="table">
+				<view class="row">{{angle1}}°公示表</view>
+				<view class="row stripe">
+					<text class="txt">高</text>
+					<text class="txt">*</text>
+					<text class="txt">{{value1.toFixed(3)}}</text>
+					<text class="txt">=</text>
+					<text class="txt">底边</text>
+				</view>
+				<view class="row">
+					<text class="txt">高</text>
+					<text class="txt">*</text>
+					<text class="txt">{{value2.toFixed(3)}}</text>
+					<text class="txt">=</text>
+					<text class="txt">斜边</text></view>
+				<view class="row stripe">
+					<text class="txt">边高</text>
+					<text class="txt">*</text>
+					<text class="txt">{{value3.toFixed(3)}}</text>
+					<text class="txt">=</text>
+					<text class="txt">切口</text></view>
+				<view class="row">
+					<text class="txt">底边</text>
+					<text class="txt">*</text>
+					<text class="txt">{{value4.toFixed(3)}}</text>
+					<text class="txt">=</text>
+					<text class="txt">高</text></view>
+				<view class="row stripe">
+					<text class="txt">底边</text>
+					<text class="txt">*</text>
+					<text class="txt">{{value5.toFixed(3)}}</text>
+					<text class="txt">=</text>
+					<text class="txt">斜边</text></view>
+				<view class="row">
+					<text class="txt">斜边</text>
+					<text class="txt">*</text>
+					<text class="txt">{{value6.toFixed(3)}}</text>
+					<text class="txt">=</text>
+					<text class="txt">高</text></view>
+				<view class="row stripe">
+					<text class="txt">斜边</text>
+					<text class="txt">*</text>
+					<text class="txt">{{value7.toFixed(3)}}</text>
+					<text class="txt">=</text>
+					<text class="txt">底边</text></view>
+			</view>
+		</view>
+	</app-layout>
+</template>
+
+<script>
+	import appLayout from "@/components/app-layout"
+	export default {
+		components:{
+			appLayout,
+		},
+		data() {
+			return {
+				angle: '',
+				angle1: 45,
+				value1: 1.0,
+				value2: 1.414,
+				value3: 0.828,
+				value4: 1.0,
+				value5: 1.414,
+				value6: 0.707,
+				value7: 0.707,
+			}
+		},
+		methods: {
+			handleCalc(){
+				if(!this.angle){
+					this.$u.toast('角度不能为0');
+					return;
+				}
+				if(!this.angle > 90){
+					this.$u.toast('角度不能大于90');
+					return;
+				}
+				this.angle1 = this.angle
+				this.value1 = this.$util.cot(this.angle);
+				this.value2 = this.$util.csc(this.angle);
+				this.value3 = this.$util.tan(this.angle/2)*2;
+				this.value4 = this.$util.tan(this.angle);
+				this.value5 = this.$util.sec(this.angle);
+				this.value6 = this.$util.sin(this.angle);
+				this.value7 = this.$util.cos(this.angle);
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	.app-math{
+		.input{
+			margin-top: 32rpx;
+		}
+		.button{
+			margin-top: 32rpx;
+		}
+
+		.table{
+			border-radius: 16rpx;
+			overflow: hidden;
+			text-align: center;
+			margin-top: 32rpx;
+			.row{
+				padding: 16rpx 0;
+				font-size: 36rpx;
+				font-weight: 600;
+				background: #EAEAEA;
+				&.stripe{
+					background: #D8D8D8;
+				}
+				>.txt{
+					font-size: 28rpx;
+					display: inline-block;
+					font-weight: normal;
+					&:nth-child(1){
+						width: 15%;
+					}
+					&:nth-child(2){
+						width: 15%;
+					}
+					&:nth-child(3){
+						width: 15%;
+					}
+					&:nth-child(4){
+						width: 15%;
+					}
+					&:nth-child(5){
+						width: 15%;
+					}
+				}
+			}
+		}
+
+		.version{
+			font-size: 24rpx;
+			margin-top: 32rpx;
+			color: #c6c6c6;
+		}
+		.copyright{
+			padding: 8rpx 0;
+			color: #C6C6C6;
+			font-size: 24rpx;
+			.agreement{
+				color: $main-color;
+			}
+		}
+	}
+</style>

+ 155 - 0
pages/my/index.vue

xqd
@@ -0,0 +1,155 @@
+<template>
+	<app-layout class="app-my">
+		<view class="container">
+			<view class="customer">
+				<view class="bg"></view>
+				<view class="info dir-top-wrap cross-center main-center">
+					<view class="head-img">
+						<u-image
+								width="140"
+								height="140"
+								:src="userData.head_img"
+								shape="circle"></u-image>
+					</view>
+					<view class="nickname">{{userData.nickname}}</view>
+					<view class="mobile">
+						{{userData.phone_num}}
+						<!--#ifdef MP-WEIXIN-->
+						<u-button size="mini"
+								  v-if="!userData.phone_num"
+								  :custom-style="{backgroundColor: '#097268'}"
+								  type="success" open-type="getPhoneNumber" @getphonenumber="getPhoneNumber">获取手机号</u-button>
+						<!--#endif-->
+					</view>
+				</view>
+			</view>
+			<u-cell-group :border="false" class="tab-list">
+				<u-cell-item title="桥架会员" :border-bottom="false" bg-color="#f9f9f9" @click="$jump({url:'/pages/my/member',type:'to'})">
+					<view slot="icon" class="icon bridge"></view>
+				</u-cell-item>
+				<u-cell-item title="会员购买记录" :border-bottom="false" bg-color="#f9f9f9" @click="$jump({url:'/pages/my/member-record',type:'to'})">
+					<view slot="icon" class="icon member"></view>
+				</u-cell-item>
+				<u-cell-item title="分销中心" :border-bottom="false" bg-color="#f9f9f9" @click="$jump({url:'/pages/share/index',type:'to'})">
+					<view slot="icon" class="icon share"></view>
+				</u-cell-item>
+				<u-cell-item title="微信号" :border-bottom="false" bg-color="#f9f9f9">
+					<view slot="icon" class="icon wechat"></view>
+				</u-cell-item>
+				<u-cell-item title="在线客服" :border-bottom="false" bg-color="#f9f9f9">
+					<view slot="icon" class="icon kefu"></view>
+				</u-cell-item>
+			</u-cell-group>
+			<view class="qrcode dir-top-wrap cross-center">
+				<view class="img">
+					<u-image
+							width="260"
+							height="260"
+							src="https://xiansin.oss-cn-shenzhen.aliyuncs.com/sange-bridge/images/sample.jpg"
+					></u-image>
+				</view>
+				<text class="title">扫描二维码联系客服</text>
+			</view>
+		</view>
+	</app-layout>
+</template>
+
+<script>
+	import appLayout from "@/components/app-layout"
+	export default {
+		components:{
+			appLayout,
+		},
+		data() {
+			return {
+				userData: this.vuex_user_data
+			}
+		},
+		methods: {
+			getPhoneNumber(phoneNumber){
+				console.log('-->data',phoneNumber)
+			},
+			getUser(){
+				this.$u.api.userGet().then(data => {
+					console.log('-->data',data)
+				})
+			},
+			getSetting(){
+				this.$u.api.settingGet().then(data => {
+					console.log('-->data',data)
+				})
+			}
+		},
+		onLoad(){
+			this.userData = this.vuex_user_data
+			//this.getUser();
+			this.getSetting();
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	/deep/.u-cell{
+		padding: 22rpx 32rpx !important;
+	}
+	.container{
+		padding: 0;
+		height: 100vh;
+		.customer{
+			position: relative;
+			.bg{
+				position: absolute;
+				background: url("@/static/images/bg.png") no-repeat center;
+				background-size: 100% 100%;
+				height: 300rpx;
+				left: 0;
+				top: 0;
+				width: 750rpx;
+				z-index: 0;
+			}
+			.info{
+				height: 300rpx;
+				position: relative;
+				color: #ffffff;
+				z-index: 1;
+				.nickname{
+					margin: 8rpx 0;
+					font-weight: 600;
+				}
+			}
+		}
+		.tab-list{
+			margin-top: 32rpx;
+			.icon{
+				width: 32rpx;
+				height: 32rpx;
+				background-size: cover;
+				background-repeat: no-repeat;
+				background-position: center;
+				margin-right: 8rpx;
+				&.bridge{
+					background-image: url("@/static/images/icons/bridge.png");
+				}
+				&.kefu{
+					background-image: url("@/static/images/icons/kefu.png");
+				}
+				&.member{
+					background-image: url("@/static/images/icons/member.png");
+				}
+				&.share{
+					background-image: url("@/static/images/icons/share.png");
+				}
+				&.wechat{
+					background-image: url("@/static/images/icons/wechat.png");
+				}
+			}
+		}
+		.qrcode{
+			margin-top: 32rpx;
+			>.title{
+				padding: 8rpx 0;
+				color: #6e6e6e;
+			}
+		}
+	}
+</style>

+ 64 - 0
pages/my/member-record.vue

xqd
@@ -0,0 +1,64 @@
+<template>
+	<app-layout>
+		<view class="container">
+			<view class="cell-box main-left">
+				<view class="head-img">
+					<u-image
+							width="140"
+							height="140"
+							src="https://xiansin.oss-cn-shenzhen.aliyuncs.com/sange-bridge/images/sample.jpg"
+							shape="circle"></u-image>
+				</view>
+				<view class="info dir-top-wrap main-center">
+					<view class="title">重置桥架会员</view>
+					<view class="sub-title">重置时间:2021-07-21 12:23:45</view>
+				</view>
+				<view class="price main-center cross-center">¥58</view>
+			</view>
+		</view>
+	</app-layout>
+</template>
+
+<script>
+	import appLayout from "@/components/app-layout"
+	export default {
+		components:{
+			appLayout,
+		},
+		data() {
+			return {
+
+			}
+		},
+		methods: {
+
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	.cell-box{
+		padding: 30rpx 0;
+		.head-img{}
+		.info{
+			flex: 1;
+			padding: 0 30rpx;
+			.title{
+				color: #333333;
+				font-size: 32rpx;
+				font-weight: 600;
+			}
+			.sub-title{
+				color: #cccccc;
+				font-size: 24rpx;
+				line-height: 60rpx;
+			}
+		}
+		.price{
+			width: 100rpx;
+			color: $main-color;
+			font-weight: 600;
+			font-size: 36rpx;
+		}
+	}
+</style>

+ 135 - 0
pages/my/member.vue

xqd
@@ -0,0 +1,135 @@
+<template>
+	<app-layout>
+		<view class="container">
+			<view class="header">
+				<view class="bg"></view>
+				<view class="title-group dir-top-wrap cross-center main-center">
+					<view class="title">桥架计算和公示表永久使用</view>
+					<view class="sub-title">做桥架不求人,让你秒变大师</view>
+				</view>
+			</view>
+			<view class="main">
+				<view class="bg"></view>
+				<view class="item-group  dir-top-wrap cross-center">
+					<view class="title">开通桥架会员</view>
+					<view class="price">
+						<view class="current">¥58</view>
+						<view class="origin">¥98</view>
+						<view class="desc">永久使用</view>
+					</view>
+					<u-button shape="circle"
+							  type="success"
+							  hover-class="none"
+							  :custom-style="btnStyle"
+					>立即开通</u-button>
+				</view>
+			</view>
+		</view>
+	</app-layout>
+</template>
+
+<script>
+	import appLayout from "@/components/app-layout"
+	export default {
+		components:{
+			appLayout,
+		},
+		data() {
+			return {
+
+			}
+		},
+		methods: {
+
+		},
+		computed:{
+			btnStyle() {
+				return {
+					border:'none',
+					background:'linear-gradient(90deg, rgba(196,146,68,1) 0%, rgba(225,193,117,1) 100%, rgba(225,193,117,1) 100%)',
+					width: '600rpx',
+					padding: '42rpx 0',
+					height: '100rpx',
+					fontSize: '36rpx',
+					fontWeight: 600
+				};
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	.container {
+		padding: 0;
+		.header {
+			.bg {
+				position: absolute;
+				background: url("@/static/images/member-bg.png") no-repeat center;
+				background-size: 100% 100%;
+				height: 300rpx;
+				left: 0;
+				top: 0;
+				width: 750rpx;
+				z-index: 0;
+			}
+			.title-group{
+				position: relative;
+				color: #CFBB8B;
+				height: 220rpx;
+				.title{
+					font-size: 48rpx;
+					font-weight: 600;
+				}
+				.sub-title{
+					margin-top: 8rpx;
+					font-size: 30rpx;
+				}
+			}
+		}
+		.main{
+			position: relative;
+			.bg{
+				position: absolute;
+				background: url("@/static/images/member-bg1.png") no-repeat center;
+				background-size: 100% 100%;
+				left: 50%;
+				transform: translateX(-50%);
+				height: 750rpx;
+				width: 680rpx;
+				z-index: 0;
+			}
+			.item-group{
+				position: relative;
+				height: 750rpx;
+				padding: 32rpx 0;
+				.title{
+					font-size: 42rpx;
+					font-weight: 600;
+					color: #4e5958;
+				}
+			}
+			.price{
+				border: 4rpx solid #C49244;
+				color: #c5923d;
+				padding: 20rpx 60rpx;
+				text-align: center;
+				margin-bottom: 100rpx;
+				margin-top: 100rpx;
+				border-radius: 10rpx;
+				.current{
+					font-weight: 600;
+					font-size: 42rpx;
+					padding: 30rpx 0;
+				}
+				.origin{
+					color: #e3c377;
+					text-decoration: line-through;
+				}
+				.desc{
+					font-size: 40rpx;
+					padding: 30rpx 0;
+				}
+			}
+		}
+	}
+</style>

+ 113 - 0
pages/price/apply.vue

xqd
@@ -0,0 +1,113 @@
+<template>
+	<app-layout>
+		<view class="container dir-top-wrap cross-center">
+			<view class="input-group">
+				<view class="input-item main-left cross-center">
+					<view class="label">姓名</view>
+					<input type="text" placeholder="请输入正确的姓名" v-model="form.name">
+				</view>
+				<view class="input-item main-left cross-center">
+					<view class="label">账号</view>
+					<input type="text" placeholder="请输入正确微信账号" v-model="form.wechat">
+				</view>
+				<view class="input-item main-left cross-center" >
+					<view class="label">备注</view>
+					<input type="text" placeholder="选填" v-model="form.remark">
+				</view>
+			</view>
+
+			<u-button shape="circle"
+					  class="button"
+					  type="success"
+					  hover-class="none"
+					  :custom-style="btnStyle"
+					  @click="handelApply"
+					  :disabled="!form.name || !form.wechat"
+			>提交申请</u-button>
+		</view>
+	</app-layout>
+</template>
+
+<script>
+	import appLayout from "@/components/app-layout"
+	export default {
+		components:{
+			appLayout,
+		},
+		data() {
+			return {
+				form:{
+					name:'',
+					wechat:'',
+					remark:'',
+				}
+			}
+		},
+		methods: {
+			handelApply(){
+				uni.showToast({
+					title: '提交成功',
+					icon: 'success',
+					duration: 1500
+				});
+			}
+		},
+		computed:{
+			btnStyle() {
+				let background = 'linear-gradient(90deg, rgba(196,146,68,1) 0%, rgba(225,193,117,1) 100%, rgba(225,193,117,1) 100%)';
+				if(!this.form.name || !this.form.wechat){
+					background = '#ccc !important'
+				}
+				return {
+					border:'none',
+					background: background,
+					width: '600rpx',
+					padding: '36rpx 0',
+					height: '100rpx',
+					fontSize: '36rpx',
+					fontWeight: 600
+				};
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	.button[disabled]{
+		background: #ccc !important;
+	}
+	.input-group{
+		width: 700rpx;
+		.input-item{
+			position: relative;
+			margin: 32rpx 0;
+			padding: 24rpx 32rpx;
+			font-size: 32rpx;
+			z-index: 1;
+			&:after{
+				content: "";
+				position: absolute;
+				top: 0;
+				left: 0;
+				width: 200%;
+				height: 200%;
+				border: 1px solid #979797;
+				transform: scale(.5);
+				-webkit-transform-origin: 0 0;
+				transform-origin: 0 0;
+				z-index: -1;
+				border-radius: 16rpx;
+			}
+			.label{
+				width: 120rpx;
+				color: #666666;
+				letter-spacing: .15em;
+			}
+			input{
+				font-size: 32rpx;
+				font-weight: 500;
+				color: #333333;
+			}
+		}
+	}
+</style>

+ 84 - 0
pages/price/index.vue

xqd
@@ -0,0 +1,84 @@
+<template>
+	<app-layout>
+		<view class="container dir-top-wrap cross-center">
+			<view class="price-box dir-top-wrap cross-center ">
+				<view class="title">账户剩余金额</view>
+				<view class="price">¥766</view>
+				<view class="desc">微信线下打款</view>
+			</view>
+			<view class="tips dir-top-wrap cross-center">
+				<text>提现说明:提现后将在7个工作日打款</text>
+				<text>客服热线:32429</text>
+			</view>
+			<u-button shape="circle"
+					  type="success"
+					  hover-class="none"
+					  :custom-style="btnStyle"
+					  @click="$jump({url:'/pages/price/apply',type:'to'})"
+			>提现</u-button>
+		</view>
+	</app-layout>
+</template>
+
+<script>
+	import appLayout from "@/components/app-layout"
+	export default {
+		components:{
+			appLayout,
+		},
+		data() {
+			return {
+
+			}
+		},
+		methods: {
+
+		},
+		computed:{
+			btnStyle() {
+				return {
+					border:'none',
+					background:'linear-gradient(90deg, rgba(196,146,68,1) 0%, rgba(225,193,117,1) 100%, rgba(225,193,117,1) 100%)',
+					width: '600rpx',
+					padding: '36rpx 0',
+					height: '100rpx',
+					fontSize: '36rpx',
+					fontWeight: 600
+				};
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	.price-box{
+		background: #fff;
+		width: 650rpx;
+		border-radius: 16rpx;
+		margin: 32rpx 0;
+		padding: 48rpx 0;
+		.title{
+			color: #333333;
+			font-weight: 600;
+			font-size: 30rpx;
+		}
+		.price{
+			color: #c49244;
+			font-size: 64rpx;
+			font-weight: bold;
+			margin: 24rpx 0 12rpx;
+			&:first-letter{
+				font-size: .6em;
+			}
+		}
+		.desc{
+			color: #c49244;
+			font-size: 28rpx;
+		}
+	}
+	.tips{
+		color: #666666;
+		margin: 12rpx 0 52rpx;
+		line-height: 1.8em;
+	}
+</style>

+ 210 - 0
pages/share/index.vue

xqd
@@ -0,0 +1,210 @@
+<template>
+	<app-layout>
+		<view class="container">
+			<view class="header">
+				<view class="bg"></view>
+				<view class="info main-left cross-center">
+					<view class="head-img">
+						<u-image
+								width="150"
+								height="150"
+								src="https://xiansin.oss-cn-shenzhen.aliyuncs.com/sange-bridge/images/sample.jpg"
+								shape="circle"></u-image>
+					</view>
+					<view class="share-box dir-top-wrap">
+						<view class="nickname">会员昵称</view>
+						<view class="recommend">推荐人:大虎</view>
+					</view>
+				</view>
+			</view>
+			<view class="item-group dir-left-wrap">
+				<view class="item-box dir-top-wrap cross-center main-center"
+					  v-for="(item,index) in shareTool"
+					  @click="handleItemClick(index)"
+				>
+					<view class="icon">
+						<u-image
+								width="90"
+								height="90"
+								mode="aspectFit"
+								:src="item.icon"></u-image>
+					</view>
+					<view class="title">{{item.title}}</view>
+				</view>
+			</view>
+		</view>
+
+		<view class="qrcode-modal" :class="{show:qrCodeModal.show}">
+			<view class="content">
+				<view class="qrcode-modal-content dir-top-wrap cross-center">
+					<view class="head-img">
+						<u-image
+								width="150"
+								height="150"
+								src="https://xiansin.oss-cn-shenzhen.aliyuncs.com/sange-bridge/images/sample.jpg"
+								shape="circle"></u-image>
+					</view>
+					<view class="nickname">小星星</view>
+					<u-line class="u-line" border-style="dashed" length="90%"></u-line>
+					<view class="qrcode">
+						<u-image
+								width="350"
+								height="350"
+								src="https://xiansin.oss-cn-shenzhen.aliyuncs.com/sange-bridge/images/sample.jpg"
+						></u-image>
+					</view>
+				</view>
+				<u-button shape="circle"
+						  @click="qrCodeModal.show = false"
+						  hover-class="none"
+						  :custom-style="{
+					background:'linear-gradient(90deg, rgba(4,153,140,1) 0%, rgba(4,153,128,1) 100%, rgba(14,175,160,.5) 100%)',
+					boxShadow: '0 0 20rpx rgba(13,239,250,.3)',
+					borderColor:'none',
+					color: '#ffffff',
+					marginTop: '50rpx'}">确定</u-button>
+			</view>
+		</view>
+	</app-layout>
+</template>
+
+<script>
+	import appLayout from "@/components/app-layout"
+	export default {
+		components:{
+			appLayout,
+		},
+		data() {
+			return {
+				shareTool: [
+					{icon:'/static/images/price.png',title:'佣金',url:'/pages/price/index'},
+					{icon:'/static/images/withdraw.png',title:'佣金提现明细',url:'/pages/withdraw/index'},
+					{icon:'/static/images/list.png',title:'推广列表',url:'/pages/share/list'},
+					{icon:'/static/images/income.png',title:'收益列表',url:'/pages/income/index'},
+					{icon:'/static/images/share.png',title:'推广分享',method: this.handleShare},
+					{icon:'/static/images/qrcode.png',title:'推广二维码',url:'',method: this.handleQrCode},
+				],
+				qrCodeModal:{
+					show: false
+				}
+			}
+		},
+		methods: {
+			handleItemClick(index){
+				let item = this.shareTool[index];
+				if(typeof item.method === "function"){
+					item.method();
+				}else{
+					this.$jump({url:item.url,type:'to'});
+				}
+			},
+			handleQrCode(){
+				this.qrCodeModal.show = true
+			},
+			handleShare(){
+				this.$u.toast('推广分享')
+			}
+		}
+	}
+</script>
+<style lang="scss" scoped>
+	.container{
+		padding: 0;
+		.header{
+			position: relative;
+			padding: 0 30rpx;
+			.bg{
+				position: absolute;
+				background: url("@/static/images/share-bg.png") no-repeat top;
+				background-size: 100% 120%;
+				background-position: 0 -10rpx;
+				height: 240rpx;
+				left: 0;
+				top: 0;
+				width: 750rpx;
+				z-index: 0;
+			}
+			.info{
+				height: 240rpx;
+				position: relative;
+				color: #ffffff;
+				z-index: 1;
+			}
+			.share-box{
+				padding: 0 30rpx;
+				.nickname{
+					font-size: 36rpx;
+					padding: 6rpx 0;
+				}
+			}
+		}
+
+		.item-group{
+			margin: 30rpx 0;
+			.item-box{
+				width: 330rpx;
+				background: #ffffff;
+				border-radius: 16rpx;
+				margin-bottom: 30rpx;
+				margin-left: 30rpx;
+				height: 260rpx;
+				.title{
+					color: #455150;
+					font-size: 28rpx;
+					font-weight: bold;
+					padding: 16rpx 0;
+				}
+			}
+		}
+	}
+	.qrcode-modal{
+		position: fixed;
+		top: 0;
+		left: 0;
+		width: 750rpx;
+		height: 100vh;
+		background: rgba(0,0,0,.35);
+		z-index: 999;
+		display: none;
+		animation: show-modal linear .5s;
+		&.show{
+			display: block;
+		}
+		.content{
+			position: absolute;
+			width: 600rpx;
+			top: 50%;
+			left: 50%;
+			transform: translate(-50%,-50%);
+			.qrcode-modal-content{
+				position: relative;
+				width: 100%;
+				height: 50vh;
+				background: url("@/static/images/share-qrcode-bg.png") no-repeat center;
+				background-size: 100% 100%;
+				.head-img{
+					position: absolute;
+					top: -75rpx;
+				}
+				.nickname{
+					position: absolute;
+					color: #ffffff;
+					top: 11%;
+					font-size: 36rpx;
+				}
+				.u-line{
+					position: absolute;
+					top: 28.55%;
+				}
+				.qrcode{
+					position: absolute;
+					top: 35%;
+				}
+			}
+		}
+	}
+	@keyframes show-modal {
+		0%{opacity: 0;}
+		100%{opacity: 1;}
+	}
+</style>

+ 87 - 0
pages/share/list.vue

xqd
@@ -0,0 +1,87 @@
+<template>
+	<app-layout>
+		<view class="container">
+			<view class="cell-box main-left">
+				<view class="head-img">
+					<u-image
+							width="140"
+							height="140"
+							src="https://xiansin.oss-cn-shenzhen.aliyuncs.com/sange-bridge/images/sample.jpg"
+							shape="circle"></u-image>
+				</view>
+				<view class="nickname dir-top-wrap main-center">
+					<view class="title main-left cross-center">
+						<text>重置桥架会员</text>
+						<u-image
+								width="35"
+								height="35"
+								src="@/static/images/vip.png"
+						></u-image>
+					</view>
+					<view class="sub-title">
+						绑定时间:2021-8-1 15:02:42
+					</view>
+				</view>
+			</view>
+			<u-line></u-line>
+			<view class="cell-box main-left">
+				<view class="head-img">
+					<u-image
+							width="140"
+							height="140"
+							src="https://xiansin.oss-cn-shenzhen.aliyuncs.com/sange-bridge/images/sample.jpg"
+							shape="circle"></u-image>
+				</view>
+				<view class="nickname dir-top-wrap main-center">
+					<view class="title main-left">
+						<text>重置桥架会员</text>
+					</view>
+					<view class="sub-title">
+						绑定时间:2021-8-1 15:02:42
+					</view>
+				</view>
+			</view>
+		</view>
+	</app-layout>
+</template>
+
+<script>
+	import appLayout from "@/components/app-layout"
+	export default {
+		components:{
+			appLayout,
+		},
+		data() {
+			return {
+
+			}
+		},
+		methods: {
+
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	.cell-box{
+		padding: 30rpx 0;
+		.head-img{}
+		.nickname{
+			flex: 1;
+			padding: 0 30rpx;
+			color: #333333;
+			font-size: 32rpx;
+			font-weight: 500;
+			.title{
+				text{
+					margin-right: 12rpx;
+				}
+			}
+			.sub-title{
+				color: #666666;
+				font-size: 28rpx;
+				line-height: 2em;
+			}
+		}
+	}
+</style>

+ 201 - 0
pages/withdraw/index.vue

xqd
@@ -0,0 +1,201 @@
+<template>
+	<app-layout class="c-wrapper">
+		<!--tab-->
+		<view class="u-tabs-box">
+			<u-tabs-swiper ref="tabs"
+						   :active-color="$u.color['mainBgColor']"
+						   inactive-color="#666666"
+						   bar-height="4"
+						   height="90"
+						   :list="tabs"
+						   :current="currentTab"
+						   @change="changeTabs"
+						   :is-scroll="false" swiperWidth="750"
+			></u-tabs-swiper>
+		</view>
+		<!--内容-->
+		<swiper class="swiper-box" :current="currentSwiper" @transition="swiperTransition" @animationfinish="swiperAnimationFinish">
+			<swiper-item class="swiper-item" v-for="(arr,index) in dataList" :key="index">
+				<scroll-view scroll-y
+							 class="scroll-item"
+							 lower-threshold="80"
+							 @scrolltolower="scrollBottom">
+					<view class="page-box">
+						<view class="cell-box">
+							<view class="stamp">
+								<u-image
+										width="150"
+										height="150"
+										mode="aspectFit"
+										src="@/static/images/agree.png"
+								></u-image>
+							</view>
+							<view class="header main-between">
+								<text class="nickname">微信昵称:Z是一颗星</text>
+								<text>待审核</text>
+							</view>
+							<view class="section main-between cross-cente">
+								<view class="info main-left cross-center">
+									<text>提现金额</text>
+									<view class="price">¥76</view>
+								</view>
+								<view class="name cross-center">
+									张三(2390573452730)
+								</view>
+							</view>
+							<view class="footer">
+								<text>申请时间:2021/07/21 15:45</text>
+							</view>
+						</view>
+					</view>
+				</scroll-view>
+			</swiper-item>
+		</swiper>
+	</app-layout>
+</template>
+
+<script>
+	import appLayout from '@/components/app-layout'
+	export default {
+		components:{
+			appLayout
+		},
+		data() {
+			return {
+				currentTab: 0,
+				tabs: [
+					{name: '全部', value: -1},
+					{name: '待审核', value: 1},
+					{name: '已通过', value: 2},
+					{name: '已驳回', value: 3},
+				],
+				currentSwiper: 0,
+				dataList: [],
+				isReload: false,
+			}
+		},
+		onLoad(options){
+			this.getTabsData();
+			if(options.status){
+				const index = options.status - 2;
+				this.currentTab = index;
+				this.currentSwiper = index;
+				this.getList(index, 1);
+			}
+		},
+		onPullDownRefresh(){
+			this.isReload = true;
+			this.getList(this.currentSwiper,1);
+		},
+		methods: {
+			getTabsData(){
+				let _this = this;
+				_this.tabs.forEach((obj, index) => {
+					_this.dataList.push({
+						data: [],
+						status: 'loadmore',
+						currentPage: 1,
+						totalPage: 0,
+						initData: false,
+					});
+				});
+				_this.getList(0, 1);
+			},
+			getList(index, page){
+
+			},
+			// tabs 切换
+			changeTabs(index){
+				this.currentSwiper = index;
+				if(!this.dataList[index].initData){
+					this.getList(index, 1);
+				}
+			},
+			// 滚动到底部
+			scrollBottom(){
+				let index = this.currentTab;
+				let activeData = this.dataList[index];
+				if(activeData.currentPage < activeData.totalPage){
+					activeData.status = 'loading';
+					//this.getList(index,activeData.currentPage + 1);
+				}else{
+					activeData.status = 'nomore';
+				}
+			},
+			// 左右切换 设置滑块位置
+			swiperTransition({ detail: { dx } }){
+				this.$refs.tabs.setDx(dx);
+			},
+			// 切换完成
+			swiperAnimationFinish({ detail: { current } }){
+				this.$refs.tabs.setFinishCurrent(current);
+				this.currentSwiper = current;
+				this.currentTab = current;
+				if(!this.dataList[current].initData){
+					this.getList(current, 1);
+				}
+				if(this.dataList[current].data.length === 0 && this.dataList[current].status !== 'nomore'){
+					this.dataList[current].status = 'loading';
+					//this.getList(current,this.dataList[current].currentPage);
+				}
+			},
+		},
+		computed:{
+		},
+	}
+</script>
+
+<style lang="scss">
+	.swiper-box {
+		flex: 1;
+		.swiper-item {
+			height: 100%;
+		}
+		.scroll-item{
+			height: 100%;
+			width: 100%;
+		}
+	}
+	.page-box{
+		flex: 1;
+		padding: 20rpx 30rpx;
+		.cell-box{
+			position: relative;
+			background: #fff;
+			border-radius: 16rpx;
+			padding: 36rpx 32rpx;
+			.stamp{
+				position: absolute;
+				right: 0;
+				z-index: 9;
+			}
+			.header{
+				.nickname{
+					color: #666666;
+					font-size: 30rpx;
+				}
+			}
+			.section{
+				color: #666666;
+				margin: 36rpx 0;
+				font-size: 30rpx;
+				.info {
+					.price{
+						color: $main-color;
+						font-weight: bold;
+						font-size: 56rpx;
+						margin-left: 10rpx;
+						&:first-letter{
+							font-size: .5em;
+						}
+					}
+				}
+			}
+			.footer{
+				text{
+					color: #999999;
+				}
+			}
+		}
+	}
+</style>

+ 363 - 0
static/common/js/touch-emulator.js

xqd
@@ -0,0 +1,363 @@
+(function(window, document, exportName, undefined) {
+    "use strict";
+
+    var isMultiTouch = false;
+    var multiTouchStartPos;
+    var eventTarget;
+    var touchElements = {};
+
+    // polyfills
+    if(!document.createTouch) {
+        document.createTouch = function(view, target, identifier, pageX, pageY, screenX, screenY, clientX, clientY) {
+            // auto set
+            if(clientX == undefined || clientY == undefined) {
+                clientX = pageX - window.pageXOffset;
+                clientY = pageY - window.pageYOffset;
+            }
+
+            return new Touch(target, identifier, {
+                pageX: pageX,
+                pageY: pageY,
+                screenX: screenX,
+                screenY: screenY,
+                clientX: clientX,
+                clientY: clientY
+            });
+        };
+    }
+
+    if(!document.createTouchList) {
+        document.createTouchList = function() {
+            var touchList = new TouchList();
+            for (var i = 0; i < arguments.length; i++) {
+                touchList[i] = arguments[i];
+            }
+            touchList.length = arguments.length;
+            return touchList;
+        };
+    }
+
+    /**
+     * create an touch point
+     * @constructor
+     * @param target
+     * @param identifier
+     * @param pos
+     * @param deltaX
+     * @param deltaY
+     * @returns {Object} touchPoint
+     */
+    function Touch(target, identifier, pos, deltaX, deltaY) {
+        deltaX = deltaX || 0;
+        deltaY = deltaY || 0;
+
+        this.identifier = identifier;
+        this.target = target;
+        this.clientX = pos.clientX + deltaX;
+        this.clientY = pos.clientY + deltaY;
+        this.screenX = pos.screenX + deltaX;
+        this.screenY = pos.screenY + deltaY;
+        this.pageX = pos.pageX + deltaX;
+        this.pageY = pos.pageY + deltaY;
+    }
+
+    /**
+     * create empty touchlist with the methods
+     * @constructor
+     * @returns touchList
+     */
+    function TouchList() {
+        var touchList = [];
+
+        touchList.item = function(index) {
+            return this[index] || null;
+        };
+
+        // specified by Mozilla
+        touchList.identifiedTouch = function(id) {
+            return this[id + 1] || null;
+        };
+
+        return touchList;
+    }
+
+
+    /**
+     * Simple trick to fake touch event support
+     * this is enough for most libraries like Modernizr and Hammer
+     */
+    function fakeTouchSupport() {
+        var objs = [window, document.documentElement];
+        var props = ['ontouchstart', 'ontouchmove', 'ontouchcancel', 'ontouchend'];
+
+        for(var o=0; o<objs.length; o++) {
+            for(var p=0; p<props.length; p++) {
+                if(objs[o] && objs[o][props[p]] == undefined) {
+                    objs[o][props[p]] = null;
+                }
+            }
+        }
+    }
+
+    /**
+     * we don't have to emulate on a touch device
+     * @returns {boolean}
+     */
+    function hasTouchSupport() {
+        return ("ontouchstart" in window) || // touch events
+               (window.Modernizr && window.Modernizr.touch) || // modernizr
+               (navigator.msMaxTouchPoints || navigator.maxTouchPoints) > 2; // pointer events
+    }
+
+    /**
+     * disable mouseevents on the page
+     * @param ev
+     */
+    function preventMouseEvents(ev) {
+		// 注释启用默认事件
+        // ev.preventDefault();
+        // ev.stopPropagation();
+    }
+
+    /**
+     * only trigger touches when the left mousebutton has been pressed
+     * @param touchType
+     * @returns {Function}
+     */
+    function onMouse(touchType) {
+        return function(ev) {
+            // prevent mouse events
+            preventMouseEvents(ev);
+
+            if (ev.which !== 1) {
+                return;
+            }
+
+            // The EventTarget on which the touch point started when it was first placed on the surface,
+            // even if the touch point has since moved outside the interactive area of that element.
+            // also, when the target doesnt exist anymore, we update it
+            if (ev.type == 'mousedown' || !eventTarget || (eventTarget && !eventTarget.dispatchEvent)) {
+                eventTarget = ev.target;
+            }
+
+            // shiftKey has been lost, so trigger a touchend
+            if (isMultiTouch && !ev.shiftKey) {
+                triggerTouch('touchend', ev);
+                isMultiTouch = false;
+            }
+
+            triggerTouch(touchType, ev);
+
+            // we're entering the multi-touch mode!
+            if (!isMultiTouch && ev.shiftKey) {
+                isMultiTouch = true;
+                multiTouchStartPos = {
+                    pageX: ev.pageX,
+                    pageY: ev.pageY,
+                    clientX: ev.clientX,
+                    clientY: ev.clientY,
+                    screenX: ev.screenX,
+                    screenY: ev.screenY
+                };
+                triggerTouch('touchstart', ev);
+            }
+
+            // reset
+            if (ev.type == 'mouseup') {
+                multiTouchStartPos = null;
+                isMultiTouch = false;
+                eventTarget = null;
+            }
+        }
+    }
+
+    /**
+     * trigger a touch event
+     * @param eventName
+     * @param mouseEv
+     */
+    function triggerTouch(eventName, mouseEv) {
+        var touchEvent = document.createEvent('Event');
+        touchEvent.initEvent(eventName, true, true);
+
+        touchEvent.altKey = mouseEv.altKey;
+        touchEvent.ctrlKey = mouseEv.ctrlKey;
+        touchEvent.metaKey = mouseEv.metaKey;
+        touchEvent.shiftKey = mouseEv.shiftKey;
+
+        touchEvent.touches = getActiveTouches(mouseEv, eventName);
+        touchEvent.targetTouches = getActiveTouches(mouseEv, eventName);
+        touchEvent.changedTouches = getChangedTouches(mouseEv, eventName);
+
+        eventTarget.dispatchEvent(touchEvent);
+    }
+
+    /**
+     * create a touchList based on the mouse event
+     * @param mouseEv
+     * @returns {TouchList}
+     */
+    function createTouchList(mouseEv) {
+        var touchList = new TouchList();
+
+        if (isMultiTouch) {
+            var f = TouchEmulator.multiTouchOffset;
+            var deltaX = multiTouchStartPos.pageX - mouseEv.pageX;
+            var deltaY = multiTouchStartPos.pageY - mouseEv.pageY;
+
+            touchList.push(new Touch(eventTarget, 1, multiTouchStartPos, (deltaX*-1) - f, (deltaY*-1) + f));
+            touchList.push(new Touch(eventTarget, 2, multiTouchStartPos, deltaX+f, deltaY-f));
+        } else {
+            touchList.push(new Touch(eventTarget, 1, mouseEv, 0, 0));
+        }
+
+        return touchList;
+    }
+
+    /**
+     * receive all active touches
+     * @param mouseEv
+     * @returns {TouchList}
+     */
+    function getActiveTouches(mouseEv, eventName) {
+        // empty list
+        if (mouseEv.type == 'mouseup') {
+            return new TouchList();
+        }
+
+        var touchList = createTouchList(mouseEv);
+        if(isMultiTouch && mouseEv.type != 'mouseup' && eventName == 'touchend') {
+            touchList.splice(1, 1);
+        }
+        return touchList;
+    }
+
+    /**
+     * receive a filtered set of touches with only the changed pointers
+     * @param mouseEv
+     * @param eventName
+     * @returns {TouchList}
+     */
+    function getChangedTouches(mouseEv, eventName) {
+        var touchList = createTouchList(mouseEv);
+
+        // we only want to return the added/removed item on multitouch
+        // which is the second pointer, so remove the first pointer from the touchList
+        //
+        // but when the mouseEv.type is mouseup, we want to send all touches because then
+        // no new input will be possible
+        if(isMultiTouch && mouseEv.type != 'mouseup' &&
+            (eventName == 'touchstart' || eventName == 'touchend')) {
+            touchList.splice(0, 1);
+        }
+
+        return touchList;
+    }
+
+    /**
+     * show the touchpoints on the screen
+     */
+    function showTouches(ev) {
+        var touch, i, el, styles;
+
+        // first all visible touches
+        for(i = 0; i < ev.touches.length; i++) {
+            touch = ev.touches[i];
+            el = touchElements[touch.identifier];
+            if(!el) {
+                el = touchElements[touch.identifier] = document.createElement("div");
+                document.body.appendChild(el);
+            }
+
+            styles = TouchEmulator.template(touch);
+            for(var prop in styles) {
+                el.style[prop] = styles[prop];
+            }
+        }
+
+        // remove all ended touches
+        if(ev.type == 'touchend' || ev.type == 'touchcancel') {
+            for(i = 0; i < ev.changedTouches.length; i++) {
+                touch = ev.changedTouches[i];
+                el = touchElements[touch.identifier];
+                if(el) {
+                    el.parentNode.removeChild(el);
+                    delete touchElements[touch.identifier];
+                }
+            }
+        }
+    }
+
+    /**
+     * TouchEmulator initializer
+     */
+    function TouchEmulator() {
+        if (hasTouchSupport()) {
+            return;
+        }
+
+        fakeTouchSupport();
+
+        window.addEventListener("mousedown", onMouse('touchstart'), true);
+        window.addEventListener("mousemove", onMouse('touchmove'), true);
+        window.addEventListener("mouseup", onMouse('touchend'), true);
+
+        window.addEventListener("mouseenter", preventMouseEvents, true);
+        window.addEventListener("mouseleave", preventMouseEvents, true);
+        window.addEventListener("mouseout", preventMouseEvents, true);
+        window.addEventListener("mouseover", preventMouseEvents, true);
+
+        // it uses itself!
+        window.addEventListener("touchstart", showTouches, true);
+        window.addEventListener("touchmove", showTouches, true);
+        window.addEventListener("touchend", showTouches, true);
+        window.addEventListener("touchcancel", showTouches, true);
+    }
+
+    // start distance when entering the multitouch mode
+    TouchEmulator.multiTouchOffset = 75;
+
+    /**
+     * css template for the touch rendering
+     * @param touch
+     * @returns object
+     */
+    TouchEmulator.template = function(touch) {
+        var size = 0;
+        var transform = 'translate('+ (touch.clientX-(size/2)) +'px, '+ (touch.clientY-(size/2)) +'px)';
+        return {
+            position: 'fixed',
+            left: 0,
+            top: 0,
+            background: '#fff',
+            border: 'solid 1px #999',
+            opacity: .6,
+            borderRadius: '100%',
+            height: size + 'px',
+            width: size + 'px',
+            padding: 0,
+            margin: 0,
+            display: 'block',
+            overflow: 'hidden',
+            pointerEvents: 'none',
+            webkitUserSelect: 'none',
+            mozUserSelect: 'none',
+            userSelect: 'none',
+            webkitTransform: transform,
+            mozTransform: transform,
+            transform: transform,
+            zIndex: 100
+        }
+    };
+
+    // export
+    if (typeof define == "function" && define.amd) {
+        define(function() {
+            return TouchEmulator;
+        });
+    } else if (typeof module != "undefined" && module.exports) {
+        module.exports = TouchEmulator;
+    } else {
+        window[exportName] = TouchEmulator;
+    }
+})(window, document, "TouchEmulator");

+ 18 - 0
static/css/common.scss

xqd
@@ -0,0 +1,18 @@
+*{
+    margin: 0;
+    padding: 0;
+    box-sizing: border-box;
+}
+page,
+uni-page-body{
+    height: 100%;
+    background: $main-bg-color;
+}
+
+.container{
+    width: 750rpx;
+    padding: 0 30rpx;
+}
+.image-view-view{
+    background: #fff;
+}

+ 266 - 0
static/css/flex.scss

xqd
@@ -0,0 +1,266 @@
+.dir-left-nowrap {
+    /* 主轴 排列方式从左侧开始 不换行*/
+    display: -webkit-box;
+    display: -webkit-flex;
+    display: flex;
+    -webkit-flex-direction: row;
+    flex-direction: row;
+    flex-wrap: nowrap;
+}
+
+.dir-left-wrap {
+    /* 主轴 排列方式从左侧开始 换行*/
+    display: -webkit-box;
+    display: -webkit-flex;
+    display: flex;
+    -webkit-flex-direction: row;
+    flex-direction: row;
+    flex-wrap: wrap;
+}
+
+.dir-left-wrap-reverse {
+    /* 主轴 排列方式从左侧开始 换行 第一行在下方*/
+    display: -webkit-box;
+    display: -webkit-flex;
+    display: flex;
+    -webkit-flex-direction: row;
+    flex-direction: row;
+    flex-wrap: wrap-reverse;
+}
+
+.dir-right-nowrap {
+    /* 主轴 排列方式从 右侧开始 */
+    display: -webkit-box;
+    display: -webkit-flex;
+    display: flex;
+    -webkit-flex-direction: row-reverse;
+    flex-direction: row-reverse;
+    flex-wrap: nowrap;
+}
+
+.dir-right-wrap {
+    /* 主轴 排列方式从 右侧开始 换行*/
+    display: -webkit-box;
+    display: -webkit-flex;
+    display: flex;
+    -webkit-flex-direction: row;
+    flex-direction: row;
+    flex-wrap: wrap;
+}
+
+.dir-right-wrap-reverse {
+    /* 主轴 排列方式从 右侧开始 换行 第一行在下方*/
+    display: -webkit-box;
+    display: -webkit-flex;
+    display: flex;
+    -webkit-flex-direction: row;
+    flex-direction: row;
+    flex-wrap: wrap-reverse;
+}
+
+.dir-top-nowrap {
+    /* 主轴 排列方式从顶部开始 不换行 */
+    display: -webkit-box;
+    display: -webkit-flex;
+    display: flex;
+    -webkit-box-orient: vertical;
+    -webkit-flex-direction: column;
+    flex-direction: column;
+    flex-wrap: nowrap;
+}
+
+.dir-top-wrap {
+    /* 主轴 排列方式从顶部开始  换行*/
+    display: -webkit-box;
+    display: -webkit-flex;
+    display: flex;
+    -webkit-box-orient: vertical;
+    -webkit-flex-direction: column;
+    flex-direction: column;
+    flex-wrap: wrap;
+}
+
+.dir-top-wrap-reverse {
+    /* 主轴 排列方式从顶部开始 不换行换行 第一行在下方*/
+    display: -webkit-box;
+    display: -webkit-flex;
+    display: flex;
+    -webkit-box-orient: vertical;
+    -webkit-flex-direction: column;
+    flex-direction: column;
+    flex-wrap: wrap-reverse;
+}
+
+.dir-bottom-nowrap {
+    /* 主轴 排列方式从底部开始 */
+    display: -webkit-box;
+    display: -webkit-flex;
+    display: flex;
+    -webkit-flex-direction: column-reverse;
+    flex-direction: column-reverse;
+    flex-wrap: nowrap;
+}
+
+.dir-bottom-wrap {
+    /* 主轴 排列方式从底部开始 不换行 换行*/
+    display: -webkit-box;
+    display: -webkit-flex;
+    display: flex;
+    -webkit-box-orient: vertical;
+    -webkit-flex-direction: column;
+    flex-direction: column;
+    flex-wrap: wrap;
+}
+
+.dir-bottom-wrap-reverse {
+    /* 主轴 排列方式从底部开始 不换行换行 第一行在下方*/
+    display: -webkit-box;
+    display: -webkit-flex;
+    display: flex;
+    -webkit-box-orient: vertical;
+    -webkit-flex-direction: column;
+    flex-direction: column;
+    flex-wrap: wrap-reverse;
+}
+
+.main-left {
+    /* 主轴 左对齐 */
+    display: -webkit-box;
+    display: -webkit-flex;
+    display: flex;
+    -webkit-justify-content: flex-start;
+    justify-content: flex-start;
+}
+
+.main-right {
+    /* 主轴 右对齐 */
+    display: -webkit-box;
+    display: -webkit-flex;
+    display: flex;
+    -webkit-justify-content: flex-end;
+    justify-content: flex-end;
+}
+
+.main-between {
+    /* 主轴 两端对齐 */
+    display: -webkit-box;
+    display: -webkit-flex;
+    display: flex;
+    -webkit-justify-content: space-between;
+    justify-content: space-between;
+}
+
+.main-center {
+    /* 主轴 居中对齐 */
+    display: -webkit-box;
+    display: -webkit-flex;
+    display: flex;
+    -webkit-box-pack: center;
+    -webkit-justify-content: center;
+    -ms-flex-pack: center;
+    justify-content: center;
+}
+
+.main-around {
+    /* 主轴 项目位于各行之前、之间、之后都留有空白的容器内*/
+    display: -webkit-box;
+    display: -webkit-flex;
+    display: flex;
+    -webkit-justify-content: space-around;
+    justify-content: space-around;
+}
+
+.cross-top {
+    /* 交叉轴 起点对齐 */
+    display: -webkit-box;
+    display: -webkit-flex;
+    display: flex;
+    -webkit-align-items: flex-start;
+    align-items: flex-start;
+}
+
+.cross-bottom {
+    /* 交叉轴 终点对齐 */
+    display: -webkit-box;
+    display: -webkit-flex;
+    display: flex;
+    -webkit-box-align: end;
+    -webkit-align-items: flex-end;
+    -ms-flex-align: end;
+    -ms-grid-row-align: flex-end;
+    align-items: flex-end;
+}
+
+.cross-baseline {
+    /* 交叉轴 第一行文字基线对齐 */
+    display: -webkit-box;
+    display: -webkit-flex;
+    display: flex;
+    -webkit-align-items: baseline;
+    align-items: baseline;
+}
+
+.cross-center {
+    /* 交叉轴 居中对齐 */
+    display: -webkit-box;
+    display: -webkit-flex;
+    display: flex;
+    -webkit-align-items: center;
+    align-items: center;
+}
+
+.cross-stretch {
+    /* 交叉轴 高度并排铺满 高度不固定*/
+    display: -webkit-box;
+    display: -webkit-flex;
+    display: flex;
+    -webkit-align-items: stretch;
+    align-items: stretch;
+}
+
+.flex-wrap {
+    /* 流模式 第一行在上方 */
+    display: -webkit-box;
+    display: -webkit-flex;
+    display: flex;
+    -webkit-flex-wrap: wrap;
+    flex-wrap: wrap;
+}
+
+.flex-wrap-reverse {
+    /* 流模式 第一行在下方 */
+    display: -webkit-box;
+    display: -webkit-flex;
+    display: flex;
+    -webkit-flex-wrap: wrap-reverse;
+    flex-wrap: wrap-reverse;
+}
+
+.box-grow-0 {
+    /* flex 子元素固定宽度*/
+    min-width: 0;
+    -webkit-box-flex: 0;
+    -webkit-flex-grow: 0;
+    -ms-flex-positive: 0;
+    flex-grow: 0;
+    -webkit-flex-shrink: 0;
+    -ms-flex-negative: 0;
+    flex-shrink: 0;
+}
+
+.box-grow-1 {
+    /* flex 子元素等分 */
+    min-width: 0;
+    -webkit-box-flex: 1;
+    -webkit-flex-grow: 1;
+    -ms-flex-positive: 1;
+    flex-grow: 1;
+    -webkit-flex-shrink: 1;
+    -ms-flex-negative: 1;
+    flex-shrink: 1;
+}
+
+.inline-flex {
+    display: -webkit-inline-flex;
+    display: inline-flex;
+}

+ 63 - 0
static/css/math.scss

xqd
@@ -0,0 +1,63 @@
+.math-container{
+    padding: 0 30rpx;
+    background: #ffffff;
+    /deep/.u-form-item{
+        padding: 8rpx 0;
+    }
+    .select{
+        width: 100%;
+        display: inline-block;
+        background: #f5f5f5;
+        padding-left: 30rpx;
+        font-weight: bold;
+    }
+    .header{
+        padding: 30rpx 0;
+    }
+    .main{
+        padding: 30rpx 0;
+        .form{
+            width: 100%;
+            margin-bottom: 30rpx;
+        }
+    }
+    .footer{
+        padding: 0 0 80rpx;
+        .result{
+            color: $main-color;
+            font-size: 36rpx;
+            font-weight: 600;
+            line-height: 1.15rem;
+            text{
+                font-size: 24rpx;
+                font-weight: normal;
+                &:last-child{
+                    margin-left: 10rpx;
+                }
+            }
+        }
+        .calc-img{
+            width: 100%;
+            position: relative;
+            text{
+                position: absolute;
+                font-size: 28rpx;
+                font-weight: 600;
+                color: $main-color;
+                z-index: 999;
+                &.top{
+                    top: 20rpx;
+                }
+                &.bottom{
+                    top: 260rpx;
+                }
+            }
+        }
+        .title{
+            text-align: center;
+            font-size: 37rpx;
+            padding: 30rpx 0;
+            font-weight: 600;
+        }
+    }
+}

+ 2 - 0
static/css/variable.scss

xqd
@@ -0,0 +1,2 @@
+$main-color: #097268;
+$main-bg-color: #F9F9F9;

BIN
static/images/agree.png


BIN
static/images/bg.png


BIN
static/images/icons/bridge.png


BIN
static/images/icons/kefu.png


BIN
static/images/icons/member.png


BIN
static/images/icons/share.png


BIN
static/images/icons/tabs/bridge-HL.png


BIN
static/images/icons/tabs/bridge.png


BIN
static/images/icons/tabs/math-HL.png


BIN
static/images/icons/tabs/math.png


BIN
static/images/icons/tabs/my-HL.png


BIN
static/images/icons/tabs/my.png


BIN
static/images/icons/wechat.png


BIN
static/images/income.png


BIN
static/images/list.png


BIN
static/images/member-bg.png


BIN
static/images/member-bg1.png


BIN
static/images/price.png


BIN
static/images/qrcode.png


BIN
static/images/share-bg.png


BIN
static/images/share-qrcode-bg.png


BIN
static/images/share.png


BIN
static/images/vip.png


BIN
static/images/withdraw.png


+ 27 - 0
store/$u.mixin.js

xqd
@@ -0,0 +1,27 @@
+import { mapState } from 'vuex'
+import store from "@/store"
+
+// 尝试将用户在根目录中的store/index.js的vuex的state变量,全部加载到全局变量中
+let $uStoreKey = [];
+try{
+	$uStoreKey = store.state ? Object.keys(store.state) : [];
+}catch(e){
+	
+}
+
+module.exports = {
+	beforeCreate() {
+		// 将vuex方法挂在到$u中
+		// 使用方法为:如果要修改vuex的state中的user.name变量为"史诗" => this.$u.vuex('user.name', '史诗')
+		// 如果要修改vuex的state的version变量为1.0.1 => this.$u.vuex('version', '1.0.1')
+		this.$u.vuex = (name, value) => {
+			this.$store.commit('$uStore', {
+				name,value
+			})
+		}
+	},
+	computed: {
+		// 将vuex的state中的所有变量,解构到全局混入的mixin中
+		...mapState($uStoreKey)
+	}
+}

+ 62 - 0
store/index.js

xqd
@@ -0,0 +1,62 @@
+import Vue from 'vue'
+import Vuex from 'vuex'
+import constant from '../core/constant'
+Vue.use(Vuex)
+const cacheKey = "__LIFE_DATA";
+let lifeData = {};
+
+try{
+	// 尝试获取本地是否存在lifeData变量,第一次启动APP时是不存在的
+	lifeData = uni.getStorageSync(cacheKey);
+}catch(e){
+
+}
+
+// 需要永久存储,且下次APP启动需要取出的,在state中的变量名
+let saveStateKeys = [constant.USER_TOKEN,constant.USER_DATA];
+
+// 保存变量到本地存储中
+const saveStorage = function(key, value){
+	// 判断变量名是否在需要存储的数组中
+	if(saveStateKeys.indexOf(key) != -1) {
+		// 获取本地存储的lifeData对象,将变量添加到对象中
+		let tmp = uni.getStorageSync(cacheKey);
+		// 第一次打开APP,不存在lifeData变量,故放一个{}空对象
+		tmp = tmp ? tmp : {};
+		tmp[key] = value;
+		// 执行这一步后,所有需要存储的变量,都挂载在本地的lifeData对象中
+		uni.setStorageSync(cacheKey, tmp);
+	}
+}
+const store = new Vuex.Store({
+	state: {
+		// token
+		vuex_user_token: lifeData[constant.USER_TOKEN],
+		// 用户信息
+		vuex_user_data: lifeData[constant.USER_DATA],
+	},
+	mutations: {
+		$uStore(state, payload) {
+			// 判断是否多层级调用,state中为对象存在的情况,诸如user.info.score = 1
+			let nameArr = payload.name.split('.');
+			let saveKey = '';
+			let len = nameArr.length;
+			if(len >= 2) {
+				let obj = state[nameArr[0]];
+				for(let i = 1; i < len - 1; i ++) {
+					obj = obj[nameArr[i]];
+				}
+				obj[nameArr[len - 1]] = payload.value;
+				saveKey = nameArr[0];
+			} else {
+				// 单层级变量,在state就是一个普通变量的情况
+				state[payload.name] = payload.value;
+				saveKey = payload.name;
+			}
+			// 保存变量到本地,见顶部函数定义
+			saveStorage(saveKey, state[saveKey])
+		}
+	}
+})
+
+export default store

+ 42 - 0
template.h5.html

xqd
@@ -0,0 +1,42 @@
+<!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="https://cdn.uviewui.com/uview/common/favicon.ico">-->
+		<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
+		<title>
+			<%= htmlWebpackPlugin.options.title %>
+		</title>
+		<!-- 正式发布的时候使用,开发期间不启用。↓ -->
+        <script src="/static/common/js/touch-emulator.js"></script>
+		<script>
+            TouchEmulator();
+		</script>
+        <style>
+            ::-webkit-scrollbar{
+                display: none;
+            }
+        </style>
+        <!-- 正式发布的时候使用,开发期间不启用。↑ -->
+		<script>
+			document.addEventListener('DOMContentLoaded', function() {
+				document.documentElement.style.fontSize = document.documentElement.clientWidth / 20 + 'px'
+			})
+		</script>
+		<link rel="stylesheet" href="<%= BASE_URL %>static/index.css" />
+	</head>
+	<body>
+		<!-- 该文件为 H5 平台的模板 HTML,并非应用入口。 -->
+		<!-- 请勿在此文件编写页面代码或直接运行此文件。 -->
+		<!-- 详见文档:https://uniapp.dcloud.io/collocation/manifest?id=h5-template -->
+		<noscript>
+			<strong>本站点必须要开启JavaScript才能运行</strong>
+		</noscript>
+		<div id="app"></div>
+		<!-- built files will be auto injected -->
+		<script>
+			/*BAIDU_STAT*/
+		</script>
+	</body>
+</html>

+ 10 - 0
uni.scss

xqd
@@ -0,0 +1,10 @@
+/**
+ * 下方引入的为uView UI的集成样式文件,为scss预处理器,其中包含了一些"u-"开头的自定义变量
+ * 使用的时候,请将下面的一行复制到您的uniapp项目根目录的uni.scss中即可
+ * uView自定义的css类名和scss变量,均以"u-"开头,不会造成冲突,请放心使用
+ */
+@import 'uview-ui/theme.scss';
+@import 'static/css/flex.scss';
+@import 'static/css/variable.scss';
+@import 'static/css/common.scss';
+

+ 21 - 0
uview-ui/LICENSE

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

+ 106 - 0
uview-ui/README.md

xqd
@@ -0,0 +1,106 @@
+<p align="center">
+    <img alt="logo" src="https://uviewui.com/common/logo.png" width="120" height="120" style="margin-bottom: 10px;">
+</p>
+<h3 align="center" style="margin: 30px 0 30px;font-weight: bold;font-size:40px;">uView</h3>
+<h3 align="center">多平台快速开发的UI框架</h3>
+
+
+## 说明
+
+uView UI,是[uni-app](https://uniapp.dcloud.io/)生态优秀的UI框架,全面的组件和便捷的工具会让您信手拈来,如鱼得水
+
+## 特性
+
+- 兼容安卓,iOS,微信小程序,H5,QQ小程序,百度小程序,支付宝小程序,头条小程序
+- 60+精选组件,功能丰富,多端兼容,让您快速集成,开箱即用
+- 众多贴心的JS利器,让您飞镖在手,召之即来,百步穿杨
+- 众多的常用页面和布局,让您专注逻辑,事半功倍
+- 详尽的文档支持,现代化的演示效果
+- 按需引入,精简打包体积
+
+
+## 安装
+
+```bash
+# npm方式安装
+npm i uview-ui
+```
+
+## 快速上手
+
+1. `main.js`引入uView库
+```js
+// main.js
+import uView from 'uview-ui';
+Vue.use(uView);
+```
+
+2. `App.vue`引入基础样式(注意style标签需声明scss属性支持)
+```css
+/* App.vue */
+<style lang="scss">
+@import "uview-ui/index.scss";
+</style>
+```
+
+3. `uni.scss`引入全局scss变量文件
+```css
+/* uni.scss */
+@import "uview-ui/theme.scss";
+```
+
+4. `pages.json`配置easycom规则(按需引入)
+
+```js
+// pages.json
+{
+	"easycom": {
+		// npm安装的方式不需要前面的"@/",下载安装的方式需要"@/"
+		// npm安装方式
+		"^u-(.*)": "uview-ui/components/u-$1/u-$1.vue"
+		// 下载安装方式
+		// "^u-(.*)": "@/uview-ui/components/u-$1/u-$1.vue"
+	},
+	// 此为本身已有的内容
+	"pages": [
+		// ......
+	]
+}
+```
+
+请通过[快速上手](https://uviewui.com/components/quickstart.html)了解更详细的内容 
+
+## 使用方法
+配置easycom规则后,自动按需引入,无需`import`组件,直接引用即可。
+
+```html
+<template>
+	<u-button>按钮</u-button>
+</template>
+```
+
+请通过[快速上手](https://uviewui.com/components/quickstart.html)了解更详细的内容 
+
+## 链接
+
+- [官方文档](https://uviewui.com/)
+- [更新日志](https://uviewui.com/components/changelog.html)
+- [升级指南](https://uviewui.com/components/changelog.html)
+- [关于我们](https://uviewui.com/cooperation/about.html)
+
+## 预览
+
+您可以通过**微信**扫码,查看最佳的演示效果。
+<br>
+<br>
+<img src="https://uviewui.com/common/weixin_mini_qrcode.png" width="220" height="220" >
+
+<!-- ## 捐赠uView的研发
+
+uView文档和源码全部开源免费,如果您认为uView帮到了您的开发工作,您可以捐赠uView的研发工作,捐赠无门槛,哪怕是一杯可乐也好(相信这比打赏主播更有意义)。
+
+<img src="https://uviewui.com/common/wechat.png" width="220" >
+<img style="margin-left: 100px;" src="https://uviewui.com/common/alipay.png" width="220" >
+ -->
+## 版权信息
+uView遵循[MIT](https://en.wikipedia.org/wiki/MIT_License)开源协议,意味着您无需支付任何费用,也无需授权,即可将uView应用到您的产品中。

+ 190 - 0
uview-ui/components/u-action-sheet/u-action-sheet.vue

xqd
@@ -0,0 +1,190 @@
+<template>
+	<u-popup mode="bottom" :border-radius="borderRadius" :popup="false" v-model="value" :maskCloseAble="maskCloseAble"
+	    length="auto" :safeAreaInsetBottom="safeAreaInsetBottom" @close="popupClose" :z-index="uZIndex">
+		<view class="u-tips u-border-bottom" v-if="tips.text" :style="[tipsStyle]">
+			{{tips.text}}
+		</view>
+		<block v-for="(item, index) in list" :key="index">
+			<view 
+				@touchmove.stop.prevent 
+				@tap="itemClick(index)" 
+				:style="[itemStyle(index)]" 
+				class="u-action-sheet-item u-line-1" 
+				:class="[index < list.length - 1 ? 'u-border-bottom' : '']"
+				:hover-stay-time="150"
+			>
+				<text>{{item.text}}</text>
+				<text class="u-action-sheet-item__subtext u-line-1" v-if="item.subText">{{item.subText}}</text>
+			</view>
+		</block>
+		<view class="u-gab" v-if="cancelBtn">
+		</view>
+		<view @touchmove.stop.prevent class="u-actionsheet-cancel u-action-sheet-item" hover-class="u-hover-class"
+		    :hover-stay-time="150" v-if="cancelBtn" @tap="close">{{cancelText}}</view>
+	</u-popup>
+</template>
+
+<script>
+	/**
+	 * actionSheet 操作菜单
+	 * @description 本组件用于从底部弹出一个操作菜单,供用户选择并返回结果。本组件功能类似于uni的uni.showActionSheetAPI,配置更加灵活,所有平台都表现一致。
+	 * @tutorial https://www.uviewui.com/components/actionSheet.html
+	 * @property {Array<Object>} list 按钮的文字数组,见官方文档示例
+	 * @property {Object} tips 顶部的提示文字,见官方文档示例
+	 * @property {String} cancel-text 取消按钮的提示文字
+	 * @property {Boolean} cancel-btn 是否显示底部的取消按钮(默认true)
+	 * @property {Number String} border-radius 弹出部分顶部左右的圆角值,单位rpx(默认0)
+	 * @property {Boolean} mask-close-able 点击遮罩是否可以关闭(默认true)
+	 * @property {Boolean} safe-area-inset-bottom 是否开启底部安全区适配(默认false)
+	 * @property {Number String} z-index z-index值(默认1075)
+	 * @property {String} cancel-text 取消按钮的提示文字
+	 * @event {Function} click 点击ActionSheet列表项时触发
+	 * @event {Function} close 点击取消按钮时触发
+	 * @example <u-action-sheet :list="list" @click="click" v-model="show"></u-action-sheet>
+	 */
+	export default {
+		name: "u-action-sheet",
+		props: {
+			// 点击遮罩是否可以关闭actionsheet
+			maskCloseAble: {
+				type: Boolean,
+				default: true
+			},
+			// 按钮的文字数组,可以自定义颜色和字体大小,字体单位为rpx
+			list: {
+				type: Array,
+				default () {
+					// 如下
+					// return [{
+					// 	text: '确定',
+					// 	color: '',
+					// 	fontSize: ''
+					// }]
+					return [];
+				}
+			},
+			// 顶部的提示文字
+			tips: {
+				type: Object,
+				default () {
+					return {
+						text: '',
+						color: '',
+						fontSize: '26'
+					}
+				}
+			},
+			// 底部的取消按钮
+			cancelBtn: {
+				type: Boolean,
+				default: true
+			},
+			// 是否开启底部安全区适配,开启的话,会在iPhoneX机型底部添加一定的内边距
+			safeAreaInsetBottom: {
+				type: Boolean,
+				default: false
+			},
+			// 通过双向绑定控制组件的弹出与收起
+			value: {
+				type: Boolean,
+				default: false
+			},
+			// 弹出的顶部圆角值
+			borderRadius: {
+				type: [String, Number],
+				default: 0
+			},
+			// 弹出的z-index值
+			zIndex: {
+				type: [String, Number],
+				default: 0
+			},
+			// 取消按钮的文字提示
+			cancelText: {
+				type: String,
+				default: '取消'
+			}
+		},
+		computed: {
+			// 顶部提示的样式
+			tipsStyle() {
+				let style = {};
+				if (this.tips.color) style.color = this.tips.color;
+				if (this.tips.fontSize) style.fontSize = this.tips.fontSize + 'rpx';
+				return style;
+			},
+			// 操作项目的样式
+			itemStyle() {
+				return (index) => {
+					let style = {};
+					if (this.list[index].color) style.color = this.list[index].color;
+					if (this.list[index].fontSize) style.fontSize = this.list[index].fontSize + 'rpx';
+					// 选项被禁用的样式
+					if (this.list[index].disabled) style.color = '#c0c4cc';
+					return style;
+				}
+			},
+			uZIndex() {
+				// 如果用户有传递z-index值,优先使用
+				return this.zIndex ? this.zIndex : this.$u.zIndex.popup;
+			}
+		},
+		methods: {
+			// 点击取消按钮
+			close() {
+				// 发送input事件,并不会作用于父组件,而是要设置组件内部通过props传递的value参数
+				// 这是一个vue发送事件的特殊用法
+				this.popupClose();
+				this.$emit('close');
+			},
+			// 弹窗关闭
+			popupClose() {
+				this.$emit('input', false);
+			},
+			// 点击某一个item
+			itemClick(index) {
+				// disabled的项禁止点击
+				if(this.list[index].disabled) return;
+				this.$emit('click', index);
+				this.$emit('input', false);
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	@import "../../libs/css/style.components.scss";
+
+	.u-tips {
+		font-size: 26rpx;
+		text-align: center;
+		padding: 34rpx 0;
+		line-height: 1;
+		color: $u-tips-color;
+	}
+
+	.u-action-sheet-item {
+		@include vue-flex;;
+		line-height: 1;
+		justify-content: center;
+		align-items: center;
+		font-size: 32rpx;
+		padding: 34rpx 0;
+		flex-direction: column;
+	}
+	
+	.u-action-sheet-item__subtext {
+		font-size: 24rpx;
+		color: $u-tips-color;
+		margin-top: 20rpx;
+	}
+
+	.u-gab {
+		height: 12rpx;
+		background-color: rgb(234, 234, 236);
+	}
+
+	.u-actionsheet-cancel {
+		color: $u-main-color;
+	}
+</style>

+ 256 - 0
uview-ui/components/u-alert-tips/u-alert-tips.vue

xqd
@@ -0,0 +1,256 @@
+<template>
+	<view class="u-alert-tips" v-if="show" :class="[
+		!show ? 'u-close-alert-tips': '',
+		type ? 'u-alert-tips--bg--' + type + '-light' : '',
+		type ? 'u-alert-tips--border--' + type + '-disabled' : '',
+	]" :style="{
+		backgroundColor: bgColor,
+		borderColor: borderColor
+	}">
+		<view class="u-icon-wrap">
+			<u-icon v-if="showIcon" :name="uIcon" :size="description ? 40 : 32" class="u-icon" :color="uIconType" :custom-style="iconStyle"></u-icon>
+		</view>
+		<view class="u-alert-content" @tap.stop="click">
+			<view class="u-alert-title" :style="[uTitleStyle]">
+				{{title}}
+			</view>
+			<view v-if="description" class="u-alert-desc" :style="[descStyle]">
+				{{description}}
+			</view>
+		</view>
+		<view class="u-icon-wrap">
+			<u-icon @click="close" v-if="closeAble && !closeText" hoverClass="u-type-error-hover-color" name="close" color="#c0c4cc"
+			 :size="22" class="u-close-icon" :style="{
+				top: description ? '18rpx' : '24rpx'
+			}"></u-icon>
+		</view>
+		<text v-if="closeAble && closeText" class="u-close-text" :style="{
+			top: description ? '18rpx' : '24rpx'
+		}">{{closeText}}</text>
+	</view>
+</template>
+
+<script>
+	/**
+	 * alertTips 警告提示
+	 * @description 警告提示,展现需要关注的信息
+	 * @tutorial https://uviewui.com/components/alertTips.html
+	 * @property {String} title 显示的标题文字
+	 * @property {String} description 辅助性文字,颜色比title浅一点,字号也小一点,可选
+	 * @property {String} type 关闭按钮(默认为叉号icon图标)
+	 * @property {String} icon 图标名称
+	 * @property {Object} icon-style 图标的样式,对象形式
+	 * @property {Object} title-style 标题的样式,对象形式
+	 * @property {Object} desc-style 描述的样式,对象形式
+	 * @property {String} close-able 用文字替代关闭图标,close-able为true时有效
+	 * @property {Boolean} show-icon 是否显示左边的辅助图标
+	 * @property {Boolean} show 显示或隐藏组件
+	 * @event {Function} click 点击组件时触发
+	 * @event {Function} close 点击关闭按钮时触发
+	 */
+	export default {
+		name: 'u-alert-tips',
+		props: {
+			// 显示文字
+			title: {
+				type: String,
+				default: ''
+			},
+			// 主题,success/warning/info/error
+			type: {
+				type: String,
+				default: 'warning'
+			},
+			// 辅助性文字
+			description: {
+				type: String,
+				default: ''
+			},
+			// 是否可关闭
+			closeAble: {
+				type: Boolean,
+				default: false
+			},
+			// 关闭按钮自定义文本
+			closeText: {
+				type: String,
+				default: ''
+			},
+			// 是否显示图标
+			showIcon: {
+				type: Boolean,
+				default: false
+			},
+			// 文字颜色,如果定义了color值,icon会失效
+			color: {
+				type: String,
+				default: ''
+			},
+			// 背景颜色
+			bgColor: {
+				type: String,
+				default: ''
+			},
+			// 边框颜色
+			borderColor: {
+				type: String,
+				default: ''
+			},
+			// 是否显示
+			show: {
+				type: Boolean,
+				default: true
+			},
+			// 左边显示的icon
+			icon: {
+				type: String,
+				default: ''
+			},
+			// icon的样式
+			iconStyle: {
+				type: Object,
+				default() {
+					return {}
+				}
+			},
+			// 标题的样式
+			titleStyle: {
+				type: Object,
+				default() {
+					return {}
+				}
+			},
+			// 描述文字的样式
+			descStyle: {
+				type: Object,
+				default() {
+					return {}
+				}
+			},
+		},
+		data() {
+			return {
+			}
+		},
+		computed: {
+			uTitleStyle() {
+				let style = {};
+				// 如果有描述文字的话,标题进行加粗
+				style.fontWeight = this.description ? 500 : 'normal';
+				// 将用户传入样式对象和style合并,传入的优先级比style高,同属性会被覆盖
+				return this.$u.deepMerge(style, this.titleStyle);
+			},
+			uIcon() {
+				// 如果有设置icon名称就使用,否则根据type主题,推定一个默认的图标
+				return this.icon ? this.icon : this.$u.type2icon(this.type);
+			},
+			uIconType() {
+				// 如果有设置图标的样式,优先使用,没有的话,则用type的样式
+				return Object.keys(this.iconStyle).length ? '' : this.type;
+			}
+		},
+		methods: {
+			// 点击内容
+			click() {
+				this.$emit('click');
+			},
+			// 点击关闭按钮
+			close() {
+				this.$emit('close');
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	@import "../../libs/css/style.components.scss";
+	
+	.u-alert-tips {
+		@include vue-flex;
+		align-items: center;
+		padding: 16rpx 30rpx;
+		border-radius: 8rpx;
+		position: relative;
+		transition: all 0.3s linear;
+		border: 1px solid #fff;
+		
+		&--bg--primary-light {
+			background-color: $u-type-primary-light;
+		}
+		
+		&--bg--info-light {
+			background-color: $u-type-info-light;
+		}
+		
+		&--bg--success-light {
+			background-color: $u-type-success-light;
+		}
+		
+		&--bg--warning-light {
+			background-color: $u-type-warning-light;
+		}
+		
+		&--bg--error-light {
+			background-color: $u-type-error-light;
+		}
+		
+		&--border--primary-disabled {
+			border-color: $u-type-primary-disabled;
+		}
+		
+		&--border--success-disabled {
+			border-color: $u-type-success-disabled;
+		}
+		
+		&--border--error-disabled {
+			border-color: $u-type-error-disabled;
+		}
+		
+		&--border--warning-disabled {
+			border-color: $u-type-warning-disabled;
+		}
+		
+		&--border--info-disabled {
+			border-color: $u-type-info-disabled;
+		}
+	}
+
+	.u-close-alert-tips {
+		opacity: 0;
+		visibility: hidden;
+	}
+
+	.u-icon {
+		margin-right: 16rpx;
+	}
+
+	.u-alert-title {
+		font-size: 28rpx;
+		color: $u-main-color;
+	}
+
+	.u-alert-desc {
+		font-size: 26rpx;
+		text-align: left;
+		color: $u-content-color;
+	}
+
+	.u-close-icon {
+		position: absolute;
+		top: 20rpx;
+		right: 20rpx;
+	}
+
+	.u-close-hover {
+		color: red;
+	}
+	
+	.u-close-text {
+		font-size: 24rpx;
+		color: $u-tips-color;
+		position: absolute;
+		top: 20rpx;
+		right: 20rpx;
+		line-height: 1;
+	}
+</style>

+ 290 - 0
uview-ui/components/u-avatar-cropper/u-avatar-cropper.vue

xqd
@@ -0,0 +1,290 @@
+<template>
+	<view class="content">
+		<view class="cropper-wrapper" :style="{ height: cropperOpt.height + 'px' }">
+			<canvas
+				class="cropper"
+				:disable-scroll="true"
+				@touchstart="touchStart"
+				@touchmove="touchMove"
+				@touchend="touchEnd"
+				:style="{ width: cropperOpt.width, height: cropperOpt.height, backgroundColor: 'rgba(0, 0, 0, 0.8)' }"
+				canvas-id="cropper"
+				id="cropper"
+			></canvas>
+			<canvas
+				class="cropper"
+				:disable-scroll="true"
+				:style="{
+					position: 'fixed',
+					top: `-${cropperOpt.width * cropperOpt.pixelRatio}px`,
+					left: `-${cropperOpt.height * cropperOpt.pixelRatio}px`,
+					width: `${cropperOpt.width * cropperOpt.pixelRatio}px`,
+					height: `${cropperOpt.height * cropperOpt.pixelRatio}`
+				}"
+				canvas-id="targetId"
+				id="targetId"
+			></canvas>
+		</view>
+		<view class="cropper-buttons safe-area-padding" :style="{ height: bottomNavHeight + 'px' }">
+			<!-- #ifdef H5 -->
+			<view class="upload" @tap="uploadTap">选择图片</view>
+			<!-- #endif -->
+			<!-- #ifndef H5 -->
+			<view class="upload" @tap="uploadTap">重新选择</view>
+			<!-- #endif -->
+			<view class="getCropperImage" @tap="getCropperImage(false)">确定</view>
+		</view>
+	</view>
+</template>
+
+<script>
+import WeCropper from './weCropper.js';
+export default {
+	props: {
+		// 裁剪矩形框的样式,其中可包含的属性为lineWidth-边框宽度(单位rpx),color: 边框颜色,
+		// mask-遮罩颜色,一般设置为一个rgba的透明度,如"rgba(0, 0, 0, 0.35)"
+		boundStyle: {
+			type: Object,
+			default() {
+				return {
+					lineWidth: 4,
+					borderColor: 'rgb(245, 245, 245)',
+					mask: 'rgba(0, 0, 0, 0.35)'
+				};
+			}
+		}
+		// // 裁剪框宽度,单位rpx
+		// rectWidth: {
+		// 	type: [String, Number],
+		// 	default: 400
+		// },
+		// // 裁剪框高度,单位rpx
+		// rectHeight: {
+		// 	type: [String, Number],
+		// 	default: 400
+		// },
+		// // 输出图片宽度,单位rpx
+		// destWidth: {
+		// 	type: [String, Number],
+		// 	default: 400
+		// },
+		// // 输出图片高度,单位rpx
+		// destHeight: {
+		// 	type: [String, Number],
+		// 	default: 400
+		// },
+		// // 输出的图片类型,如果发现裁剪的图片很大,可能是因为设置为了"png",改成"jpg"即可
+		// fileType: {
+		// 	type: String,
+		// 	default: 'jpg',
+		// },
+		// // 生成的图片质量
+		// // H5上无效,目前不考虑使用此参数
+		// quality: {
+		// 	type: [Number, String],
+		// 	default: 1
+		// }
+	},
+	data() {
+		return {
+			// 底部导航的高度
+			bottomNavHeight: 50,
+			originWidth: 200,
+			width: 0,
+			height: 0,
+			cropperOpt: {
+				id: 'cropper',
+				targetId: 'targetCropper',
+				pixelRatio: 1,
+				width: 0,
+				height: 0,
+				scale: 2.5,
+				zoom: 8,
+				cut: {
+					x: (this.width - this.originWidth) / 2,
+					y: (this.height - this.originWidth) / 2,
+					width: this.originWidth,
+					height: this.originWidth
+				},
+				boundStyle: {
+					lineWidth: uni.upx2px(this.boundStyle.lineWidth),
+					mask: this.boundStyle.mask,
+					color: this.boundStyle.borderColor
+				}
+			},
+			// 裁剪框和输出图片的尺寸,高度默认等于宽度
+			// 输出图片宽度,单位px
+			destWidth: 200,
+			// 裁剪框宽度,单位px
+			rectWidth: 200,
+			// 输出的图片类型,如果'png'类型发现裁剪的图片太大,改成"jpg"即可
+			fileType: 'jpg',
+			src: '', // 选择的图片路径,用于在点击确定时,判断是否选择了图片
+		};
+	},
+	onLoad(option) {
+		let rectInfo = uni.getSystemInfoSync();
+		this.width = rectInfo.windowWidth;
+		this.height = rectInfo.windowHeight - this.bottomNavHeight;
+		this.cropperOpt.width = this.width;
+		this.cropperOpt.height = this.height;
+		this.cropperOpt.pixelRatio = rectInfo.pixelRatio;
+
+		if (option.destWidth) this.destWidth = option.destWidth;
+		if (option.rectWidth) {
+			let rectWidth = Number(option.rectWidth);
+			this.cropperOpt.cut = {
+				x: (this.width - rectWidth) / 2,
+				y: (this.height - rectWidth) / 2,
+				width: rectWidth,
+				height: rectWidth
+			};
+		}
+		this.rectWidth = option.rectWidth;
+		if (option.fileType) this.fileType = option.fileType;
+		// 初始化
+		this.cropper = new WeCropper(this.cropperOpt)
+			.on('ready', ctx => {
+				// wecropper is ready for work!
+			})
+			.on('beforeImageLoad', ctx => {
+				// before picture loaded, i can do something
+			})
+			.on('imageLoad', ctx => {
+				// picture loaded
+			})
+			.on('beforeDraw', (ctx, instance) => {
+				// before canvas draw,i can do something
+			});
+		// 设置导航栏样式,以免用户在page.json中没有设置为黑色背景
+		uni.setNavigationBarColor({
+			frontColor: '#ffffff',
+			backgroundColor: '#000000'
+		});
+		uni.chooseImage({
+			count: 1, // 默认9
+			sizeType: ['compressed'], // 可以指定是原图还是压缩图,默认二者都有
+			sourceType: ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有
+			success: res => {
+				this.src = res.tempFilePaths[0];
+				//  获取裁剪图片资源后,给data添加src属性及其值
+				this.cropper.pushOrign(this.src);
+			}
+		});
+	},
+	methods: {
+		touchStart(e) {
+			this.cropper.touchStart(e);
+		},
+		touchMove(e) {
+			this.cropper.touchMove(e);
+		},
+		touchEnd(e) {
+			this.cropper.touchEnd(e);
+		},
+		getCropperImage(isPre = false) {
+			if(!this.src) return this.$u.toast('请先选择图片再裁剪');
+
+			let cropper_opt = {
+				destHeight: Number(this.destWidth), // uni.canvasToTempFilePath要求这些参数为数值
+				destWidth: Number(this.destWidth),
+				fileType: this.fileType
+			};
+			this.cropper.getCropperImage(cropper_opt, (path, err) => {
+				if (err) {
+					uni.showModal({
+						title: '温馨提示',
+						content: err.message
+					});
+				} else {
+					if (isPre) {
+						uni.previewImage({
+							current: '', // 当前显示图片的 http 链接
+							urls: [path] // 需要预览的图片 http 链接列表
+						});
+					} else {
+						uni.$emit('uAvatarCropper', path);
+						this.$u.route({
+							type: 'back'
+						});
+					}
+				}
+			});
+		},
+		uploadTap() {
+			const self = this;
+			uni.chooseImage({
+				count: 1, // 默认9
+				sizeType: ['original', 'compressed'], // 可以指定是原图还是压缩图,默认二者都有
+				sourceType: ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有
+				success: (res) => {
+					self.src = res.tempFilePaths[0];
+					//  获取裁剪图片资源后,给data添加src属性及其值
+
+					self.cropper.pushOrign(this.src);
+				}
+			});
+		}
+	}
+};
+</script>
+
+<style scoped lang="scss">
+@import '../../libs/css/style.components.scss';
+
+.content {
+	background: rgba(255, 255, 255, 1);
+}
+
+.cropper {
+	position: absolute;
+	top: 0;
+	left: 0;
+	width: 100%;
+	height: 100%;
+	z-index: 11;
+}
+
+.cropper-buttons {
+	background-color: #000000;
+	color: #eee;
+}
+
+.cropper-wrapper {
+	position: relative;
+	@include vue-flex;
+	flex-direction: row;
+	justify-content: space-between;
+	align-items: center;
+	width: 100%;
+	background-color: #000;
+}
+
+.cropper-buttons {
+	width: 100vw;
+	@include vue-flex;
+	flex-direction: row;
+	justify-content: space-between;
+	align-items: center;
+	position: fixed;
+	bottom: 0;
+	left: 0;
+	font-size: 28rpx;
+}
+
+.cropper-buttons .upload,
+.cropper-buttons .getCropperImage {
+	width: 50%;
+	text-align: center;
+}
+
+.cropper-buttons .upload {
+	text-align: left;
+	padding-left: 50rpx;
+}
+
+.cropper-buttons .getCropperImage {
+	text-align: right;
+	padding-right: 50rpx;
+}
+</style>

+ 1265 - 0
uview-ui/components/u-avatar-cropper/weCropper.js

xqd
@@ -0,0 +1,1265 @@
+/**
+ * we-cropper v1.3.9
+ * (c) 2020 dlhandsome
+ * @license MIT
+ */
+(function(global, factory) {
+	typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
+		typeof define === 'function' && define.amd ? define(factory) :
+		(global.WeCropper = factory());
+}(this, (function() {
+	'use strict';
+
+	var device = void 0;
+	var TOUCH_STATE = ['touchstarted', 'touchmoved', 'touchended'];
+
+	function firstLetterUpper(str) {
+		return str.charAt(0).toUpperCase() + str.slice(1)
+	}
+
+	function setTouchState(instance) {
+		var arg = [],
+			len = arguments.length - 1;
+		while (len-- > 0) arg[len] = arguments[len + 1];
+
+		TOUCH_STATE.forEach(function(key, i) {
+			if (arg[i] !== undefined) {
+				instance[key] = arg[i];
+			}
+		});
+	}
+
+	function validator(instance, o) {
+		Object.defineProperties(instance, o);
+	}
+
+	function getDevice() {
+		if (!device) {
+			device = uni.getSystemInfoSync();
+		}
+		return device
+	}
+
+	var tmp = {};
+
+	var ref = getDevice();
+	var pixelRatio = ref.pixelRatio;
+
+	var DEFAULT = {
+		id: {
+			default: 'cropper',
+			get: function get() {
+				return tmp.id
+			},
+			set: function set(value) {
+				if (typeof(value) !== 'string') {
+					console.error(("id:" + value + " is invalid"));
+				}
+				tmp.id = value;
+			}
+		},
+		width: {
+			default: 750,
+			get: function get() {
+				return tmp.width
+			},
+			set: function set(value) {
+				if (typeof(value) !== 'number') {
+					console.error(("width:" + value + " is invalid"));
+				}
+				tmp.width = value;
+			}
+		},
+		height: {
+			default: 750,
+			get: function get() {
+				return tmp.height
+			},
+			set: function set(value) {
+				if (typeof(value) !== 'number') {
+					console.error(("height:" + value + " is invalid"));
+				}
+				tmp.height = value;
+			}
+		},
+		pixelRatio: {
+			default: pixelRatio,
+			get: function get() {
+				return tmp.pixelRatio
+			},
+			set: function set(value) {
+				if (typeof(value) !== 'number') {
+					console.error(("pixelRatio:" + value + " is invalid"));
+				}
+				tmp.pixelRatio = value;
+			}
+		},
+		scale: {
+			default: 2.5,
+			get: function get() {
+				return tmp.scale
+			},
+			set: function set(value) {
+				if (typeof(value) !== 'number') {
+					console.error(("scale:" + value + " is invalid"));
+				}
+				tmp.scale = value;
+			}
+		},
+		zoom: {
+			default: 5,
+			get: function get() {
+				return tmp.zoom
+			},
+			set: function set(value) {
+				if (typeof(value) !== 'number') {
+					console.error(("zoom:" + value + " is invalid"));
+				} else if (value < 0 || value > 10) {
+					console.error("zoom should be ranged in 0 ~ 10");
+				}
+				tmp.zoom = value;
+			}
+		},
+		src: {
+			default: '',
+			get: function get() {
+				return tmp.src
+			},
+			set: function set(value) {
+				if (typeof(value) !== 'string') {
+					console.error(("src:" + value + " is invalid"));
+				}
+				tmp.src = value;
+			}
+		},
+		cut: {
+			default: {},
+			get: function get() {
+				return tmp.cut
+			},
+			set: function set(value) {
+				if (typeof(value) !== 'object') {
+					console.error(("cut:" + value + " is invalid"));
+				}
+				tmp.cut = value;
+			}
+		},
+		boundStyle: {
+			default: {},
+			get: function get() {
+				return tmp.boundStyle
+			},
+			set: function set(value) {
+				if (typeof(value) !== 'object') {
+					console.error(("boundStyle:" + value + " is invalid"));
+				}
+				tmp.boundStyle = value;
+			}
+		},
+		onReady: {
+			default: null,
+			get: function get() {
+				return tmp.ready
+			},
+			set: function set(value) {
+				tmp.ready = value;
+			}
+		},
+		onBeforeImageLoad: {
+			default: null,
+			get: function get() {
+				return tmp.beforeImageLoad
+			},
+			set: function set(value) {
+				tmp.beforeImageLoad = value;
+			}
+		},
+		onImageLoad: {
+			default: null,
+			get: function get() {
+				return tmp.imageLoad
+			},
+			set: function set(value) {
+				tmp.imageLoad = value;
+			}
+		},
+		onBeforeDraw: {
+			default: null,
+			get: function get() {
+				return tmp.beforeDraw
+			},
+			set: function set(value) {
+				tmp.beforeDraw = value;
+			}
+		}
+	};
+
+	var ref$1 = getDevice();
+	var windowWidth = ref$1.windowWidth;
+
+	function prepare() {
+		var self = this;
+
+		// v1.4.0 版本中将不再自动绑定we-cropper实例
+		self.attachPage = function() {
+			var pages = getCurrentPages();
+			// 获取到当前page上下文
+			var pageContext = pages[pages.length - 1];
+			// 把this依附在Page上下文的wecropper属性上,便于在page钩子函数中访问
+			Object.defineProperty(pageContext, 'wecropper', {
+				get: function get() {
+					console.warn(
+						'Instance will not be automatically bound to the page after v1.4.0\n\n' +
+						'Please use a custom instance name instead\n\n' +
+						'Example: \n' +
+						'this.mycropper = new WeCropper(options)\n\n' +
+						'// ...\n' +
+						'this.mycropper.getCropperImage()'
+					);
+					return self
+				},
+				configurable: true
+			});
+		};
+
+		self.createCtx = function() {
+			var id = self.id;
+			var targetId = self.targetId;
+
+			if (id) {
+				self.ctx = self.ctx || uni.createCanvasContext(id);
+				self.targetCtx = self.targetCtx || uni.createCanvasContext(targetId);
+			} else {
+				console.error("constructor: create canvas context failed, 'id' must be valuable");
+			}
+		};
+
+		self.deviceRadio = windowWidth / 750;
+	}
+
+	var commonjsGlobal = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !==
+		'undefined' ? self : {};
+
+
+
+
+
+	function createCommonjsModule(fn, module) {
+		return module = {
+			exports: {}
+		}, fn(module, module.exports), module.exports;
+	}
+
+	var tools = createCommonjsModule(function(module, exports) {
+		/**
+		 * String type check
+		 */
+		exports.isStr = function(v) {
+			return typeof v === 'string';
+		};
+		/**
+		 * Number type check
+		 */
+		exports.isNum = function(v) {
+			return typeof v === 'number';
+		};
+		/**
+		 * Array type check
+		 */
+		exports.isArr = Array.isArray;
+		/**
+		 * undefined type check
+		 */
+		exports.isUndef = function(v) {
+			return v === undefined;
+		};
+
+		exports.isTrue = function(v) {
+			return v === true;
+		};
+
+		exports.isFalse = function(v) {
+			return v === false;
+		};
+		/**
+		 * Function type check
+		 */
+		exports.isFunc = function(v) {
+			return typeof v === 'function';
+		};
+		/**
+		 * Quick object check - this is primarily used to tell
+		 * Objects from primitive values when we know the value
+		 * is a JSON-compliant type.
+		 */
+		exports.isObj = exports.isObject = function(obj) {
+			return obj !== null && typeof obj === 'object'
+		};
+
+		/**
+		 * Strict object type check. Only returns true
+		 * for plain JavaScript objects.
+		 */
+		var _toString = Object.prototype.toString;
+		exports.isPlainObject = function(obj) {
+			return _toString.call(obj) === '[object Object]'
+		};
+
+		/**
+		 * Check whether the object has the property.
+		 */
+		var hasOwnProperty = Object.prototype.hasOwnProperty;
+		exports.hasOwn = function(obj, key) {
+			return hasOwnProperty.call(obj, key)
+		};
+
+		/**
+		 * Perform no operation.
+		 * Stubbing args to make Flow happy without leaving useless transpiled code
+		 * with ...rest (https://flow.org/blog/2017/05/07/Strict-Function-Call-Arity/)
+		 */
+		exports.noop = function(a, b, c) {};
+
+		/**
+		 * Check if val is a valid array index.
+		 */
+		exports.isValidArrayIndex = function(val) {
+			var n = parseFloat(String(val));
+			return n >= 0 && Math.floor(n) === n && isFinite(val)
+		};
+	});
+
+	var tools_7 = tools.isFunc;
+	var tools_10 = tools.isPlainObject;
+
+	var EVENT_TYPE = ['ready', 'beforeImageLoad', 'beforeDraw', 'imageLoad'];
+
+	function observer() {
+		var self = this;
+
+		self.on = function(event, fn) {
+			if (EVENT_TYPE.indexOf(event) > -1) {
+				if (tools_7(fn)) {
+					event === 'ready' ?
+						fn(self) :
+						self[("on" + (firstLetterUpper(event)))] = fn;
+				}
+			} else {
+				console.error(("event: " + event + " is invalid"));
+			}
+			return self
+		};
+	}
+
+	function wxPromise(fn) {
+		return function(obj) {
+			var args = [],
+				len = arguments.length - 1;
+			while (len-- > 0) args[len] = arguments[len + 1];
+
+			if (obj === void 0) obj = {};
+			return new Promise(function(resolve, reject) {
+				obj.success = function(res) {
+					resolve(res);
+				};
+				obj.fail = function(err) {
+					reject(err);
+				};
+				fn.apply(void 0, [obj].concat(args));
+			})
+		}
+	}
+
+	function draw(ctx, reserve) {
+		if (reserve === void 0) reserve = false;
+
+		return new Promise(function(resolve) {
+			ctx.draw(reserve, resolve);
+		})
+	}
+
+	var getImageInfo = wxPromise(uni.getImageInfo);
+
+	var canvasToTempFilePath = wxPromise(uni.canvasToTempFilePath);
+
+	var base64 = createCommonjsModule(function(module, exports) {
+		/*! http://mths.be/base64 v0.1.0 by @mathias | MIT license */
+		(function(root) {
+
+			// Detect free variables `exports`.
+			var freeExports = 'object' == 'object' && exports;
+
+			// Detect free variable `module`.
+			var freeModule = 'object' == 'object' && module &&
+				module.exports == freeExports && module;
+
+			// Detect free variable `global`, from Node.js or Browserified code, and use
+			// it as `root`.
+			var freeGlobal = typeof commonjsGlobal == 'object' && commonjsGlobal;
+			if (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal) {
+				root = freeGlobal;
+			}
+
+			/*--------------------------------------------------------------------------*/
+
+			var InvalidCharacterError = function(message) {
+				this.message = message;
+			};
+			InvalidCharacterError.prototype = new Error;
+			InvalidCharacterError.prototype.name = 'InvalidCharacterError';
+
+			var error = function(message) {
+				// Note: the error messages used throughout this file match those used by
+				// the native `atob`/`btoa` implementation in Chromium.
+				throw new InvalidCharacterError(message);
+			};
+
+			var TABLE = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
+			// http://whatwg.org/html/common-microsyntaxes.html#space-character
+			var REGEX_SPACE_CHARACTERS = /[\t\n\f\r ]/g;
+
+			// `decode` is designed to be fully compatible with `atob` as described in the
+			// HTML Standard. http://whatwg.org/html/webappapis.html#dom-windowbase64-atob
+			// The optimized base64-decoding algorithm used is based on @atk’s excellent
+			// implementation. https://gist.github.com/atk/1020396
+			var decode = function(input) {
+				input = String(input)
+					.replace(REGEX_SPACE_CHARACTERS, '');
+				var length = input.length;
+				if (length % 4 == 0) {
+					input = input.replace(/==?$/, '');
+					length = input.length;
+				}
+				if (
+					length % 4 == 1 ||
+					// http://whatwg.org/C#alphanumeric-ascii-characters
+					/[^+a-zA-Z0-9/]/.test(input)
+				) {
+					error(
+						'Invalid character: the string to be decoded is not correctly encoded.'
+					);
+				}
+				var bitCounter = 0;
+				var bitStorage;
+				var buffer;
+				var output = '';
+				var position = -1;
+				while (++position < length) {
+					buffer = TABLE.indexOf(input.charAt(position));
+					bitStorage = bitCounter % 4 ? bitStorage * 64 + buffer : buffer;
+					// Unless this is the first of a group of 4 characters…
+					if (bitCounter++ % 4) {
+						// …convert the first 8 bits to a single ASCII character.
+						output += String.fromCharCode(
+							0xFF & bitStorage >> (-2 * bitCounter & 6)
+						);
+					}
+				}
+				return output;
+			};
+
+			// `encode` is designed to be fully compatible with `btoa` as described in the
+			// HTML Standard: http://whatwg.org/html/webappapis.html#dom-windowbase64-btoa
+			var encode = function(input) {
+				input = String(input);
+				if (/[^\0-\xFF]/.test(input)) {
+					// Note: no need to special-case astral symbols here, as surrogates are
+					// matched, and the input is supposed to only contain ASCII anyway.
+					error(
+						'The string to be encoded contains characters outside of the ' +
+						'Latin1 range.'
+					);
+				}
+				var padding = input.length % 3;
+				var output = '';
+				var position = -1;
+				var a;
+				var b;
+				var c;
+				var buffer;
+				// Make sure any padding is handled outside of the loop.
+				var length = input.length - padding;
+
+				while (++position < length) {
+					// Read three bytes, i.e. 24 bits.
+					a = input.charCodeAt(position) << 16;
+					b = input.charCodeAt(++position) << 8;
+					c = input.charCodeAt(++position);
+					buffer = a + b + c;
+					// Turn the 24 bits into four chunks of 6 bits each, and append the
+					// matching character for each of them to the output.
+					output += (
+						TABLE.charAt(buffer >> 18 & 0x3F) +
+						TABLE.charAt(buffer >> 12 & 0x3F) +
+						TABLE.charAt(buffer >> 6 & 0x3F) +
+						TABLE.charAt(buffer & 0x3F)
+					);
+				}
+
+				if (padding == 2) {
+					a = input.charCodeAt(position) << 8;
+					b = input.charCodeAt(++position);
+					buffer = a + b;
+					output += (
+						TABLE.charAt(buffer >> 10) +
+						TABLE.charAt((buffer >> 4) & 0x3F) +
+						TABLE.charAt((buffer << 2) & 0x3F) +
+						'='
+					);
+				} else if (padding == 1) {
+					buffer = input.charCodeAt(position);
+					output += (
+						TABLE.charAt(buffer >> 2) +
+						TABLE.charAt((buffer << 4) & 0x3F) +
+						'=='
+					);
+				}
+
+				return output;
+			};
+
+			var base64 = {
+				'encode': encode,
+				'decode': decode,
+				'version': '0.1.0'
+			};
+
+			// Some AMD build optimizers, like r.js, check for specific condition patterns
+			// like the following:
+			if (
+				typeof undefined == 'function' &&
+				typeof undefined.amd == 'object' &&
+				undefined.amd
+			) {
+				undefined(function() {
+					return base64;
+				});
+			} else if (freeExports && !freeExports.nodeType) {
+				if (freeModule) { // in Node.js or RingoJS v0.8.0+
+					freeModule.exports = base64;
+				} else { // in Narwhal or RingoJS v0.7.0-
+					for (var key in base64) {
+						base64.hasOwnProperty(key) && (freeExports[key] = base64[key]);
+					}
+				}
+			} else { // in Rhino or a web browser
+				root.base64 = base64;
+			}
+
+		}(commonjsGlobal));
+	});
+
+	function makeURI(strData, type) {
+		return 'data:' + type + ';base64,' + strData
+	}
+
+	function fixType(type) {
+		type = type.toLowerCase().replace(/jpg/i, 'jpeg');
+		var r = type.match(/png|jpeg|bmp|gif/)[0];
+		return 'image/' + r
+	}
+
+	function encodeData(data) {
+		var str = '';
+		if (typeof data === 'string') {
+			str = data;
+		} else {
+			for (var i = 0; i < data.length; i++) {
+				str += String.fromCharCode(data[i]);
+			}
+		}
+		return base64.encode(str)
+	}
+
+	/**
+	 * 获取图像区域隐含的像素数据
+	 * @param canvasId canvas标识
+	 * @param x 将要被提取的图像数据矩形区域的左上角 x 坐标
+	 * @param y 将要被提取的图像数据矩形区域的左上角 y 坐标
+	 * @param width 将要被提取的图像数据矩形区域的宽度
+	 * @param height 将要被提取的图像数据矩形区域的高度
+	 * @param done 完成回调
+	 */
+	function getImageData(canvasId, x, y, width, height, done) {
+		uni.canvasGetImageData({
+			canvasId: canvasId,
+			x: x,
+			y: y,
+			width: width,
+			height: height,
+			success: function success(res) {
+				done(res, null);
+			},
+			fail: function fail(res) {
+				done(null, res);
+			}
+		});
+	}
+
+	/**
+	 * 生成bmp格式图片
+	 * 按照规则生成图片响应头和响应体
+	 * @param oData 用来描述 canvas 区域隐含的像素数据 { data, width, height } = oData
+	 * @returns {*} base64字符串
+	 */
+	function genBitmapImage(oData) {
+		//
+		// BITMAPFILEHEADER: http://msdn.microsoft.com/en-us/library/windows/desktop/dd183374(v=vs.85).aspx
+		// BITMAPINFOHEADER: http://msdn.microsoft.com/en-us/library/dd183376.aspx
+		//
+		var biWidth = oData.width;
+		var biHeight = oData.height;
+		var biSizeImage = biWidth * biHeight * 3;
+		var bfSize = biSizeImage + 54; // total header size = 54 bytes
+
+		//
+		//  typedef struct tagBITMAPFILEHEADER {
+		//  	WORD bfType;
+		//  	DWORD bfSize;
+		//  	WORD bfReserved1;
+		//  	WORD bfReserved2;
+		//  	DWORD bfOffBits;
+		//  } BITMAPFILEHEADER;
+		//
+		var BITMAPFILEHEADER = [
+			// WORD bfType -- The file type signature; must be "BM"
+			0x42, 0x4D,
+			// DWORD bfSize -- The size, in bytes, of the bitmap file
+			bfSize & 0xff, bfSize >> 8 & 0xff, bfSize >> 16 & 0xff, bfSize >> 24 & 0xff,
+			// WORD bfReserved1 -- Reserved; must be zero
+			0, 0,
+			// WORD bfReserved2 -- Reserved; must be zero
+			0, 0,
+			// DWORD bfOffBits -- The offset, in bytes, from the beginning of the BITMAPFILEHEADER structure to the bitmap bits.
+			54, 0, 0, 0
+		];
+
+		//
+		//  typedef struct tagBITMAPINFOHEADER {
+		//  	DWORD biSize;
+		//  	LONG  biWidth;
+		//  	LONG  biHeight;
+		//  	WORD  biPlanes;
+		//  	WORD  biBitCount;
+		//  	DWORD biCompression;
+		//  	DWORD biSizeImage;
+		//  	LONG  biXPelsPerMeter;
+		//  	LONG  biYPelsPerMeter;
+		//  	DWORD biClrUsed;
+		//  	DWORD biClrImportant;
+		//  } BITMAPINFOHEADER, *PBITMAPINFOHEADER;
+		//
+		var BITMAPINFOHEADER = [
+			// DWORD biSize -- The number of bytes required by the structure
+			40, 0, 0, 0,
+			// LONG biWidth -- The width of the bitmap, in pixels
+			biWidth & 0xff, biWidth >> 8 & 0xff, biWidth >> 16 & 0xff, biWidth >> 24 & 0xff,
+			// LONG biHeight -- The height of the bitmap, in pixels
+			biHeight & 0xff, biHeight >> 8 & 0xff, biHeight >> 16 & 0xff, biHeight >> 24 & 0xff,
+			// WORD biPlanes -- The number of planes for the target device. This value must be set to 1
+			1, 0,
+			// WORD biBitCount -- The number of bits-per-pixel, 24 bits-per-pixel -- the bitmap
+			// has a maximum of 2^24 colors (16777216, Truecolor)
+			24, 0,
+			// DWORD biCompression -- The type of compression, BI_RGB (code 0) -- uncompressed
+			0, 0, 0, 0,
+			// DWORD biSizeImage -- The size, in bytes, of the image. This may be set to zero for BI_RGB bitmaps
+			biSizeImage & 0xff, biSizeImage >> 8 & 0xff, biSizeImage >> 16 & 0xff, biSizeImage >> 24 & 0xff,
+			// LONG biXPelsPerMeter, unused
+			0, 0, 0, 0,
+			// LONG biYPelsPerMeter, unused
+			0, 0, 0, 0,
+			// DWORD biClrUsed, the number of color indexes of palette, unused
+			0, 0, 0, 0,
+			// DWORD biClrImportant, unused
+			0, 0, 0, 0
+		];
+
+		var iPadding = (4 - ((biWidth * 3) % 4)) % 4;
+
+		var aImgData = oData.data;
+
+		var strPixelData = '';
+		var biWidth4 = biWidth << 2;
+		var y = biHeight;
+		var fromCharCode = String.fromCharCode;
+
+		do {
+			var iOffsetY = biWidth4 * (y - 1);
+			var strPixelRow = '';
+			for (var x = 0; x < biWidth; x++) {
+				var iOffsetX = x << 2;
+				strPixelRow += fromCharCode(aImgData[iOffsetY + iOffsetX + 2]) +
+					fromCharCode(aImgData[iOffsetY + iOffsetX + 1]) +
+					fromCharCode(aImgData[iOffsetY + iOffsetX]);
+			}
+
+			for (var c = 0; c < iPadding; c++) {
+				strPixelRow += String.fromCharCode(0);
+			}
+
+			strPixelData += strPixelRow;
+		} while (--y)
+
+		var strEncoded = encodeData(BITMAPFILEHEADER.concat(BITMAPINFOHEADER)) + encodeData(strPixelData);
+
+		return strEncoded
+	}
+
+	/**
+	 * 转换为图片base64
+	 * @param canvasId canvas标识
+	 * @param x 将要被提取的图像数据矩形区域的左上角 x 坐标
+	 * @param y 将要被提取的图像数据矩形区域的左上角 y 坐标
+	 * @param width 将要被提取的图像数据矩形区域的宽度
+	 * @param height 将要被提取的图像数据矩形区域的高度
+	 * @param type 转换图片类型
+	 * @param done 完成回调
+	 */
+	function convertToImage(canvasId, x, y, width, height, type, done) {
+		if (done === void 0) done = function() {};
+
+		if (type === undefined) {
+			type = 'png';
+		}
+		type = fixType(type);
+		if (/bmp/.test(type)) {
+			getImageData(canvasId, x, y, width, height, function(data, err) {
+				var strData = genBitmapImage(data);
+				tools_7(done) && done(makeURI(strData, 'image/' + type), err);
+			});
+		} else {
+			console.error('暂不支持生成\'' + type + '\'类型的base64图片');
+		}
+	}
+
+	var CanvasToBase64 = {
+		convertToImage: convertToImage,
+		// convertToPNG: function (width, height, done) {
+		//   return convertToImage(width, height, 'png', done)
+		// },
+		// convertToJPEG: function (width, height, done) {
+		//   return convertToImage(width, height, 'jpeg', done)
+		// },
+		// convertToGIF: function (width, height, done) {
+		//   return convertToImage(width, height, 'gif', done)
+		// },
+		convertToBMP: function(ref, done) {
+			if (ref === void 0) ref = {};
+			var canvasId = ref.canvasId;
+			var x = ref.x;
+			var y = ref.y;
+			var width = ref.width;
+			var height = ref.height;
+			if (done === void 0) done = function() {};
+
+			return convertToImage(canvasId, x, y, width, height, 'bmp', done)
+		}
+	};
+
+	function methods() {
+		var self = this;
+
+		var boundWidth = self.width; // 裁剪框默认宽度,即整个画布宽度
+		var boundHeight = self.height; // 裁剪框默认高度,即整个画布高度
+
+		var id = self.id;
+		var targetId = self.targetId;
+		var pixelRatio = self.pixelRatio;
+
+		var ref = self.cut;
+		var x = ref.x;
+		if (x === void 0) x = 0;
+		var y = ref.y;
+		if (y === void 0) y = 0;
+		var width = ref.width;
+		if (width === void 0) width = boundWidth;
+		var height = ref.height;
+		if (height === void 0) height = boundHeight;
+
+		self.updateCanvas = function(done) {
+			if (self.croperTarget) {
+				//  画布绘制图片
+				self.ctx.drawImage(
+					self.croperTarget,
+					self.imgLeft,
+					self.imgTop,
+					self.scaleWidth,
+					self.scaleHeight
+				);
+			}
+			tools_7(self.onBeforeDraw) && self.onBeforeDraw(self.ctx, self);
+
+			self.setBoundStyle(self.boundStyle); //	设置边界样式
+
+			self.ctx.draw(false, done);
+			return self
+		};
+
+		self.pushOrigin = self.pushOrign = function(src) {
+			self.src = src;
+
+			tools_7(self.onBeforeImageLoad) && self.onBeforeImageLoad(self.ctx, self);
+
+			return getImageInfo({
+					src: src
+				})
+				.then(function(res) {
+					var innerAspectRadio = res.width / res.height;
+					var customAspectRadio = width / height;
+
+					self.croperTarget = res.path;
+
+					if (innerAspectRadio < customAspectRadio) {
+						self.rectX = x;
+						self.baseWidth = width;
+						self.baseHeight = width / innerAspectRadio;
+						self.rectY = y - Math.abs((height - self.baseHeight) / 2);
+					} else {
+						self.rectY = y;
+						self.baseWidth = height * innerAspectRadio;
+						self.baseHeight = height;
+						self.rectX = x - Math.abs((width - self.baseWidth) / 2);
+					}
+
+					self.imgLeft = self.rectX;
+					self.imgTop = self.rectY;
+					self.scaleWidth = self.baseWidth;
+					self.scaleHeight = self.baseHeight;
+
+					self.update();
+
+					return new Promise(function(resolve) {
+						self.updateCanvas(resolve);
+					})
+				})
+				.then(function() {
+					tools_7(self.onImageLoad) && self.onImageLoad(self.ctx, self);
+				})
+		};
+
+		self.removeImage = function() {
+			self.src = '';
+			self.croperTarget = '';
+			return draw(self.ctx)
+		};
+
+		self.getCropperBase64 = function(done) {
+			if (done === void 0) done = function() {};
+
+			CanvasToBase64.convertToBMP({
+				canvasId: id,
+				x: x,
+				y: y,
+				width: width,
+				height: height
+			}, done);
+		};
+
+		self.getCropperImage = function(opt, fn) {
+			var customOptions = opt;
+
+			var canvasOptions = {
+				canvasId: id,
+				x: x,
+				y: y,
+				width: width,
+				height: height
+			};
+
+			var task = function() {
+				return Promise.resolve();
+			};
+
+			if (
+				tools_10(customOptions) &&
+				customOptions.original
+			) {
+				// original mode
+				task = function() {
+					self.targetCtx.drawImage(
+						self.croperTarget,
+						self.imgLeft * pixelRatio,
+						self.imgTop * pixelRatio,
+						self.scaleWidth * pixelRatio,
+						self.scaleHeight * pixelRatio
+					);
+
+					canvasOptions = {
+						canvasId: targetId,
+						x: x * pixelRatio,
+						y: y * pixelRatio,
+						width: width * pixelRatio,
+						height: height * pixelRatio
+					};
+
+					return draw(self.targetCtx)
+				};
+			}
+
+			return task()
+				.then(function() {
+					if (tools_10(customOptions)) {
+						canvasOptions = Object.assign({}, canvasOptions, customOptions);
+					}
+
+					if (tools_7(customOptions)) {
+						fn = customOptions;
+					}
+
+					var arg = canvasOptions.componentContext ?
+						[canvasOptions, canvasOptions.componentContext] :
+						[canvasOptions];
+
+					return canvasToTempFilePath.apply(null, arg)
+				})
+				.then(function(res) {
+					var tempFilePath = res.tempFilePath;
+
+					return tools_7(fn) ?
+						fn.call(self, tempFilePath, null) :
+						tempFilePath
+				})
+				.catch(function(err) {
+					if (tools_7(fn)) {
+						fn.call(self, null, err);
+					} else {
+						throw err
+					}
+				})
+		};
+	}
+
+	/**
+	 * 获取最新缩放值
+	 * @param oldScale 上一次触摸结束后的缩放值
+	 * @param oldDistance 上一次触摸结束后的双指距离
+	 * @param zoom 缩放系数
+	 * @param touch0 第一指touch对象
+	 * @param touch1 第二指touch对象
+	 * @returns {*}
+	 */
+	var getNewScale = function(oldScale, oldDistance, zoom, touch0, touch1) {
+		var xMove, yMove, newDistance;
+		// 计算二指最新距离
+		xMove = Math.round(touch1.x - touch0.x);
+		yMove = Math.round(touch1.y - touch0.y);
+		newDistance = Math.round(Math.sqrt(xMove * xMove + yMove * yMove));
+
+		return oldScale + 0.001 * zoom * (newDistance - oldDistance)
+	};
+
+	function update() {
+		var self = this;
+
+		if (!self.src) {
+			return
+		}
+
+		self.__oneTouchStart = function(touch) {
+			self.touchX0 = Math.round(touch.x);
+			self.touchY0 = Math.round(touch.y);
+		};
+
+		self.__oneTouchMove = function(touch) {
+			var xMove, yMove;
+			// 计算单指移动的距离
+			if (self.touchended) {
+				return self.updateCanvas()
+			}
+			xMove = Math.round(touch.x - self.touchX0);
+			yMove = Math.round(touch.y - self.touchY0);
+
+			var imgLeft = Math.round(self.rectX + xMove);
+			var imgTop = Math.round(self.rectY + yMove);
+
+			self.outsideBound(imgLeft, imgTop);
+
+			self.updateCanvas();
+		};
+
+		self.__twoTouchStart = function(touch0, touch1) {
+			var xMove, yMove, oldDistance;
+
+			self.touchX1 = Math.round(self.rectX + self.scaleWidth / 2);
+			self.touchY1 = Math.round(self.rectY + self.scaleHeight / 2);
+
+			// 计算两指距离
+			xMove = Math.round(touch1.x - touch0.x);
+			yMove = Math.round(touch1.y - touch0.y);
+			oldDistance = Math.round(Math.sqrt(xMove * xMove + yMove * yMove));
+
+			self.oldDistance = oldDistance;
+		};
+
+		self.__twoTouchMove = function(touch0, touch1) {
+			var oldScale = self.oldScale;
+			var oldDistance = self.oldDistance;
+			var scale = self.scale;
+			var zoom = self.zoom;
+
+			self.newScale = getNewScale(oldScale, oldDistance, zoom, touch0, touch1);
+
+			//  设定缩放范围
+			self.newScale <= 1 && (self.newScale = 1);
+			self.newScale >= scale && (self.newScale = scale);
+
+			self.scaleWidth = Math.round(self.newScale * self.baseWidth);
+			self.scaleHeight = Math.round(self.newScale * self.baseHeight);
+			var imgLeft = Math.round(self.touchX1 - self.scaleWidth / 2);
+			var imgTop = Math.round(self.touchY1 - self.scaleHeight / 2);
+
+			self.outsideBound(imgLeft, imgTop);
+
+			self.updateCanvas();
+		};
+
+		self.__xtouchEnd = function() {
+			self.oldScale = self.newScale;
+			self.rectX = self.imgLeft;
+			self.rectY = self.imgTop;
+		};
+	}
+
+	var handle = {
+		//  图片手势初始监测
+		touchStart: function touchStart(e) {
+			var self = this;
+			var ref = e.touches;
+			var touch0 = ref[0];
+			var touch1 = ref[1];
+
+			if (!self.src) {
+				return
+			}
+
+			setTouchState(self, true, null, null);
+
+			// 计算第一个触摸点的位置,并参照改点进行缩放
+			self.__oneTouchStart(touch0);
+
+			// 两指手势触发
+			if (e.touches.length >= 2) {
+				self.__twoTouchStart(touch0, touch1);
+			}
+		},
+
+		//  图片手势动态缩放
+		touchMove: function touchMove(e) {
+			var self = this;
+			var ref = e.touches;
+			var touch0 = ref[0];
+			var touch1 = ref[1];
+
+			if (!self.src) {
+				return
+			}
+
+			setTouchState(self, null, true);
+
+			// 单指手势时触发
+			if (e.touches.length === 1) {
+				self.__oneTouchMove(touch0);
+			}
+			// 两指手势触发
+			if (e.touches.length >= 2) {
+				self.__twoTouchMove(touch0, touch1);
+			}
+		},
+
+		touchEnd: function touchEnd(e) {
+			var self = this;
+
+			if (!self.src) {
+				return
+			}
+
+			setTouchState(self, false, false, true);
+			self.__xtouchEnd();
+		}
+	};
+
+	function cut() {
+		var self = this;
+		var boundWidth = self.width; // 裁剪框默认宽度,即整个画布宽度
+		var boundHeight = self.height;
+		// 裁剪框默认高度,即整个画布高度
+		var ref = self.cut;
+		var x = ref.x;
+		if (x === void 0) x = 0;
+		var y = ref.y;
+		if (y === void 0) y = 0;
+		var width = ref.width;
+		if (width === void 0) width = boundWidth;
+		var height = ref.height;
+		if (height === void 0) height = boundHeight;
+
+		/**
+		 * 设置边界
+		 * @param imgLeft 图片左上角横坐标值
+		 * @param imgTop 图片左上角纵坐标值
+		 */
+		self.outsideBound = function(imgLeft, imgTop) {
+			self.imgLeft = imgLeft >= x ?
+				x :
+				self.scaleWidth + imgLeft - x <= width ?
+				x + width - self.scaleWidth :
+				imgLeft;
+
+			self.imgTop = imgTop >= y ?
+				y :
+				self.scaleHeight + imgTop - y <= height ?
+				y + height - self.scaleHeight :
+				imgTop;
+		};
+
+		/**
+		 * 设置边界样式
+		 * @param color	边界颜色
+		 */
+		self.setBoundStyle = function(ref) {
+			if (ref === void 0) ref = {};
+			var color = ref.color;
+			if (color === void 0) color = '#04b00f';
+			var mask = ref.mask;
+			if (mask === void 0) mask = 'rgba(0, 0, 0, 0.3)';
+			var lineWidth = ref.lineWidth;
+			if (lineWidth === void 0) lineWidth = 1;
+
+			var half = lineWidth / 2;
+			var boundOption = [{
+					start: {
+						x: x - half,
+						y: y + 10 - half
+					},
+					step1: {
+						x: x - half,
+						y: y - half
+					},
+					step2: {
+						x: x + 10 - half,
+						y: y - half
+					}
+				},
+				{
+					start: {
+						x: x - half,
+						y: y + height - 10 + half
+					},
+					step1: {
+						x: x - half,
+						y: y + height + half
+					},
+					step2: {
+						x: x + 10 - half,
+						y: y + height + half
+					}
+				},
+				{
+					start: {
+						x: x + width - 10 + half,
+						y: y - half
+					},
+					step1: {
+						x: x + width + half,
+						y: y - half
+					},
+					step2: {
+						x: x + width + half,
+						y: y + 10 - half
+					}
+				},
+				{
+					start: {
+						x: x + width + half,
+						y: y + height - 10 + half
+					},
+					step1: {
+						x: x + width + half,
+						y: y + height + half
+					},
+					step2: {
+						x: x + width - 10 + half,
+						y: y + height + half
+					}
+				}
+			];
+
+			// 绘制半透明层
+			self.ctx.beginPath();
+			self.ctx.setFillStyle(mask);
+			self.ctx.fillRect(0, 0, x, boundHeight);
+			self.ctx.fillRect(x, 0, width, y);
+			self.ctx.fillRect(x, y + height, width, boundHeight - y - height);
+			self.ctx.fillRect(x + width, 0, boundWidth - x - width, boundHeight);
+			self.ctx.fill();
+
+			boundOption.forEach(function(op) {
+				self.ctx.beginPath();
+				self.ctx.setStrokeStyle(color);
+				self.ctx.setLineWidth(lineWidth);
+				self.ctx.moveTo(op.start.x, op.start.y);
+				self.ctx.lineTo(op.step1.x, op.step1.y);
+				self.ctx.lineTo(op.step2.x, op.step2.y);
+				self.ctx.stroke();
+			});
+		};
+	}
+
+	var version = "1.3.9";
+
+	var WeCropper = function WeCropper(params) {
+		var self = this;
+		var _default = {};
+
+		validator(self, DEFAULT);
+
+		Object.keys(DEFAULT).forEach(function(key) {
+			_default[key] = DEFAULT[key].default;
+		});
+		Object.assign(self, _default, params);
+
+		self.prepare();
+		self.attachPage();
+		self.createCtx();
+		self.observer();
+		self.cutt();
+		self.methods();
+		self.init();
+		self.update();
+
+		return self
+	};
+
+	WeCropper.prototype.init = function init() {
+		var self = this;
+		var src = self.src;
+
+		self.version = version;
+
+		typeof self.onReady === 'function' && self.onReady(self.ctx, self);
+
+		if (src) {
+			self.pushOrign(src);
+		} else {
+			self.updateCanvas();
+		}
+		setTouchState(self, false, false, false);
+
+		self.oldScale = 1;
+		self.newScale = 1;
+
+		return self
+	};
+
+	Object.assign(WeCropper.prototype, handle);
+
+	WeCropper.prototype.prepare = prepare;
+	WeCropper.prototype.observer = observer;
+	WeCropper.prototype.methods = methods;
+	WeCropper.prototype.cutt = cut;
+	WeCropper.prototype.update = update;
+
+	return WeCropper;
+
+})));

File diff suppressed because it is too large
+ 24 - 0
uview-ui/components/u-avatar/u-avatar.vue


+ 153 - 0
uview-ui/components/u-back-top/u-back-top.vue

xqd
@@ -0,0 +1,153 @@
+<template>
+	<view @tap="backToTop" class="u-back-top" :class="['u-back-top--mode--' + mode]" :style="[{
+		bottom: bottom + 'rpx',
+		right: right + 'rpx',
+		borderRadius: mode == 'circle' ? '10000rpx' : '8rpx',
+		zIndex: uZIndex,
+		opacity: opacity
+	}, customStyle]">
+		<view class="u-back-top__content" v-if="!$slots.default && !$slots.$default">
+			<u-icon @click="backToTop" :name="icon" :custom-style="iconStyle"></u-icon>
+			<view class="u-back-top__content__tips">
+				{{tips}}
+			</view>
+		</view>
+		<slot v-else />
+	</view>
+</template>
+
+<script>
+	export default {
+		name: 'u-back-top',
+		props: {
+			// 返回顶部的形状,circle-圆形,square-方形
+			mode: {
+				type: String,
+				default: 'circle'
+			},
+			// 自定义图标
+			icon: {
+				type: String,
+				default: 'arrow-upward'
+			},
+			// 提示文字
+			tips: {
+				type: String,
+				default: ''
+			},
+			// 返回顶部滚动时间
+			duration: {
+				type: [Number, String],
+				default: 100
+			},
+			// 滚动距离
+			scrollTop: {
+				type: [Number, String],
+				default: 0
+			},
+			// 距离顶部多少距离显示,单位rpx
+			top: {
+				type: [Number, String],
+				default: 400
+			},
+			// 返回顶部按钮到底部的距离,单位rpx
+			bottom: {
+				type: [Number, String],
+				default: 200
+			},
+			// 返回顶部按钮到右边的距离,单位rpx
+			right: {
+				type: [Number, String],
+				default: 40
+			},
+			// 层级
+			zIndex: {
+				type: [Number, String],
+				default: '9'
+			},
+			// 图标的样式,对象形式
+			iconStyle: {
+				type: Object,
+				default() {
+					return {
+						color: '#909399',
+						fontSize: '38rpx'
+					}
+				}
+			},
+			// 整个组件的样式
+			customStyle: {
+				type: Object,
+				default() {
+					return {}
+				}
+			}
+		},
+		watch: {
+			showBackTop(nVal, oVal) {
+				// 当组件的显示与隐藏状态发生跳变时,修改组件的层级和不透明度
+				// 让组件有显示和消失的动画效果,如果用v-if控制组件状态,将无设置动画效果
+				if(nVal) {
+					this.uZIndex = this.zIndex;
+					this.opacity = 1;
+				} else {
+					this.uZIndex = -1;
+					this.opacity = 0;
+				}
+			}
+		},
+		computed: {
+			showBackTop() {
+				// 由于scrollTop为页面的滚动距离,默认为px单位,这里将用于传入的top(rpx)值
+				// 转为px用于比较,如果滚动条到顶的距离大于设定的距离,就显示返回顶部的按钮
+				return this.scrollTop > uni.upx2px(this.top);
+			},
+		},
+		data() {
+			return {
+				// 不透明度,为了让组件有一个显示和隐藏的过渡动画
+				opacity: 0,
+				// 组件的z-index值,隐藏时设置为-1,就会看不到
+				uZIndex: -1
+			}
+		},
+		methods: {
+			backToTop() {
+				uni.pageScrollTo({
+					scrollTop: 0,
+					duration: this.duration
+				});
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	@import "../../libs/css/style.components.scss";
+	
+	.u-back-top {
+		width: 80rpx;
+		height: 80rpx;
+		position: fixed;
+		z-index: 9;
+		@include vue-flex;
+		flex-direction: column;
+		justify-content: center;
+		background-color: #E1E1E1;
+		color: $u-content-color;
+		align-items: center;
+		transition: opacity 0.4s;
+		
+		&__content {
+			@include vue-flex;
+			flex-direction: column;
+			align-items: center;
+			
+			&__tips {
+				font-size: 24rpx;
+				transform: scale(0.8);
+				line-height: 1;
+			}
+		}
+	}
+</style>

+ 216 - 0
uview-ui/components/u-badge/u-badge.vue

xqd
@@ -0,0 +1,216 @@
+<template>
+	<view v-if="show" class="u-badge" :class="[
+			isDot ? 'u-badge-dot' : '', 
+			size == 'mini' ? 'u-badge-mini' : '',
+			type ? 'u-badge--bg--' + type : ''
+		]" :style="[{
+			top: offset[0] + 'rpx',
+			right: offset[1] + 'rpx',
+			fontSize: fontSize + 'rpx',
+			position: absolute ? 'absolute' : 'static',
+			color: color,
+			backgroundColor: bgColor
+		}, boxStyle]"
+	>
+		{{showText}}
+	</view>
+</template>
+
+<script>
+	/**
+	 * badge 角标
+	 * @description 本组件一般用于展示头像的地方,如个人中心,或者评论列表页的用户头像展示等场所。
+	 * @tutorial https://www.uviewui.com/components/badge.html
+	 * @property {String Number} count 展示的数字,大于 overflowCount 时显示为 ${overflowCount}+,为0且show-zero为false时隐藏
+	 * @property {Boolean} is-dot 不展示数字,只有一个小点(默认false)
+	 * @property {Boolean} absolute 组件是否绝对定位,为true时,offset参数才有效(默认true)
+	 * @property {String Number} overflow-count 展示封顶的数字值(默认99)
+	 * @property {String} type 使用预设的背景颜色(默认error)
+	 * @property {Boolean} show-zero 当数值为 0 时,是否展示 Badge(默认false)
+	 * @property {String} size Badge的尺寸,设为mini会得到小一号的Badge(默认default)
+	 * @property {Array} offset 设置badge的位置偏移,格式为 [x, y],也即设置的为top和right的值,单位rpx。absolute为true时有效(默认[20, 20])
+	 * @property {String} color 字体颜色(默认#ffffff)
+	 * @property {String} bgColor 背景颜色,优先级比type高,如设置,type参数会失效
+	 * @property {Boolean} is-center 组件中心点是否和父组件右上角重合,优先级比offset高,如设置,offset参数会失效(默认false)
+	 * @example <u-badge type="error" count="7"></u-badge>
+	 */
+	export default {
+		name: 'u-badge',
+		props: {
+			// primary,warning,success,error,info
+			type: {
+				type: String,
+				default: 'error'
+			},
+			// default, mini
+			size: {
+				type: String,
+				default: 'default'
+			},
+			//是否是圆点
+			isDot: {
+				type: Boolean,
+				default: false
+			},
+			// 显示的数值内容
+			count: {
+				type: [Number, String],
+			},
+			// 展示封顶的数字值
+			overflowCount: {
+				type: Number,
+				default: 99
+			},
+			// 当数值为 0 时,是否展示 Badge
+			showZero: {
+				type: Boolean,
+				default: false
+			},
+			// 位置偏移
+			offset: {
+				type: Array,
+				default: () => {
+					return [20, 20]
+				}
+			},
+			// 是否开启绝对定位,开启了offset才会起作用
+			absolute: {
+				type: Boolean,
+				default: true
+			},
+			// 字体大小
+			fontSize: {
+				type: [String, Number],
+				default: '24'
+			},
+			// 字体演示
+			color: {
+				type: String,
+				default: '#ffffff'
+			},
+			// badge的背景颜色
+			bgColor: {
+				type: String,
+				default: ''
+			},
+			// 是否让badge组件的中心点和父组件右上角重合,配置的话,offset将会失效
+			isCenter: {
+				type: Boolean,
+				default: false
+			}
+		},
+		computed: {
+			// 是否将badge中心与父组件右上角重合
+			boxStyle() {
+				let style = {};
+				if(this.isCenter) {
+					style.top = 0;
+					style.right = 0;
+					// Y轴-50%,意味着badge向上移动了badge自身高度一半,X轴50%,意味着向右移动了自身宽度一半
+					style.transform = "translateY(-50%) translateX(50%)";
+				} else {
+					style.top = this.offset[0] + 'rpx';
+					style.right = this.offset[1] + 'rpx';
+					style.transform = "translateY(0) translateX(0)";
+				}
+				// 如果尺寸为mini,后接上scal()
+				if(this.size == 'mini') {
+					style.transform = style.transform + " scale(0.8)";
+				}
+				return style;
+			},
+			// isDot类型时,不显示文字
+			showText() {
+				if(this.isDot) return '';
+				else {
+					if(this.count > this.overflowCount) return `${this.overflowCount}+`;
+					else return this.count;
+				}
+			},
+			// 是否显示组件
+			show() {
+				// 如果count的值为0,并且showZero设置为false,不显示组件
+				if(this.count == 0 && this.showZero == false) return false;
+				else return true;
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	@import "../../libs/css/style.components.scss";
+	
+	.u-badge {
+		/* #ifndef APP-NVUE */
+		display: inline-flex;
+		/* #endif */
+		justify-content: center;
+		align-items: center;
+		line-height: 24rpx;
+		padding: 4rpx 8rpx;
+		border-radius: 100rpx;
+		z-index: 9;
+		
+		&--bg--primary {
+			background-color: $u-type-primary;
+		}
+		
+		&--bg--error {
+			background-color: $u-type-error;
+		}
+		
+		&--bg--success {
+			background-color: $u-type-success;
+		}
+		
+		&--bg--info {
+			background-color: $u-type-info;
+		}
+		
+		&--bg--warning {
+			background-color: $u-type-warning;
+		}
+	}
+	
+	.u-badge-dot {
+		height: 16rpx;
+		width: 16rpx;
+		border-radius: 100rpx;
+		line-height: 1;
+	}
+	
+	.u-badge-mini {
+		transform: scale(0.8);
+		transform-origin: center center;
+	}
+	
+	// .u-primary {
+	// 	background: $u-type-primary;
+	// 	color: #fff;
+	// }
+	
+	// .u-error {
+	// 	background: $u-type-error;
+	// 	color: #fff;
+	// }
+	
+	// .u-warning {
+	// 	background: $u-type-warning;
+	// 	color: #fff;
+	// }
+	
+	// .u-success {
+	// 	background: $u-type-success;
+	// 	color: #fff;
+	// }
+	
+	// .u-black {
+	// 	background: #585858;
+	// 	color: #fff;
+	// }
+	
+	.u-info {
+		background-color: $u-type-info;
+		color: #fff;
+	}
+</style>

+ 596 - 0
uview-ui/components/u-button/u-button.vue

xqd
@@ -0,0 +1,596 @@
+<template>
+	<button
+		id="u-wave-btn"
+		class="u-btn u-line-1 u-fix-ios-appearance"
+		:class="[
+			'u-size-' + size,
+			plain ? 'u-btn--' + type + '--plain' : '',
+			loading ? 'u-loading' : '',
+			shape == 'circle' ? 'u-round-circle' : '',
+			hairLine ? showHairLineBorder : 'u-btn--bold-border',
+			'u-btn--' + type,
+			disabled ? `u-btn--${type}--disabled` : '',
+		]"
+		:hover-start-time="Number(hoverStartTime)"
+		:hover-stay-time="Number(hoverStayTime)"
+		:disabled="disabled"
+		:form-type="formType"
+		:open-type="openType"
+		:app-parameter="appParameter"
+		:hover-stop-propagation="hoverStopPropagation"
+		:send-message-title="sendMessageTitle"
+		send-message-path="sendMessagePath"
+		:lang="lang"
+		:data-name="dataName"
+		:session-from="sessionFrom"
+		:send-message-img="sendMessageImg"
+		:show-message-card="showMessageCard"
+		@getphonenumber="getphonenumber"
+		@getuserinfo="getuserinfo"
+		@error="error"
+		@opensetting="opensetting"
+		@launchapp="launchapp"
+		:style="[customStyle, {
+			overflow: ripple ? 'hidden' : 'visible'
+		}]"
+		@tap.stop="click($event)"
+		:hover-class="getHoverClass"
+		:loading="loading"
+	>
+		<slot></slot>
+		<view
+			v-if="ripple"
+			class="u-wave-ripple"
+			:class="[waveActive ? 'u-wave-active' : '']"
+			:style="{
+				top: rippleTop + 'px',
+				left: rippleLeft + 'px',
+				width: fields.targetWidth + 'px',
+				height: fields.targetWidth + 'px',
+				'background-color': rippleBgColor || 'rgba(0, 0, 0, 0.15)'
+			}"
+		></view>
+	</button>
+</template>
+
+<script>
+/**
+ * button 按钮
+ * @description Button 按钮
+ * @tutorial https://www.uviewui.com/components/button.html
+ * @property {String} size 按钮的大小
+ * @property {Boolean} ripple 是否开启点击水波纹效果
+ * @property {String} ripple-bg-color 水波纹的背景色,ripple为true时有效
+ * @property {String} type 按钮的样式类型
+ * @property {Boolean} plain 按钮是否镂空,背景色透明
+ * @property {Boolean} disabled 是否禁用
+ * @property {Boolean} hair-line 是否显示按钮的细边框(默认true)
+ * @property {Boolean} shape 按钮外观形状,见文档说明
+ * @property {Boolean} loading 按钮名称前是否带 loading 图标(App-nvue 平台,在 ios 上为雪花,Android上为圆圈)
+ * @property {String} form-type 用于 <form> 组件,点击分别会触发 <form> 组件的 submit/reset 事件
+ * @property {String} open-type 开放能力
+ * @property {String} data-name 额外传参参数,用于小程序的data-xxx属性,通过target.dataset.name获取
+ * @property {String} hover-class 指定按钮按下去的样式类。当 hover-class="none" 时,没有点击态效果(App-nvue 平台暂不支持)
+ * @property {Number} hover-start-time 按住后多久出现点击态,单位毫秒
+ * @property {Number} hover-stay-time 手指松开后点击态保留时间,单位毫秒
+ * @property {Object} custom-style 对按钮的自定义样式,对象形式,见文档说明
+ * @event {Function} click 按钮点击
+ * @event {Function} getphonenumber open-type="getPhoneNumber"时有效
+ * @event {Function} getuserinfo 用户点击该按钮时,会返回获取到的用户信息,从返回参数的detail中获取到的值同uni.getUserInfo
+ * @event {Function} error 当使用开放能力时,发生错误的回调
+ * @event {Function} opensetting 在打开授权设置页并关闭后回调
+ * @event {Function} launchapp 打开 APP 成功的回调
+ * @example <u-button>月落</u-button>
+ */
+export default {
+	name: 'u-button',
+	props: {
+		// 是否细边框
+		hairLine: {
+			type: Boolean,
+			default: true
+		},
+		// 按钮的预置样式,default,primary,error,warning,success
+		type: {
+			type: String,
+			default: 'default'
+		},
+		// 按钮尺寸,default,medium,mini
+		size: {
+			type: String,
+			default: 'default'
+		},
+		// 按钮形状,circle(两边为半圆),square(带圆角)
+		shape: {
+			type: String,
+			default: 'square'
+		},
+		// 按钮是否镂空
+		plain: {
+			type: Boolean,
+			default: false
+		},
+		// 是否禁止状态
+		disabled: {
+			type: Boolean,
+			default: false
+		},
+		// 是否加载中
+		loading: {
+			type: Boolean,
+			default: false
+		},
+		// 开放能力,具体请看uniapp稳定关于button组件部分说明
+		// https://uniapp.dcloud.io/component/button
+		openType: {
+			type: String,
+			default: ''
+		},
+		// 用于 <form> 组件,点击分别会触发 <form> 组件的 submit/reset 事件
+		// 取值为submit(提交表单),reset(重置表单)
+		formType: {
+			type: String,
+			default: ''
+		},
+		// 打开 APP 时,向 APP 传递的参数,open-type=launchApp时有效
+		// 只微信小程序、QQ小程序有效
+		appParameter: {
+			type: String,
+			default: ''
+		},
+		// 指定是否阻止本节点的祖先节点出现点击态,微信小程序有效
+		hoverStopPropagation: {
+			type: Boolean,
+			default: false
+		},
+		// 指定返回用户信息的语言,zh_CN 简体中文,zh_TW 繁体中文,en 英文。只微信小程序有效
+		lang: {
+			type: String,
+			default: 'en'
+		},
+		// 会话来源,open-type="contact"时有效。只微信小程序有效
+		sessionFrom: {
+			type: String,
+			default: ''
+		},
+		// 会话内消息卡片标题,open-type="contact"时有效
+		// 默认当前标题,只微信小程序有效
+		sendMessageTitle: {
+			type: String,
+			default: ''
+		},
+		// 会话内消息卡片点击跳转小程序路径,open-type="contact"时有效
+		// 默认当前分享路径,只微信小程序有效
+		sendMessagePath: {
+			type: String,
+			default: ''
+		},
+		// 会话内消息卡片图片,open-type="contact"时有效
+		// 默认当前页面截图,只微信小程序有效
+		sendMessageImg: {
+			type: String,
+			default: ''
+		},
+		// 是否显示会话内消息卡片,设置此参数为 true,用户进入客服会话会在右下角显示"可能要发送的小程序"提示,
+		// 用户点击后可以快速发送小程序消息,open-type="contact"时有效
+		showMessageCard: {
+			type: Boolean,
+			default: false
+		},
+		// 手指按(触摸)按钮时按钮时的背景颜色
+		hoverBgColor: {
+			type: String,
+			default: ''
+		},
+		// 水波纹的背景颜色
+		rippleBgColor: {
+			type: String,
+			default: ''
+		},
+		// 是否开启水波纹效果
+		ripple: {
+			type: Boolean,
+			default: false
+		},
+		// 按下的类名
+		hoverClass: {
+			type: String,
+			default: ''
+		},
+		// 自定义样式,对象形式
+		customStyle: {
+			type: Object,
+			default() {
+				return {};
+			}
+		},
+		// 额外传参参数,用于小程序的data-xxx属性,通过target.dataset.name获取
+		dataName: {
+			type: String,
+			default: ''
+		},
+		// 节流,一定时间内只能触发一次
+		throttleTime: {
+			type: [String, Number],
+			default: 1000
+		},
+		// 按住后多久出现点击态,单位毫秒
+		hoverStartTime: {
+			type: [String, Number],
+			default: 20
+		},
+		// 手指松开后点击态保留时间,单位毫秒
+		hoverStayTime: {
+			type: [String, Number],
+			default: 150
+		},
+	},
+	computed: {
+		// 当没有传bgColor变量时,按钮按下去的颜色类名
+		getHoverClass() {
+			// 如果开启水波纹效果,则不启用hover-class效果
+			if (this.loading || this.disabled || this.ripple || this.hoverClass) return '';
+			let hoverClass = '';
+			hoverClass = this.plain ? 'u-' + this.type + '-plain-hover' : 'u-' + this.type + '-hover';
+			return hoverClass;
+		},
+		// 在'primary', 'success', 'error', 'warning'类型下,不显示边框,否则会造成四角有毛刺现象
+		showHairLineBorder() {
+			if (['primary', 'success', 'error', 'warning'].indexOf(this.type) >= 0 && !this.plain) {
+				return '';
+			} else {
+				return 'u-hairline-border';
+			}
+		}
+	},
+	data() {
+		return {
+			rippleTop: 0, // 水波纹的起点Y坐标到按钮上边界的距离
+			rippleLeft: 0, // 水波纹起点X坐标到按钮左边界的距离
+			fields: {}, // 波纹按钮节点信息
+			waveActive: false // 激活水波纹
+		};
+	},
+	methods: {
+		// 按钮点击
+		click(e) {
+			// 进行节流控制,每this.throttle毫秒内,只在开始处执行
+			this.$u.throttle(() => {
+				// 如果按钮时disabled和loading状态,不触发水波纹效果
+				if (this.loading === true || this.disabled === true) return;
+				// 是否开启水波纹效果
+				if (this.ripple) {
+					// 每次点击时,移除上一次的类,再次添加,才能触发动画效果
+					this.waveActive = false;
+					this.$nextTick(function() {
+						this.getWaveQuery(e);
+					});
+				}
+				this.$emit('click', e);
+			}, this.throttleTime);
+		},
+		// 查询按钮的节点信息
+		getWaveQuery(e) {
+			this.getElQuery().then(res => {
+				// 查询返回的是一个数组节点
+				let data = res[0];
+				// 查询不到节点信息,不操作
+				if (!data.width || !data.width) return;
+				// 水波纹的最终形态是一个正方形(通过border-radius让其变为一个圆形),这里要保证正方形的边长等于按钮的最长边
+				// 最终的方形(变换后的圆形)才能覆盖整个按钮
+				data.targetWidth = data.height > data.width ? data.height : data.width;
+				if (!data.targetWidth) return;
+				this.fields = data;
+				let touchesX = '',
+					touchesY = '';
+				// #ifdef MP-BAIDU
+				touchesX = e.changedTouches[0].clientX;
+				touchesY = e.changedTouches[0].clientY;
+				// #endif
+				// #ifdef MP-ALIPAY
+				touchesX = e.detail.clientX;
+				touchesY = e.detail.clientY;
+				// #endif
+				// #ifndef MP-BAIDU || MP-ALIPAY
+				touchesX = e.touches[0].clientX;
+				touchesY = e.touches[0].clientY;
+				// #endif
+				// 获取触摸点相对于按钮上边和左边的x和y坐标,原理是通过屏幕的触摸点(touchesY),减去按钮的上边界data.top
+				// 但是由于`transform-origin`默认是center,所以这里再减去半径才是水波纹view应该的位置
+				// 总的来说,就是把水波纹的矩形(变换后的圆形)的中心点,移动到我们的触摸点位置
+				this.rippleTop = touchesY - data.top - data.targetWidth / 2;
+				this.rippleLeft = touchesX - data.left - data.targetWidth / 2;
+				this.$nextTick(() => {
+					this.waveActive = true;
+				});
+			});
+		},
+		// 获取节点信息
+		getElQuery() {
+			return new Promise(resolve => {
+				let queryInfo = '';
+				// 获取元素节点信息,请查看uniapp相关文档
+				// https://uniapp.dcloud.io/api/ui/nodes-info?id=nodesrefboundingclientrect
+				queryInfo = uni.createSelectorQuery().in(this);
+				//#ifdef MP-ALIPAY
+				queryInfo = uni.createSelectorQuery();
+				//#endif
+				queryInfo.select('.u-btn').boundingClientRect();
+				queryInfo.exec(data => {
+					resolve(data);
+				});
+			});
+		},
+		// 下面为对接uniapp官方按钮开放能力事件回调的对接
+		getphonenumber(res) {
+			this.$emit('getphonenumber', res);
+		},
+		getuserinfo(res) {
+			this.$emit('getuserinfo', res);
+		},
+		error(res) {
+			this.$emit('error', res);
+		},
+		opensetting(res) {
+			this.$emit('opensetting', res);
+		},
+		launchapp(res) {
+			this.$emit('launchapp', res);
+		}
+	}
+};
+</script>
+
+<style scoped lang="scss">
+@import '../../libs/css/style.components.scss';
+.u-btn::after {
+	border: none;
+}
+
+.u-btn {
+	position: relative;
+	border: 0;
+	//border-radius: 10rpx;
+	/* #ifndef APP-NVUE */
+	display: inline-flex;		
+	/* #endif */
+	// 避免边框某些场景可能被“裁剪”,不能设置为hidden
+	overflow: visible;
+	line-height: 1;
+	@include vue-flex;
+	align-items: center;
+	justify-content: center;
+	cursor: pointer;
+	padding: 0 40rpx;
+	z-index: 1;
+	box-sizing: border-box;
+	transition: all 0.15s;
+	
+	&--bold-border {
+		border: 1px solid #ffffff;
+	}
+	
+	&--default {
+		color: $u-content-color;
+		border-color: #c0c4cc;
+		background-color: #ffffff;
+	}
+	
+	&--primary {
+		color: #ffffff;
+		border-color: $u-type-primary;
+		background-color: $u-type-primary;
+	}
+	
+	&--success {
+		color: #ffffff;
+		border-color: $u-type-success;
+		background-color: $u-type-success;
+	}
+	
+	&--error {
+		color: #ffffff;
+		border-color: $u-type-error;
+		background-color: $u-type-error;
+	}
+	
+	&--warning {
+		color: #ffffff;
+		border-color: $u-type-warning;
+		background-color: $u-type-warning;
+	}
+	
+	&--default--disabled {
+		color: #ffffff;
+		border-color: #e4e7ed;
+		background-color: #ffffff;
+	}
+	
+	&--primary--disabled {
+		color: #ffffff!important;
+		border-color: $u-type-primary-disabled!important;
+		background-color: $u-type-primary-disabled!important;
+	}
+	
+	&--success--disabled {
+		color: #ffffff!important;
+		border-color: $u-type-success-disabled!important;
+		background-color: $u-type-success-disabled!important;
+	}
+	
+	&--error--disabled {
+		color: #ffffff!important;
+		border-color: $u-type-error-disabled!important;
+		background-color: $u-type-error-disabled!important;
+	}
+	
+	&--warning--disabled {
+		color: #ffffff!important;
+		border-color: $u-type-warning-disabled!important;
+		background-color: $u-type-warning-disabled!important;
+	}
+	
+	&--primary--plain {
+		color: $u-type-primary!important;
+		border-color: $u-type-primary-disabled!important;
+		background-color: $u-type-primary-light!important;
+	}
+	
+	&--success--plain {
+		color: $u-type-success!important;
+		border-color: $u-type-success-disabled!important;
+		background-color: $u-type-success-light!important;
+	}
+	
+	&--error--plain {
+		color: $u-type-error!important;
+		border-color: $u-type-error-disabled!important;
+		background-color: $u-type-error-light!important;
+	}
+	
+	&--warning--plain {
+		color: $u-type-warning!important;
+		border-color: $u-type-warning-disabled!important;
+		background-color: $u-type-warning-light!important;
+	}
+}
+
+.u-hairline-border:after {
+	content: ' ';
+	position: absolute;
+	pointer-events: none;
+	// 设置为border-box,意味着下面的scale缩小为0.5,实际上缩小的是伪元素的内容(border-box意味着内容不含border)
+	box-sizing: border-box;
+	// 中心点作为变形(scale())的原点
+	-webkit-transform-origin: 0 0;
+	transform-origin: 0 0;
+	left: 0;
+	top: 0;
+	width: 199.8%;
+	height: 199.7%;
+	-webkit-transform: scale(0.5, 0.5);
+	transform: scale(0.5, 0.5);
+	border: 1px solid currentColor;
+	z-index: 1;
+}
+
+.u-wave-ripple {
+	z-index: 0;
+	position: absolute;
+	border-radius: 100%;
+	background-clip: padding-box;
+	pointer-events: none;
+	user-select: none;
+	transform: scale(0);
+	opacity: 1;
+	transform-origin: center;
+}
+
+.u-wave-ripple.u-wave-active {
+	opacity: 0;
+	transform: scale(2);
+	transition: opacity 1s linear, transform 0.4s linear;
+}
+
+.u-round-circle {
+	border-radius: 100rpx;
+}
+
+.u-round-circle::after {
+	border-radius: 100rpx;
+}
+
+.u-loading::after {
+	background-color: hsla(0, 0%, 100%, 0.35);
+}
+
+.u-size-default {
+	font-size: 30rpx;
+	height: 80rpx;
+	line-height: 80rpx;
+}
+
+.u-size-medium {
+	/* #ifndef APP-NVUE */
+	display: inline-flex;		
+	/* #endif */
+	width: auto;
+	font-size: 26rpx;
+	height: 70rpx;
+	line-height: 70rpx;
+	padding: 0 80rpx;
+}
+
+.u-size-mini {
+	/* #ifndef APP-NVUE */
+	display: inline-flex;		
+	/* #endif */
+	width: auto;
+	font-size: 22rpx;
+	padding-top: 1px;
+	height: 50rpx;
+	line-height: 50rpx;
+	padding: 0 20rpx;
+}
+
+.u-primary-plain-hover {
+	color: #ffffff !important;
+	background: $u-type-primary-dark !important;
+}
+
+.u-default-plain-hover {
+	color: $u-type-primary-dark !important;
+	background: $u-type-primary-light !important;
+}
+
+.u-success-plain-hover {
+	color: #ffffff !important;
+	background: $u-type-success-dark !important;
+}
+
+.u-warning-plain-hover {
+	color: #ffffff !important;
+	background: $u-type-warning-dark !important;
+}
+
+.u-error-plain-hover {
+	color: #ffffff !important;
+	background: $u-type-error-dark !important;
+}
+
+.u-info-plain-hover {
+	color: #ffffff !important;
+	background: $u-type-info-dark !important;
+}
+
+.u-default-hover {
+	color: $u-type-primary-dark !important;
+	border-color: $u-type-primary-dark !important;
+	background-color: $u-type-primary-light !important;
+}
+
+.u-primary-hover {
+	background: $u-type-primary-dark !important;
+	color: #fff;
+}
+
+.u-success-hover {
+	background: $u-type-success-dark !important;
+	color: #fff;
+}
+
+.u-info-hover {
+	background: $u-type-info-dark !important;
+	color: #fff;
+}
+
+.u-warning-hover {
+	background: $u-type-warning-dark !important;
+	color: #fff;
+}
+
+.u-error-hover {
+	background: $u-type-error-dark !important;
+	color: #fff;
+}
+</style>

+ 639 - 0
uview-ui/components/u-calendar/u-calendar.vue

xqd
@@ -0,0 +1,639 @@
+<template>
+	<u-popup closeable :maskCloseAble="maskCloseAble" mode="bottom" :popup="false" v-model="value" length="auto"
+	 :safeAreaInsetBottom="safeAreaInsetBottom" @close="close" :z-index="uZIndex" :border-radius="borderRadius" :closeable="closeable">
+		<view class="u-calendar">
+			<view class="u-calendar__header">
+				<view class="u-calendar__header__text" v-if="!$slots['tooltip']">
+					{{toolTip}}
+				</view>
+				<slot v-else name="tooltip" />
+			</view>
+			<view class="u-calendar__action u-flex u-row-center">
+				<view class="u-calendar__action__icon">
+					<u-icon v-if="changeYear" name="arrow-left-double" :color="yearArrowColor" @click="changeYearHandler(0)"></u-icon>
+				</view>
+				<view class="u-calendar__action__icon">
+					<u-icon v-if="changeMonth" name="arrow-left" :color="monthArrowColor" @click="changeMonthHandler(0)"></u-icon>
+				</view>
+				<view class="u-calendar__action__text">{{ showTitle }}</view>
+				<view class="u-calendar__action__icon">
+					<u-icon v-if="changeMonth" name="arrow-right" :color="monthArrowColor" @click="changeMonthHandler(1)"></u-icon>
+				</view>
+				<view class="u-calendar__action__icon">
+					<u-icon v-if="changeYear" name="arrow-right-double" :color="yearArrowColor" @click="changeYearHandler(1)"></u-icon>
+				</view>
+			</view>
+			<view class="u-calendar__week-day">
+				<view class="u-calendar__week-day__text" v-for="(item, index) in weekDayZh" :key="index">{{item}}</view>
+			</view>
+			<view class="u-calendar__content">
+				<!-- 前置空白部分 -->
+				<block v-for="(item, index) in weekdayArr" :key="index">
+					<view class="u-calendar__content__item"></view>
+				</block>
+				<view class="u-calendar__content__item" :class="{
+					'u-hover-class':openDisAbled(year,month,index+1),
+					'u-calendar__content--start-date': (mode == 'range' && startDate==`${year}-${month}-${index+1}`) || mode== 'date',
+					'u-calendar__content--end-date':(mode== 'range' && endDate==`${year}-${month}-${index+1}`) || mode == 'date'
+				}" :style="{backgroundColor: getColor(index,1)}" v-for="(item, index) in daysArr" :key="index"
+				 @tap="dateClick(index)">
+					<view class="u-calendar__content__item__inner" :style="{color: getColor(index,2)}">
+						<view>{{ index + 1 }}</view>
+					</view>
+					<view class="u-calendar__content__item__tips" :style="{color:activeColor}" v-if="mode== 'range' && startDate==`${year}-${month}-${index+1}` && startDate!=endDate">{{startText}}</view>
+					<view class="u-calendar__content__item__tips" :style="{color:activeColor}" v-if="mode== 'range' && endDate==`${year}-${month}-${index+1}`">{{endText}}</view>
+				</view>
+				<view class="u-calendar__content__bg-month">{{month}}</view>
+			</view>
+			<view class="u-calendar__bottom">
+				<view class="u-calendar__bottom__choose">
+					<text>{{mode == 'date' ? activeDate : startDate}}</text>
+					<text v-if="endDate">至{{endDate}}</text>
+				</view>
+				<view class="u-calendar__bottom__btn">
+					<u-button :type="btnType" shape="circle" size="default" @click="btnFix(false)">确定</u-button>
+				</view>
+			</view>
+		</view>
+	</u-popup>
+</template>
+<script>
+	/**
+	 * calendar 日历
+	 * @description 此组件用于单个选择日期,范围选择日期等,日历被包裹在底部弹起的容器中。
+	 * @tutorial http://uviewui.com/components/calendar.html
+	 * @property {String} mode 选择日期的模式,date-为单个日期,range-为选择日期范围
+	 * @property {Boolean} v-model 布尔值变量,用于控制日历的弹出与收起
+	 * @property {Boolean} safe-area-inset-bottom 是否开启底部安全区适配(默认false)
+	 * @property {Boolean} change-year 是否显示顶部的切换年份方向的按钮(默认true)
+	 * @property {Boolean} change-month 是否显示顶部的切换月份方向的按钮(默认true)
+	 * @property {String Number} max-year 可切换的最大年份(默认2050)
+	 * @property {String Number} min-year 可切换的最小年份(默认1950)
+	 * @property {String Number} min-date 最小可选日期(默认1950-01-01)
+	 * @property {String Number} max-date 最大可选日期(默认当前日期)
+	 * @property {String Number} 弹窗顶部左右两边的圆角值,单位rpx(默认20)
+	 * @property {Boolean} mask-close-able 是否允许通过点击遮罩关闭日历(默认true)
+	 * @property {String} month-arrow-color 月份切换按钮箭头颜色(默认#606266)
+	 * @property {String} year-arrow-color 年份切换按钮箭头颜色(默认#909399)
+	 * @property {String} color 日期字体的默认颜色(默认#303133)
+	 * @property {String} active-bg-color 起始/结束日期按钮的背景色(默认#2979ff)
+	 * @property {String Number} z-index 弹出时的z-index值(默认10075)
+	 * @property {String} active-color 起始/结束日期按钮的字体颜色(默认#ffffff)
+	 * @property {String} range-bg-color 起始/结束日期之间的区域的背景颜色(默认rgba(41,121,255,0.13))
+	 * @property {String} range-color 选择范围内字体颜色(默认#2979ff)
+	 * @property {String} start-text 起始日期底部的提示文字(默认 '开始')
+	 * @property {String} end-text 结束日期底部的提示文字(默认 '结束')
+	 * @property {String} btn-type 底部确定按钮的主题(默认 'primary')
+	 * @property {String} toolTip 顶部提示文字,如设置名为tooltip的slot,此参数将失效(默认 '选择日期')
+	 * @property {Boolean} closeable 是否显示右上角的关闭图标(默认true)
+	 * @example <u-calendar v-model="show" :mode="mode"></u-calendar>
+	 */
+	
+	export default {
+		name: 'u-calendar',
+		props: {
+			safeAreaInsetBottom: {
+				type: Boolean,
+				default: false
+			},
+			// 是否允许通过点击遮罩关闭Picker
+			maskCloseAble: {
+				type: Boolean,
+				default: true
+			},
+			// 通过双向绑定控制组件的弹出与收起
+			value: {
+				type: Boolean,
+				default: false
+			},
+			// 弹出的z-index值
+			zIndex: {
+				type: [String, Number],
+				default: 0
+			},
+			// 是否允许切换年份
+			changeYear: {
+				type: Boolean,
+				default: true
+			},
+			// 是否允许切换月份
+			changeMonth: {
+				type: Boolean,
+				default: true
+			},
+			// date-单个日期选择,range-开始日期+结束日期选择
+			mode: {
+				type: String,
+				default: 'date'
+			},
+			// 可切换的最大年份
+			maxYear: {
+				type: [Number, String],
+				default: 2050
+			},
+			// 可切换的最小年份
+			minYear: {
+				type: [Number, String],
+				default: 1950
+			},
+			// 最小可选日期(不在范围内日期禁用不可选)
+			minDate: {
+				type: [Number, String],
+				default: '1950-01-01'
+			},
+			/**
+			 * 最大可选日期
+			 * 默认最大值为今天,之后的日期不可选
+			 * 2030-12-31
+			 * */
+			maxDate: {
+				type: [Number, String],
+				default: ''
+			},
+			// 弹窗顶部左右两边的圆角值
+			borderRadius: {
+				type: [String, Number],
+				default: 20
+			},
+			// 月份切换按钮箭头颜色
+			monthArrowColor: {
+				type: String,
+				default: '#606266'
+			},
+			// 年份切换按钮箭头颜色
+			yearArrowColor: {
+				type: String,
+				default: '#909399'
+			},
+			// 默认日期字体颜色
+			color: {
+				type: String,
+				default: '#303133'
+			},
+			// 选中|起始结束日期背景色
+			activeBgColor: {
+				type: String,
+				default: '#2979ff'
+			},
+			// 选中|起始结束日期字体颜色
+			activeColor: {
+				type: String,
+				default: '#ffffff'
+			},
+			// 范围内日期背景色
+			rangeBgColor: {
+				type: String,
+				default: 'rgba(41,121,255,0.13)'
+			}, 
+			// 范围内日期字体颜色
+			rangeColor: {
+				type: String,
+				default: '#2979ff'
+			},
+			// mode=range时生效,起始日期自定义文案
+			startText: {
+				type: String,
+				default: '开始'
+			},
+			// mode=range时生效,结束日期自定义文案
+			endText: {
+				type: String,
+				default: '结束'
+			},
+			//按钮样式类型
+			btnType: {
+				type: String,
+				default: 'primary'
+			},
+			// 当前选中日期带选中效果
+			isActiveCurrent: {
+				type: Boolean,
+				default: true
+			},
+			// 切换年月是否触发事件 mode=date时生效
+			isChange: {
+				type: Boolean,
+				default: false
+			},
+			// 是否显示右上角的关闭图标
+			closeable: {
+				type: Boolean,
+				default: true
+			},
+			// 顶部的提示文字
+			toolTip: {
+				type: String,
+				default: '选择日期'
+			}
+		},
+		data() {
+			return {
+				// 星期几,值为1-7
+				weekday: 1, 
+				weekdayArr:[],
+				// 当前月有多少天
+				days: 0, 
+				daysArr:[],
+				showTitle: '',
+				year: 2020,
+				month: 0,
+				day: 0,
+				startYear: 0,
+				startMonth: 0,
+				startDay: 0,
+				endYear: 0,
+				endMonth: 0,
+				endDay: 0,
+				today: '',
+				activeDate: '',
+				startDate: '',
+				endDate: '',
+				isStart: true,
+				min: null,
+				max: null,
+				weekDayZh: ['日', '一', '二', '三', '四', '五', '六']
+			};
+		},
+		computed: {
+			dataChange() {
+				return `${this.mode}-${this.minDate}-${this.maxDate}`;
+			},
+			uZIndex() {
+				// 如果用户有传递z-index值,优先使用
+				return this.zIndex ? this.zIndex : this.$u.zIndex.popup;
+			}
+		},
+		watch: {
+			dataChange(val) {
+				this.init()
+			}
+		},
+		created() {
+			this.init()
+		},
+		methods: {
+			getColor(index, type) {
+				let color = type == 1 ? '' : this.color;
+				let day = index + 1
+				let date = `${this.year}-${this.month}-${day}`
+				let timestamp = new Date(date.replace(/\-/g, '/')).getTime();
+				let start = this.startDate.replace(/\-/g, '/')
+				let end = this.endDate.replace(/\-/g, '/')
+				if ((this.isActiveCurrent && this.activeDate == date) || this.startDate == date || this.endDate == date) {
+					color = type == 1 ? this.activeBgColor : this.activeColor;
+				} else if (this.endDate && timestamp > new Date(start).getTime() && timestamp < new Date(end).getTime()) {
+					color = type == 1 ? this.rangeBgColor : this.rangeColor;
+				}
+				return color;
+			},
+			init() {
+				let now = new Date();
+				this.year = now.getFullYear();
+				this.month = now.getMonth() + 1;
+				this.day = now.getDate();
+				this.today = `${now.getFullYear()}-${now.getMonth() + 1}-${now.getDate()}`;
+				this.activeDate = this.today;
+				this.min = this.initDate(this.minDate);
+				this.max = this.initDate(this.maxDate || this.today);
+				this.startDate = "";
+				this.startYear = 0;
+				this.startMonth = 0;
+				this.startDay = 0;
+				this.endYear = 0;
+				this.endMonth = 0;
+				this.endDay = 0;
+				this.endDate = "";
+				this.isStart = true;
+				this.changeData();
+			},
+			//日期处理
+			initDate(date) {
+				let fdate = date.split('-');
+				return {
+					year: Number(fdate[0] || 1920),
+					month: Number(fdate[1] || 1),
+					day: Number(fdate[2] || 1)
+				}
+			},
+			openDisAbled: function(year, month, day) {
+				let bool = true;
+				let date = `${year}/${month}/${day}`;
+				// let today = this.today.replace(/\-/g, '/');
+				let min = `${this.min.year}/${this.min.month}/${this.min.day}`;
+				let max = `${this.max.year}/${this.max.month}/${this.max.day}`;
+				let timestamp = new Date(date).getTime();
+				if (timestamp >= new Date(min).getTime() && timestamp <= new Date(max).getTime()) {
+					bool = false;
+				}
+				return bool;
+			},
+			generateArray: function(start, end) {
+				return Array.from(new Array(end + 1).keys()).slice(start);
+			},
+			formatNum: function(num) {
+				return num < 10 ? '0' + num : num + '';
+			},
+			//一个月有多少天
+			getMonthDay(year, month) {
+				let days = new Date(year, month, 0).getDate();
+				return days;
+			},
+			getWeekday(year, month) {
+				let date = new Date(`${year}/${month}/01 00:00:00`);
+				return date.getDay();
+			},
+			checkRange(year) {
+				let overstep = false;
+				if (year < this.minYear || year > this.maxYear) {
+					uni.showToast({
+						title: "日期超出范围啦~",
+						icon: 'none'
+					})
+					overstep = true;
+				}
+				return overstep;
+			},
+			changeMonthHandler(isAdd) {
+				if (isAdd) {
+					let month = this.month + 1;
+					let year = month > 12 ? this.year + 1 : this.year;
+					if (!this.checkRange(year)) {
+						this.month = month > 12 ? 1 : month;
+						this.year = year;
+						this.changeData();
+					}
+
+				} else {
+					let month = this.month - 1;
+					let year = month < 1 ? this.year - 1 : this.year;
+					if (!this.checkRange(year)) {
+						this.month = month < 1 ? 12 : month;
+						this.year = year;
+						this.changeData();
+					}
+				}
+			},
+			changeYearHandler(isAdd) {
+				let year = isAdd ? this.year + 1 : this.year - 1;
+				if (!this.checkRange(year)) {
+					this.year = year;
+					this.changeData();
+				}
+			},
+			changeData() {
+				this.days = this.getMonthDay(this.year, this.month);
+				this.daysArr=this.generateArray(1,this.days)
+				this.weekday = this.getWeekday(this.year, this.month);
+				this.weekdayArr=this.generateArray(1,this.weekday)
+				this.showTitle = `${this.year}年${this.month}月`;
+				if (this.isChange && this.mode == 'date') {
+					this.btnFix(true);
+				}
+			},
+			dateClick: function(day) {
+				day += 1;
+				if (!this.openDisAbled(this.year, this.month, day)) {
+					this.day = day;
+					let date = `${this.year}-${this.month}-${day}`;
+					if (this.mode == 'date') {
+						this.activeDate = date;
+					} else {
+						let compare = new Date(date.replace(/\-/g, '/')).getTime() < new Date(this.startDate.replace(/\-/g, '/')).getTime()
+						if (this.isStart || compare) {
+							this.startDate = date;
+							this.startYear = this.year;
+							this.startMonth = this.month;
+							this.startDay = this.day;
+							this.endYear = 0;
+							this.endMonth = 0;
+							this.endDay = 0;
+							this.endDate = "";
+							this.activeDate = "";
+							this.isStart = false;
+						} else {
+							this.endDate = date;
+							this.endYear = this.year;
+							this.endMonth = this.month;
+							this.endDay = this.day;
+							this.isStart = true;
+						}
+					}
+				}
+			},
+			close() {
+				// 修改通过v-model绑定的父组件变量的值为false,从而隐藏日历弹窗
+				this.$emit('input', false);
+			},
+			getWeekText(date) {
+				date = new Date(`${date.replace(/\-/g, '/')} 00:00:00`);
+				let week = date.getDay();
+				return '星期' + ['日', '一', '二', '三', '四', '五', '六'][week];
+			},
+			btnFix(show) {
+				if (!show) {
+					this.close();
+				}
+				if (this.mode == 'date') {
+					let arr = this.activeDate.split('-')
+					let year = this.isChange ? this.year : Number(arr[0]);
+					let month = this.isChange ? this.month : Number(arr[1]);
+					let day = this.isChange ? this.day : Number(arr[2]);
+					//当前月有多少天
+					let days = this.getMonthDay(year, month);
+					let result = `${year}-${this.formatNum(month)}-${this.formatNum(day)}`;
+					let weekText = this.getWeekText(result);
+					let isToday = false;
+					if (`${year}-${month}-${day}` == this.today) {
+						//今天
+						isToday = true;
+					}
+					this.$emit('change', {
+						year: year,
+						month: month,
+						day: day,
+						days: days,
+						result: result,
+						week: weekText,
+						isToday: isToday,
+						// switch: show //是否是切换年月操作
+					});
+				} else {
+					if (!this.startDate || !this.endDate) return;
+					let startMonth = this.formatNum(this.startMonth);
+					let startDay = this.formatNum(this.startDay);
+					let startDate = `${this.startYear}-${startMonth}-${startDay}`;
+					let startWeek = this.getWeekText(startDate)
+
+					let endMonth = this.formatNum(this.endMonth);
+					let endDay = this.formatNum(this.endDay);
+					let endDate = `${this.endYear}-${endMonth}-${endDay}`;
+					let endWeek = this.getWeekText(endDate);
+					this.$emit('change', {
+						startYear: this.startYear,
+						startMonth: this.startMonth,
+						startDay: this.startDay,
+						startDate: startDate,
+						startWeek: startWeek,
+						endYear: this.endYear,
+						endMonth: this.endMonth,
+						endDay: this.endDay,
+						endDate: endDate,
+						endWeek: endWeek
+					});
+				}
+			}
+		}
+	};
+</script>
+
+<style scoped lang="scss">
+	@import "../../libs/css/style.components.scss";
+	
+	.u-calendar {
+		color: $u-content-color;
+		
+		&__header {
+			width: 100%;
+			box-sizing: border-box;
+			font-size: 30rpx;
+			background-color: #fff;
+			color: $u-main-color;
+			
+			&__text {
+				margin-top: 30rpx;
+				padding: 0 60rpx;
+				@include vue-flex;
+				justify-content: center;
+				align-items: center;
+			}
+		}
+		
+		&__action {
+			padding: 40rpx 0 40rpx 0;
+			
+			&__icon {
+				margin: 0 16rpx;
+			}
+			
+			&__text {
+				padding: 0 16rpx;
+				color: $u-main-color;
+				font-size: 32rpx;
+				line-height: 32rpx;
+				font-weight: bold;
+			}
+		}
+	
+		&__week-day {
+			@include vue-flex;
+			align-items: center;
+			justify-content: center;
+			padding: 6px 0;
+			overflow: hidden;
+			
+			&__text {
+				flex: 1;
+				text-align: center;
+			}
+		}
+	
+		&__content {
+			width: 100%;
+			@include vue-flex;
+			flex-wrap: wrap;
+			padding: 6px 0;
+			box-sizing: border-box;
+			background-color: #fff;
+			position: relative;
+			
+			&--end-date {
+				border-top-right-radius: 8rpx;
+				border-bottom-right-radius: 8rpx;
+			}
+			
+			&--start-date {
+				border-top-left-radius: 8rpx;
+				border-bottom-left-radius: 8rpx;
+			}
+			
+			&__item {
+				width: 14.2857%;
+				@include vue-flex;
+				align-items: center;
+				justify-content: center;
+				padding: 6px 0;
+				overflow: hidden;
+				position: relative;
+				z-index: 2;
+				
+				&__inner {
+					height: 84rpx;
+					@include vue-flex;
+					align-items: center;
+					justify-content: center;
+					flex-direction: column;
+					font-size: 32rpx;
+					position: relative;
+					border-radius: 50%;
+					
+					&__desc {
+						width: 100%;
+						font-size: 24rpx;
+						line-height: 24rpx;
+						transform: scale(0.75);
+						transform-origin: center center;
+						position: absolute;
+						left: 0;
+						text-align: center;
+						bottom: 2rpx;
+					}
+				}
+				
+				&__tips {
+					width: 100%;
+					font-size: 24rpx;
+					line-height: 24rpx;
+					position: absolute;
+					left: 0;
+					transform: scale(0.8);
+					transform-origin: center center;
+					text-align: center;
+					bottom: 8rpx;
+					z-index: 2;
+				}
+			}
+			
+			&__bg-month {
+				position: absolute;
+				font-size: 130px;
+				line-height: 130px;
+				left: 50%;
+				top: 50%;
+				transform: translate(-50%, -50%);
+				color: #e4e7ed;
+				z-index: 1;
+			}
+		}
+	
+		&__bottom {
+			width: 100%;
+			@include vue-flex;
+			align-items: center;
+			justify-content: center;
+			flex-direction: column;
+			background-color: #fff;
+			padding: 0 40rpx 30rpx;
+			box-sizing: border-box;
+			font-size: 24rpx;
+			color: $u-tips-color;
+			
+			&__choose {
+				height: 50rpx;
+			}
+			
+			&__btn {
+				width: 100%;
+			}
+		}
+	}
+</style>

+ 257 - 0
uview-ui/components/u-car-keyboard/u-car-keyboard.vue

xqd
@@ -0,0 +1,257 @@
+<template>
+	<view class="u-keyboard" @touchmove.stop.prevent="() => {}">
+		<view class="u-keyboard-grids">
+			<block>
+				<view class="u-keyboard-grids-item" v-for="(group, i) in abc ? EngKeyBoardList : areaList" :key="i">
+					<view :hover-stay-time="100" @tap="carInputClick(i, j)" hover-class="u-carinput-hover" class="u-keyboard-grids-btn"
+					 v-for="(item, j) in group" :key="j">
+						{{ item }}
+					</view>
+				</view>
+				<view @touchstart="backspaceClick" @touchend="clearTimer" :hover-stay-time="100" class="u-keyboard-back"
+				 hover-class="u-hover-class">
+					<u-icon :size="38" name="backspace" :bold="true"></u-icon>
+				</view>
+				<view :hover-stay-time="100" class="u-keyboard-change" hover-class="u-carinput-hover" @tap="changeCarInputMode">
+					<text class="zh" :class="[!abc ? 'active' : 'inactive']">中</text>
+					/
+					<text class="en" :class="[abc ? 'active' : 'inactive']">英</text>
+				</view>
+			</block>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		name: "u-keyboard",
+		props: {
+			// 是否打乱键盘按键的顺序
+			random: {
+				type: Boolean,
+				default: false
+			}
+		},
+		data() {
+			return {
+				// 车牌输入时,abc=true为输入车牌号码,bac=false为输入省份中文简称
+				abc: false
+			};
+		},
+		computed: {
+			areaList() {
+				let data = [
+					'京',
+					'沪',
+					'粤',
+					'津',
+					'冀',
+					'豫',
+					'云',
+					'辽',
+					'黑',
+					'湘',
+					'皖',
+					'鲁',
+					'苏',
+					'浙',
+					'赣',
+					'鄂',
+					'桂',
+					'甘',
+					'晋',
+					'陕',
+					'蒙',
+					'吉',
+					'闽',
+					'贵',
+					'渝',
+					'川',
+					'青',
+					'琼',
+					'宁',
+					'挂',
+					'藏',
+					'港',
+					'澳',
+					'新',
+					'使',
+					'学'
+				];
+				let tmp = [];
+				// 打乱顺序
+				if (this.random) data = this.$u.randomArray(data);
+				// 切割成二维数组
+				tmp[0] = data.slice(0, 10);
+				tmp[1] = data.slice(10, 20);
+				tmp[2] = data.slice(20, 30);
+				tmp[3] = data.slice(30, 36);
+				return tmp;
+			},
+			EngKeyBoardList() {
+				let data = [
+					1,
+					2,
+					3,
+					4,
+					5,
+					6,
+					7,
+					8,
+					9,
+					0,
+					'Q',
+					'W',
+					'E',
+					'R',
+					'T',
+					'Y',
+					'U',
+					'I',
+					'O',
+					'P',
+					'A',
+					'S',
+					'D',
+					'F',
+					'G',
+					'H',
+					'J',
+					'K',
+					'L',
+					'Z',
+					'X',
+					'C',
+					'V',
+					'B',
+					'N',
+					'M'
+				];
+				let tmp = [];
+				if (this.random) data = this.$u.randomArray(data);
+				tmp[0] = data.slice(0, 10);
+				tmp[1] = data.slice(10, 20);
+				tmp[2] = data.slice(20, 30);
+				tmp[3] = data.slice(30, 36);
+				return tmp;
+			}
+		},
+		methods: {
+			// 点击键盘按钮
+			carInputClick(i, j) {
+				let value = '';
+				// 不同模式,获取不同数组的值
+				if (this.abc) value = this.EngKeyBoardList[i][j];
+				else value = this.areaList[i][j];
+				this.$emit('change', value);
+			},
+			// 修改汽车牌键盘的输入模式,中文|英文
+			changeCarInputMode() {
+				this.abc = !this.abc;
+			},
+			// 点击退格键
+			backspaceClick() {
+				this.$emit('backspace');
+				clearInterval(this.timer); //再次清空定时器,防止重复注册定时器
+				this.timer = null;
+				this.timer = setInterval(() => {
+					this.$emit('backspace');
+				}, 250);
+			},
+			clearTimer() {
+				clearInterval(this.timer);
+				this.timer = null;
+			},
+		}
+	};
+</script>
+
+<style lang="scss" scoped>
+	@import "../../libs/css/style.components.scss";
+
+	.u-keyboard-grids {
+		background: rgb(215, 215, 217);
+		padding: 24rpx 0;
+		position: relative;
+	}
+
+	.u-keyboard-grids-item {
+		@include vue-flex;
+		align-items: center;
+		justify-content: center;
+	}
+
+	.u-keyboard-grids-btn {
+		text-decoration: none;
+		width: 62rpx;
+		flex: 0 0 64rpx;
+		height: 80rpx;
+		/* #ifndef APP-NVUE */
+		display: inline-flex;		
+		/* #endif */
+		font-size: 36rpx;
+		text-align: center;
+		line-height: 80rpx;
+		background-color: #fff;
+		margin: 8rpx 5rpx;
+		border-radius: 8rpx;
+		box-shadow: 0 2rpx 0rpx #888992;
+		font-weight: 500;
+		justify-content: center;
+	}
+
+	.u-carinput-hover {
+		background-color: rgb(185, 188, 195) !important;
+	}
+
+	.u-keyboard-back {
+		position: absolute;
+		width: 96rpx;
+		right: 22rpx;
+		bottom: 32rpx;
+		height: 80rpx;
+		background-color: rgb(185, 188, 195);
+		@include vue-flex;
+		align-items: center;
+		border-radius: 8rpx;
+		justify-content: center;
+		box-shadow: 0 2rpx 0rpx #888992;
+	}
+
+	.u-keyboard-change {
+		font-size: 24rpx;
+		box-shadow: 0 2rpx 0rpx #888992;
+		position: absolute;
+		width: 96rpx;
+		left: 22rpx;
+		line-height: 1;
+		bottom: 32rpx;
+		height: 80rpx;
+		background-color: #ffffff;
+		@include vue-flex;
+		align-items: center;
+		border-radius: 8rpx;
+		justify-content: center;
+	}
+
+	.u-keyboard-change .inactive.zh {
+		transform: scale(0.85) translateY(-10rpx);
+	}
+
+	.u-keyboard-change .inactive.en {
+		transform: scale(0.85) translateY(10rpx);
+	}
+
+	.u-keyboard-change .active {
+		color: rgb(237, 112, 64);
+		font-size: 30rpx;
+	}
+
+	.u-keyboard-change .zh {
+		transform: translateY(-10rpx);
+	}
+
+	.u-keyboard-change .en {
+		transform: translateY(10rpx);
+	}
+</style>

+ 299 - 0
uview-ui/components/u-card/u-card.vue

xqd
@@ -0,0 +1,299 @@
+<template>
+	<view
+		class="u-card"
+		@tap.stop="click"
+		:class="{ 'u-border': border, 'u-card-full': full, 'u-card--border': borderRadius > 0 }"
+		:style="{
+			borderRadius: borderRadius + 'rpx',
+			margin: margin,
+			boxShadow: boxShadow
+		}"
+	>
+		<view
+			v-if="showHead"
+			class="u-card__head"
+			:style="[{padding: padding + 'rpx'}, headStyle]"
+			:class="{
+				'u-border-bottom': headBorderBottom
+			}"
+			@tap="headClick"
+		>
+			<view v-if="!$slots.head" class="u-flex u-row-between">
+				<view class="u-card__head--left u-flex u-line-1" v-if="title">
+					<image
+						:src="thumb"
+						class="u-card__head--left__thumb"
+						mode="aspectfull"
+						v-if="thumb"
+						:style="{ 
+							height: thumbWidth + 'rpx', 
+							width: thumbWidth + 'rpx', 
+							borderRadius: thumbCircle ? '100rpx' : '6rpx' 
+						}"
+					></image>
+					<text
+						class="u-card__head--left__title u-line-1"
+						:style="{
+							fontSize: titleSize + 'rpx',
+							color: titleColor
+						}"
+					>
+						{{ title }}
+					</text>
+				</view>
+				<view class="u-card__head--right u-line-1" v-if="subTitle">
+					<text
+						class="u-card__head__title__text"
+						:style="{
+							fontSize: subTitleSize + 'rpx',
+							color: subTitleColor
+						}"
+					>
+						{{ subTitle }}
+					</text>
+				</view>
+			</view>
+			<slot name="head" v-else />
+		</view>
+		<view @tap="bodyClick" class="u-card__body" :style="[{padding: padding + 'rpx'}, bodyStyle]"><slot name="body" /></view>
+		<view
+			v-if="showFoot"
+			class="u-card__foot"
+			 @tap="footClick"
+			:style="[{padding: $slots.foot ? padding + 'rpx' : 0}, footStyle]"
+			:class="{
+				'u-border-top': footBorderTop
+			}"
+		>
+			<slot name="foot" />
+		</view>
+	</view>
+</template>
+
+<script>
+/**
+ * card 卡片
+ * @description 卡片组件一般用于多个列表条目,且风格统一的场景
+ * @tutorial https://www.uviewui.com/components/card.html
+ * @property {Boolean} full 卡片与屏幕两侧是否留空隙(默认false)
+ * @property {String} title 头部左边的标题
+ * @property {String} title-color 标题颜色(默认#303133)
+ * @property {String | Number} title-size 标题字体大小,单位rpx(默认30)
+ * @property {String} sub-title 头部右边的副标题
+ * @property {String} sub-title-color 副标题颜色(默认#909399)
+ * @property {String | Number} sub-title-size 副标题字体大小(默认26)
+ * @property {Boolean} border 是否显示边框(默认true)
+ * @property {String | Number} index 用于标识点击了第几个卡片
+ * @property {String} box-shadow 卡片外围阴影,字符串形式(默认none)
+ * @property {String} margin 卡片与屏幕两边和上下元素的间距,需带单位,如"30rpx 20rpx"(默认30rpx)
+ * @property {String | Number} border-radius 卡片整体的圆角值,单位rpx(默认16)
+ * @property {Object} head-style 头部自定义样式,对象形式
+ * @property {Object} body-style 中部自定义样式,对象形式
+ * @property {Object} foot-style 底部自定义样式,对象形式
+ * @property {Boolean} head-border-bottom 是否显示头部的下边框(默认true)
+ * @property {Boolean} foot-border-top 是否显示底部的上边框(默认true)
+ * @property {Boolean} show-head 是否显示头部(默认true)
+ * @property {Boolean} show-head 是否显示尾部(默认true)
+ * @property {String} thumb 缩略图路径,如设置将显示在标题的左边,不建议使用相对路径
+ * @property {String | Number} thumb-width 缩略图的宽度,高等于宽,单位rpx(默认60)
+ * @property {Boolean} thumb-circle 缩略图是否为圆形(默认false)
+ * @event {Function} click 整个卡片任意位置被点击时触发
+ * @event {Function} head-click 卡片头部被点击时触发
+ * @event {Function} body-click 卡片主体部分被点击时触发
+ * @event {Function} foot-click 卡片底部部分被点击时触发
+ * @example <u-card padding="30" title="card"></u-card>
+ */
+export default {
+	name: 'u-card',
+	props: {
+		// 与屏幕两侧是否留空隙
+		full: {
+			type: Boolean,
+			default: false
+		},
+		// 标题
+		title: {
+			type: String,
+			default: ''
+		},
+		// 标题颜色
+		titleColor: {
+			type: String,
+			default: '#303133'
+		},
+		// 标题字体大小,单位rpx
+		titleSize: {
+			type: [Number, String],
+			default: '30'
+		},
+		// 副标题
+		subTitle: {
+			type: String,
+			default: ''
+		},
+		// 副标题颜色
+		subTitleColor: {
+			type: String,
+			default: '#909399'
+		},
+		// 副标题字体大小,单位rpx
+		subTitleSize: {
+			type: [Number, String],
+			default: '26'
+		},
+		// 是否显示外部边框,只对full=false时有效(卡片与边框有空隙时)
+		border: {
+			type: Boolean,
+			default: true
+		},
+		// 用于标识点击了第几个
+		index: {
+			type: [Number, String, Object],
+			default: ''
+		},
+		// 用于隔开上下左右的边距,带单位的写法,如:"30rpx 30rpx","20rpx 20rpx 30rpx 30rpx"
+		margin: {
+			type: String,
+			default: '30rpx'
+		},
+		// card卡片的圆角
+		borderRadius: {
+			type: [Number, String],
+			default: '16'
+		},
+		// 头部自定义样式,对象形式
+		headStyle: {
+			type: Object,
+			default() {
+				return {};
+			}
+		},
+		// 主体自定义样式,对象形式
+		bodyStyle: {
+			type: Object,
+			default() {
+				return {};
+			}
+		},
+		// 底部自定义样式,对象形式
+		footStyle: {
+			type: Object,
+			default() {
+				return {};
+			}
+		},
+		// 头部是否下边框
+		headBorderBottom: {
+			type: Boolean,
+			default: true
+		},
+		// 底部是否有上边框
+		footBorderTop: {
+			type: Boolean,
+			default: true
+		},
+		// 标题左边的缩略图
+		thumb: {
+			type: String,
+			default: ''
+		},
+		// 缩略图宽高,单位rpx
+		thumbWidth: {
+			type: [String, Number],
+			default: '60'
+		},
+		// 缩略图是否为圆形
+		thumbCircle: {
+			type: Boolean,
+			default: false
+		},
+		// 给head,body,foot的内边距
+		padding: {
+			type: [String, Number],
+			default: '30'
+		},
+		// 是否显示头部
+		showHead: {
+			type: Boolean,
+			default: true
+		},
+		// 是否显示尾部
+		showFoot: {
+			type: Boolean,
+			default: true
+		},
+		// 卡片外围阴影,字符串形式
+		boxShadow: {
+			type: String,
+			default: 'none'
+		}
+	},
+	data() {
+		return {};
+	},
+	methods: {
+		click() {
+			this.$emit('click', this.index);
+		},
+		headClick() {
+			this.$emit('head-click', this.index);
+		},
+		bodyClick() {
+			this.$emit('body-click', this.index);
+		},
+		footClick() {
+			this.$emit('foot-click', this.index);
+		}
+	}
+};
+</script>
+
+<style lang="scss" scoped>
+@import "../../libs/css/style.components.scss";
+	
+.u-card {
+	position: relative;
+	overflow: hidden;
+	font-size: 28rpx;
+	background-color: #ffffff;
+	box-sizing: border-box;
+	
+	&-full {
+		// 如果是与屏幕之间不留空隙,应该设置左右边距为0
+		margin-left: 0 !important;
+		margin-right: 0 !important;
+		width: 100%;
+	}
+	
+	&--border:after {
+		border-radius: 16rpx;
+	}
+
+	&__head {
+		&--left {
+			color: $u-main-color;
+			
+			&__thumb {
+				margin-right: 16rpx;
+			}
+			
+			&__title {
+				max-width: 400rpx;
+			}
+		}
+
+		&--right {
+			color: $u-tips-color;
+			margin-left: 6rpx;
+		}
+	}
+
+	&__body {
+		color: $u-content-color;
+	}
+
+	&__foot {
+		color: $u-tips-color;
+	}
+}
+</style>

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