周剑思 3 years ago
parent
commit
597566d992
100 changed files with 37620 additions and 0 deletions
  1. 4 0
      .gitignore
  2. 135 0
      App.vue
  3. 21 0
      LICENSE
  4. 262 0
      common/airport.js
  5. 2 0
      common/bus.js
  6. 1204 0
      common/commonMethod.js
  7. 195 0
      common/commonPageMethod.js
  8. 27 0
      common/css/common.css
  9. 179 0
      common/css/templateCommon.css
  10. 398 0
      common/font/iconfont.css
  11. 97 0
      common/graceChecker.js
  12. 352 0
      common/html-parser.js
  13. 90 0
      common/mixins/concern.js
  14. 256 0
      common/mixins/templateMethod.js
  15. 245 0
      common/permission.js
  16. 3 0
      common/static/crypto-js.min.js
  17. 20 0
      common/static/customicons.css
  18. BIN
      common/static/customicons.ttf
  19. 463 0
      common/static/im/newIM_init.js
  20. 284 0
      common/static/im/tim-handler.js
  21. 1 0
      common/static/im/tim-wx.js
  22. 1 0
      common/static/json/wow-emoji.json
  23. 76 0
      common/static/lib/intersection-observer.js
  24. 10 0
      common/static/lib/lottie-miniprogram.js
  25. 7 0
      common/static/lib/promisify.js
  26. 160 0
      common/static/lib/report.js
  27. 622 0
      common/static/lib/runtime.js
  28. 245 0
      common/static/templateMethod.js
  29. 136 0
      common/uni-nvue.css
  30. 1458 0
      common/uni.css
  31. 73 0
      common/util.js
  32. 181 0
      components/amap-wx/js/util.js
  33. 1 0
      components/amap-wx/lib/amap-wx.js
  34. 156 0
      components/api-set-tabbar.nvue
  35. 1 0
      components/marked/index.js
  36. 1573 0
      components/marked/lib/marked.js
  37. 12542 0
      components/mpvue-citypicker/city-data/area.js
  38. 1503 0
      components/mpvue-citypicker/city-data/city.js
  39. 139 0
      components/mpvue-citypicker/city-data/province.js
  40. 230 0
      components/mpvue-citypicker/mpvueCityPicker.vue
  41. 123 0
      components/mpvue-echarts/src/echarts.vue
  42. 73 0
      components/mpvue-echarts/src/wx-canvas.js
  43. 483 0
      components/mpvue-picker/mpvuePicker.vue
  44. 175 0
      components/mpvueGestureLock/gestureLock.js
  45. 138 0
      components/mpvueGestureLock/index.vue
  46. 38 0
      components/page-foot/page-foot.vue
  47. 16 0
      components/page-head/page-head.vue
  48. 66 0
      components/product.vue
  49. 175 0
      components/tab-nvue/mediaList.vue
  50. 5046 0
      components/u-charts/u-charts.js
  51. 59 0
      components/u-link/u-link.vue
  52. 140 0
      components/uni-section/uni-section.vue
  53. 88 0
      hybrid/html/local.html
  54. 14 0
      index.html
  55. 39 0
      main.js
  56. 144 0
      manifest.json
  57. 106 0
      package.json
  58. 1338 0
      pages.json
  59. 60 0
      pages/API/action-sheet/action-sheet.vue
  60. 102 0
      pages/API/add-phone-contact/add-phone-contact.vue
  61. 125 0
      pages/API/animation/animation.vue
  62. 163 0
      pages/API/background-audio/background-audio.vue
  63. 723 0
      pages/API/bluetooth/bluetooth.vue
  64. 86 0
      pages/API/brightness/brightness.vue
  65. 366 0
      pages/API/canvas/canvas.vue
  66. 62 0
      pages/API/choose-location/choose-location.vue
  67. 91 0
      pages/API/clipboard/clipboard.vue
  68. 63 0
      pages/API/download-file/download-file.vue
  69. 129 0
      pages/API/file/file.vue
  70. 84 0
      pages/API/full-screen-video-ad/full-screen-video-ad.vue
  71. 186 0
      pages/API/get-location/get-location.vue
  72. 86 0
      pages/API/get-network-type/get-network-type.vue
  73. 117 0
      pages/API/get-node-info/get-node-info.vue
  74. 148 0
      pages/API/get-system-info/get-system-info.vue
  75. 165 0
      pages/API/get-user-info/get-user-info.vue
  76. 300 0
      pages/API/ibeacon/ibeacon.vue
  77. 239 0
      pages/API/image/image.vue
  78. 124 0
      pages/API/inner-audio/inner-audio.vue
  79. 69 0
      pages/API/intersection-observer/intersection-observer.vue
  80. 322 0
      pages/API/login/login.vue
  81. 50 0
      pages/API/make-phone-call/make-phone-call.vue
  82. 102 0
      pages/API/map-search/map-search.nvue
  83. 448 0
      pages/API/map/map.nvue
  84. 40 0
      pages/API/modal/modal.vue
  85. 105 0
      pages/API/navigator/navigator.vue
  86. 83 0
      pages/API/navigator/new-page/new-nvue-page-1.nvue
  87. 69 0
      pages/API/navigator/new-page/new-nvue-page-2.nvue
  88. 108 0
      pages/API/navigator/new-page/new-vue-page-1.vue
  89. 84 0
      pages/API/navigator/new-page/new-vue-page-2.vue
  90. 62 0
      pages/API/on-accelerometer-change/on-accelerometer-change.vue
  91. 91 0
      pages/API/on-compass-change/on-compass-change.vue
  92. 75 0
      pages/API/open-location/open-location.vue
  93. 83 0
      pages/API/pull-down-refresh/pull-down-refresh.vue
  94. 256 0
      pages/API/request-payment/request-payment.vue
  95. 176 0
      pages/API/request/request.vue
  96. 109 0
      pages/API/rewarded-video-ad/rewarded-video-ad.vue
  97. 158 0
      pages/API/save-media/save-media.vue
  98. 76 0
      pages/API/scan-code/scan-code.vue
  99. 22 0
      pages/API/set-navigation-bar-title/set-navigation-bar-title.test.js
  100. 0 0
      pages/API/set-navigation-bar-title/set-navigation-bar-title.vue

+ 4 - 0
.gitignore

@@ -0,0 +1,4 @@
+unpackage/dist
+.hbuilderx
+uni_modules
+changelog.md

+ 135 - 0
App.vue

@@ -0,0 +1,135 @@
+<script>
+	import {
+		mapMutations
+	} from 'vuex'
+	import {
+		version
+	} from './package.json'
+	import checkUpdate from '@/uni_modules/uni-upgrade-center-app/utils/check-update';
+
+	export default {
+		onLaunch: function() {
+			// #ifdef H5
+			console.log(
+				`%c hello uniapp %c v${version} `,
+				'background:#35495e ; padding: 1px; border-radius: 3px 0 0 3px;  color: #fff',
+				'background:#007aff ;padding: 1px; border-radius: 0 3px 3px 0;  color: #fff; font-weight: bold;'
+			)
+			// #endif
+			// 线上示例使用
+			// console.log('%c uni-app官方团队诚邀优秀前端工程师加盟,一起打造更卓越的uni-app & uniCloud,欢迎投递简历到 hr2013@dcloud.io', 'color: red');
+			console.log('App Launch');
+			// #ifdef APP-PLUS
+			// App平台检测升级,服务端代码是通过uniCloud的云函数实现的,详情可参考:https://ext.dcloud.net.cn/plugin?id=4542
+			if (plus.runtime.appid !== 'HBuilder') { // 真机运行不需要检查更新,真机运行时appid固定为'HBuilder',这是调试基座的appid
+				checkUpdate()
+			}
+
+			// 一键登录预登陆,可以显著提高登录速度
+			uni.preLogin({
+				provider: 'univerify',
+				success: (res) => {
+					// 成功
+					this.setUniverifyErrorMsg();
+					console.log("preLogin success: ", res);
+				},
+				fail: (res) => {
+					this.setUniverifyLogin(false);
+					this.setUniverifyErrorMsg(res.errMsg);
+					// 失败
+					console.log("preLogin fail res: ", res);
+				}
+			})
+			// #endif
+		},
+		onShow: function() {
+			console.log('App Show')
+		},
+		onHide: function() {
+			console.log('App Hide')
+		},
+		globalData: {
+			test: ''
+		},
+		methods: {
+			...mapMutations(['setUniverifyErrorMsg', 'setUniverifyLogin'])
+		}
+	}
+</script>
+
+<style>
+	/* #ifndef APP-PLUS-NVUE */
+	/* uni.css - 通用组件、模板样式库,可以当作一套ui库应用 */
+	@import './common/uni.css';
+	@import '@/static/customicons.css';
+	/* H5 兼容 pc 所需 */
+	/* #ifdef H5 */
+	@media screen and (min-width: 768px) {
+		body {
+			overflow-y: scroll;
+		}
+	}
+
+	/* 顶栏通栏样式 */
+	/* .uni-top-window {
+	    left: 0;
+	    right: 0;
+	} */
+
+	uni-page-body {
+		background-color: #F5F5F5 !important;
+		min-height: 100% !important;
+		height: auto !important;
+	}
+
+	.uni-top-window uni-tabbar .uni-tabbar {
+		background-color: #fff !important;
+	}
+
+	.uni-app--showleftwindow .hideOnPc {
+		display: none !important;
+	}
+
+	/* #endif */
+
+	/* 以下样式用于 hello uni-app 演示所需 */
+	page {
+		background-color: #efeff4;
+		height: 100%;
+		font-size: 28rpx;
+		/* line-height: 1.8; */
+	}
+
+	.fix-pc-padding {
+		padding: 0 50px;
+	}
+
+	.uni-header-logo {
+		padding: 30rpx;
+		flex-direction: column;
+		justify-content: center;
+		align-items: center;
+		margin-top: 10rpx;
+	}
+
+	.uni-header-image {
+		width: 100px;
+		height: 100px;
+	}
+
+	.uni-hello-text {
+		color: #7A7E83;
+	}
+
+	.uni-hello-addfile {
+		text-align: center;
+		line-height: 300rpx;
+		background: #FFF;
+		padding: 50rpx;
+		margin-top: 10px;
+		font-size: 38rpx;
+		color: #808080;
+	}
+
+	/* #endif*/
+</style>

+ 21 - 0
LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2018 DCloud
+
+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.

+ 262 - 0
common/airport.js

@@ -0,0 +1,262 @@
+export default {
+    "list": [{
+        "letter": "A",
+        "data": [
+            "阿克苏机场",
+            "阿拉山口机场",
+            "阿勒泰机场",
+            "阿里昆莎机场",
+            "安庆天柱山机场",
+            "澳门国际机场"
+        ]
+    }, {
+        "letter": "B",
+        "data": [
+            "保山机场",
+            "包头机场",
+            "北海福成机场",
+            "北京南苑机场",
+            "北京首都国际机场"
+        ]
+    }, {
+        "letter": "C",
+        "data": [
+            "长白山机场",
+            "长春龙嘉国际机场",
+            "常德桃花源机场",
+            "昌都邦达机场",
+            "长沙黄花国际机场",
+            "长治王村机场",
+            "常州奔牛机场",
+            "成都双流国际机场",
+            "赤峰机场"
+        ]
+    }, {
+        "letter": "D",
+        "data": [
+            "大理机场",
+            "大连周水子国际机场",
+            "大庆萨尔图机场",
+            "大同东王庄机场",
+            "达州河市机场",
+            "丹东浪头机场",
+            "德宏芒市机场",
+            "迪庆香格里拉机场",
+            "东营机场",
+            "敦煌机场"
+        ]
+    }, {
+        "letter": "E",
+        "data": [
+            "鄂尔多斯机场",
+            "恩施许家坪机场",
+            "二连浩特赛乌苏国际机场"
+        ]
+    }, {
+        "letter": "F",
+        "data": [
+            "阜阳西关机场",
+            "福州长乐国际机场"
+        ]
+    }, {
+        "letter": "G",
+        "data": [
+            "赣州黄金机场",
+            "格尔木机场",
+            "固原六盘山机场",
+            "广元盘龙机场",
+            "广州白云国际机场",
+            "桂林两江国际机场",
+            "贵阳龙洞堡国际机场"
+        ]
+    }, {
+        "letter": "H",
+        "data": [
+            "哈尔滨太平国际机场",
+            "哈密机场",
+            "海口美兰国际机场",
+            "海拉尔东山国际机场",
+            "邯郸机场",
+            "汉中机场",
+            "杭州萧山国际机场",
+            "合肥骆岗国际机场",
+            "和田机场",
+            "黑河机场",
+            "呼和浩特白塔国际机场",
+            "淮安涟水机场",
+            "黄山屯溪国际机场"
+        ]
+    }, {
+        "letter": "I",
+        "data": []
+    }, {
+        "letter": "J",
+        "data": [
+            "济南遥墙国际机场",
+            "济宁曲阜机场",
+            "鸡西兴凯湖机场",
+            "佳木斯东郊机场",
+            "嘉峪关机场",
+            "锦州小岭子机场",
+            "景德镇机场",
+            "井冈山机场",
+            "九江庐山机场",
+            "九寨黄龙机场"
+        ]
+    }, {
+        "letter": "K",
+        "data": [
+            "喀什机场",
+            "克拉玛依机场",
+            "库车龟兹机场",
+            "库尔勒机场",
+            "昆明巫家坝国际机场"
+        ]
+    }, {
+        "letter": "L",
+        "data": [
+            "拉萨贡嘎机场",
+            "兰州中川机场",
+            "丽江三义机场",
+            "黎平机场",
+            "连云港白塔埠机场",
+            "临沧机场",
+            "临沂机场",
+            "林芝米林机场",
+            "柳州白莲机场",
+            "龙岩冠豸山机场",
+            "泸州蓝田机场",
+            "洛阳北郊机场"
+        ]
+    }, {
+        "letter": "M",
+        "data": [
+            "满洲里西郊机场",
+            "绵阳南郊机场",
+            "漠河古莲机场",
+            "牡丹江海浪机场"
+        ]
+    }, {
+        "letter": "N",
+        "data": [
+            "南昌昌北国际机场",
+            "南充高坪机场",
+            "南京禄口国际机场",
+            "南宁吴圩机场",
+            "南通兴东机场",
+            "南阳姜营机场",
+            "宁波栎社国际机场"
+        ]
+    }, {
+        "letter": "O",
+        "data": []
+    }, {
+        "letter": "P",
+        "data": [
+            "普洱思茅机场"
+        ]
+    }, {
+        "letter": "Q",
+        "data": [
+            "齐齐哈尔三家子机场",
+            "秦皇岛山海关机场",
+            "青岛流亭国际机场",
+            "衢州机场",
+            "泉州晋江机场"
+        ]
+    }, {
+        "letter": "R",
+        "data": [
+            "日喀则和平机场"
+        ]
+    }, {
+        "letter": "S",
+        "data": [
+            "三亚凤凰国际机场",
+            "汕头外砂机场",
+            "上海虹桥国际机场",
+            "上海浦东国际机场",
+            "深圳宝安国际机场",
+            "沈阳桃仙国际机场",
+            "石家庄正定国际机场",
+            "苏南硕放国际机场"
+        ]
+    }, {
+        "letter": "T",
+        "data": [
+            "塔城机场",
+            "太原武宿国际机场",
+            "台州路桥机场 (黄岩机场)",
+            "唐山三女河机场",
+            "腾冲驼峰机场",
+            "天津滨海国际机场",
+            "通辽机场",
+            "铜仁凤凰机场"
+        ]
+    }, {
+        "letter": "U",
+        "data": []
+    }, {
+        "letter": "V",
+        "data": []
+    }, {
+        "letter": "W",
+        "data": [
+            "万州五桥机场",
+            "潍坊机场",
+            "威海大水泊机场",
+            "文山普者黑机场",
+            "温州永强国际机场",
+            "乌海机场",
+            "武汉天河国际机场",
+            "乌兰浩特机场",
+            "乌鲁木齐地窝堡国际机场",
+            "武夷山机场",
+            "梧州长洲岛机场"
+        ]
+    }, {
+        "letter": "X",
+        "data": [
+            "西安咸阳国际机场",
+            "西昌青山机场",
+            "锡林浩特机场",
+            "西宁曹家堡机场",
+            "西双版纳嘎洒机场",
+            "厦门高崎国际机场",
+            "香港国际机场",
+            "襄阳刘集机场",
+            "兴义机场",
+            "徐州观音机场"
+        ]
+    }, {
+        "letter": "Y",
+        "data": [
+            "延安二十里堡机场",
+            "盐城机场",
+            "延吉朝阳川机场",
+            "烟台莱山国际机场",
+            "宜宾菜坝机场",
+            "宜昌三峡机场",
+            "伊春林都机场",
+            "伊宁机场",
+            "义乌机场",
+            "银川河东机场",
+            "永州零陵机场",
+            "榆林榆阳机场",
+            "玉树巴塘机场",
+            "运城张孝机场"
+        ]
+    }, {
+        "letter": "Z",
+        "data": [
+            "湛江机场",
+            "昭通机场",
+            "郑州新郑国际机场",
+            "芷江机场",
+            "重庆江北国际机场",
+            "中卫香山机场",
+            "舟山朱家尖机场",
+            "珠海三灶机场"
+        ]
+    }]
+}

+ 2 - 0
common/bus.js

@@ -0,0 +1,2 @@
+import Vue from 'vue';  
+export default new Vue(); 

File diff suppressed because it is too large
+ 1204 - 0
common/commonMethod.js


+ 195 - 0
common/commonPageMethod.js

@@ -0,0 +1,195 @@
+var app = getApp(); //获取应用实例
+const util = require('@/static/utils/util.js');
+const config = require('@/static/config.js');
+import requestConfig from '@/static/lib/requestConfig';
+export default {
+	data() {
+		return {
+			isSendImg: false,	//当前页面是否正在处于选择本地图片的状态-选择图片会触发小程序的隐藏事件,需要单独处理
+			previewFlag: false,	//当前页面是否预览了图片-预览图片会触发小程序的隐藏事件,需要单独处理
+			loadPromise: new Promise((resolve) => {
+				this.loadedCompleteFun = function() {
+					console.log('onload结束');
+					resolve()
+				}
+			}),
+		}
+	},
+	watch: {},
+    async onLoad(options) {
+        let shareCity = ''; //分享携带的城市信息
+        let houseId = '';   //分享携带的项目id
+        let shareUserId = '';   //分享携带的助力发起人id
+        if (!options) { //如果不存在则直接返回
+            this.loadedCompleteFun();//通告加载已经结束
+            return false;
+        }
+        // let lastPage = getCurrentPages()[getCurrentPages().length-2] ? getCurrentPages()[getCurrentPages().length-2].$vm : null;
+        // if(lastPage){
+            // if(lastPage.componentId){
+            //     options.componentId = lastPage.componentId;
+            // }
+            // if(lastPage.componentType){
+            //     options.componentType = lastPage.componentType;
+            // }
+            // if(lastPage.diyArea){
+            //     options.diyArea = lastPage.diyArea;
+            // }
+        // }
+        if(app.globalData.componentId){
+            options.componentId = app.globalData.componentId;
+            options.componentType = app.globalData.componentType;
+            options.diyArea = app.globalData.diyArea;
+            app.globalData.componentId = '';//清空该值
+            app.globalData.componentType = '';//清空该值
+            app.globalData.diyArea = false;//清空该值
+        }
+        this.pvCurPageParams = JSON.stringify(options);
+        
+        if (options.gdt_vid) { //从朋友圈广告进入的带有clickid
+            app.globalData.clickId = options.gdt_vid;
+        }
+        if (options.scene) { //如果存在该参数,则说明是通过数量无限制的小程序码进来的
+            const scene = decodeURIComponent(options.scene); // scene 需要使用 decodeURIComponent 才能获取到生成二维码时传入的 scene
+            options.shareToken = scene;     //说明用户携带分享信息
+            console.log('被scene先覆盖了')
+        }
+        //将渠道存至全局,留电接口需要用
+        if (options.shareToken && options.shareToken != "null" && options.shareToken != "undefined") {
+            app.globalData.fromChannel = options.shareToken;
+            // #ifdef H5
+            var userInfo = uni.getStorageSync('userInfo');
+            if(userInfo){
+                userInfo.fromChannel = options.shareToken;
+                uni.setStorageSync('userInfo', userInfo);
+            }
+            // #endif
+        }
+        // 解密对应的客户来源-同时防止重复解密
+        if (app.globalData.fromChannel && app.globalData.oldFromChannel != app.globalData.fromChannel) {
+            console.log("***开始解密来源信息***", app.globalData.fromChannel);
+            var param = {
+                shareSign: app.globalData.fromChannel
+            };
+            const res = await requestConfig('decryptShareSign', param, true);
+            console.log("***decryptHandle***", res);
+            if (res && res.success) {
+                app.globalData.oldFromChannel = app.globalData.fromChannel;//解密成功后记录
+                console.log("***解密来源信息成功***", res.single);
+                if (res.single) {
+                    // app.globalData.shareId = res.single.customerId;
+                    if (res.single.attrs) {// attrs 是字符串形式存储在数据库服务器上
+                        res.single.attrs = JSON.parse(res.single.attrs);
+                    }
+                    shareCity = res.single.attrs ? res.single.attrs.shareCity : '';//赋值分享者城市
+                    shareUserId = res.single.attrs ? res.single.attrs.shareUserId : '';
+                    houseId = res.single.houseId || options.houseId || '';
+                    app.globalData.exchangedFromChannel = JSON.stringify(res.single);//解密后的明文
+                    app.globalData.isZhuanFaFromProject = houseId;
+                }
+            } else {
+                let trackparam = {
+                    type: 'mini-program-Error', //埋点类型
+                    pvCurPageName: 'app.js-decrypt', //当前页面名称
+                    expand: JSON.stringify(res) + ";brandId=" + config.brandId + ";param=" + JSON.stringify(
+                        param), //扩展字段
+                };
+                util.trackRequest(trackparam);
+            }
+        }
+        //检查如果不触发解密的情况(相邻几次的秘钥是一样的)
+        //或者 不存在解密时 即 fromChannel 和 oldFromChannel 都为空
+        //则比对最新的城市解密记录情况,匹配的上,则取最新解密记录的城市刷新当前城市
+        if (app.globalData.oldFromChannel == app.globalData.fromChannel) {
+            let single = app.globalData.exchangedFromChannel ? JSON.parse(app.globalData.exchangedFromChannel) : {};
+            shareCity = single.attrs ? single.attrs.shareCity : '';
+            shareUserId = single.attrs ? single.attrs.shareUserId : '';
+        }
+        if(options.shareCity){
+            shareCity = options.shareCity;
+        }
+        if(shareCity){//存在分享城市,则更新为分享人所在的城市
+            app.addCustomerCity(shareCity);
+        }
+        if(options.houseId){
+            houseId = options.houseId;
+        }
+        let currPage = getCurrentPages()[getCurrentPages().length - 1] ? getCurrentPages()[getCurrentPages().length - 1].$vm : null;
+        if(currPage){
+            currPage.shareUserId = shareUserId ? shareUserId : '';
+            currPage.houseId = houseId ? houseId : '';
+        }
+        let shareOpenId = options.shareOpenId ? options.shareOpenId : '';
+        if (app.globalData.exchangedFromChannel) {
+            var _t = JSON.parse(app.globalData.exchangedFromChannel);
+            if (_t) {
+                _t.shareOpenId = shareOpenId;
+                app.globalData.exchangedFromChannel = JSON.stringify(_t);
+            }
+        } else {
+            let params = {
+                shareOpenId: shareOpenId
+            };
+            app.globalData.exchangedFromChannel = JSON.stringify(params);
+        }
+        console.warn("---loadedCompleteFun---")
+        this.loadedCompleteFun();//通告加载已经结束
+    },
+	onShow() {
+		// this.isSendImg = false;		//还原默认值-这里不能还原,因为微信在处理拍照选择图片时,是分两个阶段的
+		// 阶段1:拍照,开始时,会把小程序退到后台,会触发onhide;拍照结束后,会回到小程序的当前页面-页面执行onshow
+		// 阶段2:选择图片,会进入选择图片的原生组件-小程序退到后台执行onhide;图片选择完毕后,会执行onshow
+		// 上述两个阶段存在onshow事件,重置的话,会导致阶段2会执行断开socket的方法-详见App.vue 的onhide方法
+	},
+	onUnload() {
+		console.warn("---onUnload---")
+		app.hidePage();
+	},
+	/**
+	 * 生命周期函数--监听页面隐藏
+	 */
+	onHide: function() {
+		console.warn("---onhide---")
+		app.hidePage();
+	},
+	methods: {
+		// 通告加载完成事件
+		loadedCompleteFun() {},
+		/**
+		 * 获取当前页面的数据,为埋点用
+		 */
+		getCurrentPageParam() {
+			return this.pvCurPageParams
+		},
+        // 处理分享路径的公共方法
+        shareInitPath(){
+            let path = '';
+            let shareToken = app.globalData.shareToken || '';//当前用户的openid
+            let shareOpenId = app.globalData.openid || '';//当前用户的openid
+            let houseId = this.houseId || '';//当前用户的openid
+            let pageId = this.pageId || '';//当前页面的pageId
+            let activityId = this.activityId || '';//当前页面的activityId
+            let liveStreams = this.liveStreams || '';//当前页面的直播地址
+            let layoutId = this.layoutId || '';//当前页面的户型id
+            // 当前的分享参数
+            // 为了判断返回是到项目首页 还是集团页面 同时,也具有判断当前用户是来自分享的功能
+            let fromProject = houseId ? app.globalData.projectShare : app.globalData.brandShare;
+            path = "shareToken=" + shareToken 
+            + "&shareOpenId=" + shareOpenId 
+            + "&houseId=" + houseId 
+            + '&fromProject=' + fromProject
+            + "&pageId=" + pageId;
+            if(activityId){
+                path += "&activityId=" + activityId;
+            }
+            if(liveStreams){
+                path += "&liveStreams=" + liveStreams;
+            }
+            if(layoutId){
+                path += "&layoutId=" + layoutId;
+            }
+            console.warn("***shareInitPath***",path);
+            return path;
+        },
+	}
+}

+ 27 - 0
common/css/common.css

@@ -0,0 +1,27 @@
+.rows{
+    display: flex;
+    flex-direction:row;
+    align-items:center;
+}
+.rows-between{
+    justify-content:space-between;
+}
+.columns{
+    display: flex;
+    flex-direction:column;
+    align-items:center;
+    justify-content:space-between;
+}
+.eslipe {
+	overflow: hidden;
+	white-space: nowrap;
+	text-overflow: ellipsis;
+}
+.flex-start{
+	align-items:flex-start;
+}
+::-webkit-scrollbar{
+	width: 0;
+	height: 0;
+	color: transparent;
+}

+ 179 - 0
common/css/templateCommon.css

@@ -0,0 +1,179 @@
+.rows-left {
+	display: flex;
+	flex-direction: row;
+	justify-content: flex-start;
+	align-items: center;
+}
+.rows-between {
+	display: flex;
+	flex-direction: row;
+	justify-content: space-between;
+	align-items: center;
+}
+.column-flex {
+	display: flex;
+	flex-direction: column;
+    justify-content: center;
+    align-items: center;
+}
+.column-between {
+	display: flex;
+	flex-direction: column;
+	justify-content: space-between;
+	align-items: center;
+}
+.eslipe {
+	overflow: hidden;
+	white-space: nowrap;
+	text-overflow: ellipsis;
+	max-width: 330rpx;
+	display: inline-block;
+}
+.style1 {
+	position: absolute;
+	z-index: 10;
+	width: 100%;
+}
+
+.style2 {
+	position: fixed;
+	top: 0rpx;
+	left: 0rpx;
+	width: 100vw;
+	z-index: 999;
+}
+.scrollview {
+	background: #f5f5f5;
+	position: relative;
+}
+
+.searchBox {
+	position: fixed;
+}
+.nav-title {
+	position: absolute;
+	text-align: center;
+	max-width: 400rpx;
+	overflow: hidden;
+	text-overflow: ellipsis;
+	white-space: nowrap;
+	top: 0;
+	left: 0;
+	right: 0;
+	bottom: 0;
+	margin: auto;
+	color: #000;
+	font-size: 32rpx;
+	font-weight: bold;
+}
+.searchBox .searchView {
+    position: absolute;
+	left: 46rpx;
+	top: 50%;
+	-webkit-transform: translateY(-50%);
+	transform: translateY(-50%);
+    font-size: 28rpx;
+}
+.searchBox image {
+	left: 46rpx;
+	width: 105rpx;
+	height: 40rpx;
+	position: absolute;
+	top: 50%;
+	-webkit-transform: translateY(-50%);
+	transform: translateY(-50%);
+}
+.searchView2 {
+    position: absolute;
+	right: 40rpx;
+	top: 392rpx;
+    width: 72rpx;
+    height: 72rpx;
+    line-height: 72rpx;
+    text-align: center;
+    border-radius: 72rpx;
+    font-size: 28rpx;
+    background-color:#FFFFFF;
+    z-index: 9;
+}
+.shareBox {
+	overflow: hidden;
+	position: relative;
+}
+.shareBox .shareboxt {
+    width: 561rpx;
+    height: 126rpx;
+    margin: 0 auto;
+    display: block;
+    margin-top: 123rpx;
+    margin-bottom: 50rpx;
+}
+.shareBox .logo {
+    width: 258rpx;
+    height: 81rpx;
+    margin: 0 auto;
+    display: block;
+    margin-top: 100rpx;
+    margin-bottom: 16rpx;
+}
+.shareBox .groupName {
+    height:40rpx;
+    font-size:28rpx;
+    font-family:Verdana;
+    font-weight:400;
+    line-height:34rpx;
+    color:rgba(186,186,186,1);
+    text-align: center;
+}
+.shareBox .shareIcon{
+	width: 27rpx;
+	height: 38rpx;
+	display:block;
+}
+.shareBox .shareButton {
+	display: flex;
+	flex-direction: row;
+	justify-content: center;
+	align-items: center;
+	border-radius: 40rpx;
+    width: 280rpx;
+    height: 76rpx;
+    margin: 0 auto;
+    margin-top: 50rpx;
+    margin-bottom: 20rpx;
+	font-size: 28rpx;
+	color:#FFFFFF;
+	font-weight: 400;
+	text-align: center;
+}
+.joinGroup{
+	width: 280rpx;
+	height: 76rpx;
+	line-height: 76rpx;
+	background: #ffffff;
+	border: 2rpx solid #f07423;
+	border-radius: 40rpx;
+	text-align: center;
+	margin: 0 auto;
+}
+.share-box-word{
+	width: 260rpx;
+	height: 36rpx;
+	font-size: 20rpx;
+	font-family: Verdana, Verdana-Regular;
+	font-weight: 400;
+	color: #999999;
+	line-height: 36rpx;
+	margin: 0 auto;
+	margin-top: 26rpx;
+	margin-bottom: 400rpx;
+}
+/* 回到顶部按钮 */
+.backTopView{
+    position: fixed;
+    right: 40rpx;
+    bottom: 100rpx;
+    width: 80rpx;
+    height: 80rpx;
+    z-index: 9999999;
+}

+ 398 - 0
common/font/iconfont.css

@@ -0,0 +1,398 @@
+@font-face {
+  font-family: "iconfont"; /* Project id 2438458 */
+  src: url('https://at.alicdn.com/t/font_2438458_79m17d548kb.woff2?t=1643361721881') format('woff2'),
+       url('https://at.alicdn.com/t/font_2438458_79m17d548kb.woff?t=1643361721881') format('woff'),
+       url('https://at.alicdn.com/t/font_2438458_79m17d548kb.ttf?t=1643361721881') format('truetype');
+}
+
+.iconfont {
+  font-family: "iconfont" !important;
+  font-size: 16px;
+  font-style: normal;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+
+.icon-weibiaoti--:before {
+  content: "\e65b";
+}
+
+.icon-jiaotong:before {
+  content: "\e656";
+}
+
+.icon-gouwu:before {
+  content: "\e657";
+}
+
+.icon-jiaoyu:before {
+  content: "\e658";
+}
+
+.icon-shenghuobianli:before {
+  content: "\e659";
+}
+
+.icon-yiliao:before {
+  content: "\e65a";
+}
+
+.icon-live:before {
+  content: "\e655";
+}
+
+.icon-xiaoxi:before {
+  content: "\e653";
+}
+
+.icon-wode:before {
+  content: "\e654";
+}
+
+.icon-a-1949plus:before {
+  content: "\e652";
+}
+
+.icon-ic-fuzhi:before {
+  content: "\e64e";
+}
+
+.icon-ic-huanyihuan:before {
+  content: "\e64f";
+}
+
+.icon-ic-xiazai:before {
+  content: "\e650";
+}
+
+.icon-ic-pengyouquan:before {
+  content: "\e651";
+}
+
+.icon-icon_back-shang:before {
+  content: "\e64c";
+}
+
+.icon-icon_back-xia:before {
+  content: "\e64d";
+}
+
+.icon-fuzhi:before {
+  content: "\e64b";
+}
+
+.icon-fuxuankuang-weixuanzhongzhuangtai:before {
+  content: "\e64a";
+}
+
+.icon-fuxuankuang-xuanzhongzhuangtai:before {
+  content: "\e649";
+}
+
+.icon-zhengzaizhibo:before {
+  content: "\e648";
+}
+
+.icon-yuyueliudian:before {
+  content: "\e647";
+}
+
+.icon-weixin:before {
+  content: "\e646";
+}
+
+.icon-goumai:before {
+  content: "\e645";
+}
+
+.icon-fenxiang1:before {
+  content: "\e644";
+}
+
+.icon-jiarugouwuche:before {
+  content: "\e643";
+}
+
+.icon-shoucang:before {
+  content: "\e642";
+}
+
+.icon-yishoucang:before {
+  content: "\e641";
+}
+
+.icon-liulanliang:before {
+  content: "\e640";
+}
+
+.icon-jiarugouwucheliang:before {
+  content: "\e63f";
+}
+
+.icon-kaipan1:before {
+  content: "\e63e";
+}
+
+.icon-huidaodingbu:before {
+  content: "\e63d";
+}
+
+.icon-quanming:before {
+  content: "\e63c";
+}
+
+.icon-search:before {
+  content: "\e639";
+}
+
+.icon-pin:before {
+  content: "\e63a";
+}
+
+.icon-icon_delete:before {
+  content: "\e63b";
+}
+
+.icon-guanbi1:before {
+  content: "\e638";
+}
+
+.icon-timeSelector:before {
+  content: "\e637";
+}
+
+.icon-sousuo:before {
+  content: "\e636";
+}
+
+.icon-shuliang:before {
+  content: "\e635";
+}
+
+.icon-xiangmu:before {
+  content: "\e629";
+}
+
+.icon-VR2:before {
+  content: "\e634";
+}
+
+.icon-guanbi:before {
+  content: "\e633";
+}
+
+.icon-liebiao:before {
+  content: "\e631";
+}
+
+.icon-bianji:before {
+  content: "\e632";
+}
+
+.icon-shipin:before {
+  content: "\e630";
+}
+
+.icon-jieguodaiqueren:before {
+  content: "\e621";
+}
+
+.icon-baobeichenggong:before {
+  content: "\e623";
+}
+
+.icon-baobeishibai:before {
+  content: "\e624";
+}
+
+.icon-chenggong:before {
+  content: "\e61f";
+}
+
+.icon-baobei:before {
+  content: "\e622";
+}
+
+.icon-shibai:before {
+  content: "\e61c";
+}
+
+.icon-daiqueren:before {
+  content: "\e61b";
+}
+
+.icon-yijiantixingshenhe:before {
+  content: "\e61a";
+}
+
+.icon-shangsheng1:before {
+  content: "\e619";
+}
+
+.icon-weizhibai-1:before {
+  content: "\e61d";
+}
+
+.icon-fenxiangdiseicon1:before {
+  content: "\e61e";
+}
+
+.icon-fenxiang:before {
+  content: "\e625";
+}
+
+.icon-weizhibai1:before {
+  content: "\e627";
+}
+
+.icon-remen2:before {
+  content: "\e628";
+}
+
+.icon-shipin1:before {
+  content: "\e62a";
+}
+
+.icon-shanghua1:before {
+  content: "\e62b";
+}
+
+.icon-gengduo1:before {
+  content: "\e62c";
+}
+
+.icon-xiala1:before {
+  content: "\e62d";
+}
+
+.icon-weizhishense:before {
+  content: "\e62e";
+}
+
+.icon-weizhiqianse1:before {
+  content: "\e62f";
+}
+
+.icon-remen1:before {
+  content: "\e620";
+}
+
+.icon-shangsheng:before {
+  content: "\e626";
+}
+
+.icon-IM:before {
+  content: "\e618";
+}
+
+.icon-gantanhao:before {
+  content: "\e685";
+}
+
+.icon-gougou:before {
+  content: "\e6a3";
+}
+
+.icon-yijianbaobei:before {
+  content: "\e617";
+}
+
+.icon-zhuanfa:before {
+  content: "\e615";
+}
+
+.icon-canyu:before {
+  content: "\e616";
+}
+
+.icon-duoxuanxuanzhong:before {
+  content: "\e613";
+}
+
+.icon-duoxuanchanggui:before {
+  content: "\e614";
+}
+
+.icon-weixuanzhong:before {
+  content: "\e611";
+}
+
+.icon-xuanzhong:before {
+  content: "\e612";
+}
+
+.icon-tabguanzhu:before {
+  content: "\e60a";
+}
+
+.icon-fenxiangxiangmu:before {
+  content: "\e609";
+}
+
+.icon-fenxiangkapian:before {
+  content: "\e60b";
+}
+
+.icon-tabfenxiang:before {
+  content: "\e60c";
+}
+
+.icon-yidingyue:before {
+  content: "\e60d";
+}
+
+.icon-diyfenxiang:before {
+  content: "\e60e";
+}
+
+.icon-xiangmuyiguanzhu:before {
+  content: "\e60f";
+}
+
+.icon-xiangmuguanzhu:before {
+  content: "\e610";
+}
+
+.icon-remen:before {
+  content: "\e608";
+}
+
+.icon-dingwei:before {
+  content: "\e607";
+}
+
+.icon-jisuanqi:before {
+  content: "\e606";
+}
+
+.icon-yuyue1:before {
+  content: "\e6cd";
+}
+
+.icon-VR:before {
+  content: "\e603";
+}
+
+.icon-pic:before {
+  content: "\e604";
+}
+
+.icon-lujing3863:before {
+  content: "\e605";
+}
+
+.icon-bianjia:before {
+  content: "\e601";
+}
+
+.icon-kaipan:before {
+  content: "\e602";
+}
+
+.icon-lujing7708:before {
+  content: "\e600";
+}
+
+.icon-xingxing-copy:before {
+  content: "\e85a";
+}

+ 97 - 0
common/graceChecker.js

@@ -0,0 +1,97 @@
+/**
+数据验证(表单验证)
+来自 grace.hcoder.net 
+作者 hcoder 深海
+*/
+export default {
+	error:'',
+	check : function (data, rule){
+		for(var i = 0; i < rule.length; i++){
+			if (!rule[i].checkType){return true;}
+			if (!rule[i].name) {return true;}
+			if (!rule[i].errorMsg) {return true;}
+			if (!data[rule[i].name]) {this.error = rule[i].errorMsg; return false;}
+			switch (rule[i].checkType){
+				case 'string':
+					var reg = new RegExp('^.{' + rule[i].checkRule + '}$');
+					if(!reg.test(data[rule[i].name])) {this.error = rule[i].errorMsg; return false;}
+				break;
+				case 'int':
+					var reg = new RegExp('^(-[1-9]|[1-9])[0-9]{' + rule[i].checkRule + '}$');
+					if(!reg.test(data[rule[i].name])) {this.error = rule[i].errorMsg; return false;}
+					break;
+				break;
+				case 'between':
+					if (!this.isNumber(data[rule[i].name])){
+						this.error = rule[i].errorMsg;
+						return false;
+					}
+					var minMax = rule[i].checkRule.split(',');
+					minMax[0] = Number(minMax[0]);
+					minMax[1] = Number(minMax[1]);
+					if (data[rule[i].name] > minMax[1] || data[rule[i].name] < minMax[0]) {
+						this.error = rule[i].errorMsg;
+						return false;
+					}
+				break;
+				case 'betweenD':
+					var reg = /^-?[1-9][0-9]?$/;
+					if (!reg.test(data[rule[i].name])) { this.error = rule[i].errorMsg; return false; }
+					var minMax = rule[i].checkRule.split(',');
+					minMax[0] = Number(minMax[0]);
+					minMax[1] = Number(minMax[1]);
+					if (data[rule[i].name] > minMax[1] || data[rule[i].name] < minMax[0]) {
+						this.error = rule[i].errorMsg;
+						return false;
+					}
+				break;
+				case 'betweenF': 
+					var reg = /^-?[0-9][0-9]?.+[0-9]+$/;
+					if (!reg.test(data[rule[i].name])){this.error = rule[i].errorMsg; return false;}
+					var minMax = rule[i].checkRule.split(',');
+					minMax[0] = Number(minMax[0]);
+					minMax[1] = Number(minMax[1]);
+					if (data[rule[i].name] > minMax[1] || data[rule[i].name] < minMax[0]) {
+						this.error = rule[i].errorMsg;
+						return false;
+					}
+				break;
+				case 'same':
+					if (data[rule[i].name] != rule[i].checkRule) { this.error = rule[i].errorMsg; return false;}
+				break;
+				case 'notsame':
+					if (data[rule[i].name] == rule[i].checkRule) { this.error = rule[i].errorMsg; return false; }
+				break;
+				case 'email':
+					var reg = /^\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/;
+					if (!reg.test(data[rule[i].name])) { this.error = rule[i].errorMsg; return false; }
+				break;
+				case 'phoneno':
+					var reg = /^1[0-9]{10,10}$/;
+					if (!reg.test(data[rule[i].name])) { this.error = rule[i].errorMsg; return false; }
+				break;
+				case 'zipcode':
+					var reg = /^[0-9]{6}$/;
+					if (!reg.test(data[rule[i].name])) { this.error = rule[i].errorMsg; return false; }
+				break;
+				case 'reg':
+					var reg = new RegExp(rule[i].checkRule);
+					if (!reg.test(data[rule[i].name])) { this.error = rule[i].errorMsg; return false; }
+				break;
+				case 'in':
+					if(rule[i].checkRule.indexOf(data[rule[i].name]) == -1){
+						this.error = rule[i].errorMsg; return false;
+					}
+				break;
+				case 'notnull':
+					if(data[rule[i].name] == null || data[rule[i].name].length < 1){this.error = rule[i].errorMsg; return false;}
+				break;
+			}
+		}
+		return true;
+	},
+	isNumber : function (checkVal){
+		var reg = /^-?[1-9][0-9]?.?[0-9]*$/;
+		return reg.test(checkVal);
+	}
+}

+ 352 - 0
common/html-parser.js

@@ -0,0 +1,352 @@
+/*
+ * HTML5 Parser By Sam Blowes
+ *
+ * Designed for HTML5 documents
+ *
+ * Original code by John Resig (ejohn.org)
+ * http://ejohn.org/blog/pure-javascript-html-parser/
+ * Original code by Erik Arvidsson, Mozilla Public License
+ * http://erik.eae.net/simplehtmlparser/simplehtmlparser.js
+ *
+ * ----------------------------------------------------------------------------
+ * License
+ * ----------------------------------------------------------------------------
+ *
+ * This code is triple licensed using Apache Software License 2.0,
+ * Mozilla Public License or GNU Public License
+ *
+ * ////////////////////////////////////////////////////////////////////////////
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License.  You may obtain a copy
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ////////////////////////////////////////////////////////////////////////////
+ *
+ * The contents of this file are subject to the Mozilla Public License
+ * Version 1.1 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS"
+ * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
+ * License for the specific language governing rights and limitations
+ * under the License.
+ *
+ * The Original Code is Simple HTML Parser.
+ *
+ * The Initial Developer of the Original Code is Erik Arvidsson.
+ * Portions created by Erik Arvidssson are Copyright (C) 2004. All Rights
+ * Reserved.
+ *
+ * ////////////////////////////////////////////////////////////////////////////
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ *
+ * ----------------------------------------------------------------------------
+ * Usage
+ * ----------------------------------------------------------------------------
+ *
+ * // Use like so:
+ * HTMLParser(htmlString, {
+ *     start: function(tag, attrs, unary) {},
+ *     end: function(tag) {},
+ *     chars: function(text) {},
+ *     comment: function(text) {}
+ * });
+ *
+ * // or to get an XML string:
+ * HTMLtoXML(htmlString);
+ *
+ * // or to get an XML DOM Document
+ * HTMLtoDOM(htmlString);
+ *
+ * // or to inject into an existing document/DOM node
+ * HTMLtoDOM(htmlString, document);
+ * HTMLtoDOM(htmlString, document.body);
+ *
+ */
+// Regular Expressions for parsing tags and attributes
+var startTag = /^<([-A-Za-z0-9_]+)((?:\s+[a-zA-Z_:][-a-zA-Z0-9_:.]*(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/;
+var endTag = /^<\/([-A-Za-z0-9_]+)[^>]*>/;
+var attr = /([a-zA-Z_:][-a-zA-Z0-9_:.]*)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g; // Empty Elements - HTML 5
+
+var empty = makeMap('area,base,basefont,br,col,frame,hr,img,input,link,meta,param,embed,command,keygen,source,track,wbr'); // Block Elements - HTML 5
+// fixed by xxx 将 ins 标签从块级名单中移除
+
+var block = makeMap('a,address,article,applet,aside,audio,blockquote,button,canvas,center,dd,del,dir,div,dl,dt,fieldset,figcaption,figure,footer,form,frameset,h1,h2,h3,h4,h5,h6,header,hgroup,hr,iframe,isindex,li,map,menu,noframes,noscript,object,ol,output,p,pre,section,script,table,tbody,td,tfoot,th,thead,tr,ul,video'); // Inline Elements - HTML 5
+
+var inline = makeMap('abbr,acronym,applet,b,basefont,bdo,big,br,button,cite,code,del,dfn,em,font,i,iframe,img,input,ins,kbd,label,map,object,q,s,samp,script,select,small,span,strike,strong,sub,sup,textarea,tt,u,var'); // Elements that you can, intentionally, leave open
+// (and which close themselves)
+
+var closeSelf = makeMap('colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr'); // Attributes that have their values filled in disabled="disabled"
+
+var fillAttrs = makeMap('checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected'); // Special Elements (can contain anything)
+
+var special = makeMap('script,style');
+function HTMLParser(html, handler) {
+  var index;
+  var chars;
+  var match;
+  var stack = [];
+  var last = html;
+
+  stack.last = function () {
+    return this[this.length - 1];
+  };
+
+  while (html) {
+    chars = true; // Make sure we're not in a script or style element
+
+    if (!stack.last() || !special[stack.last()]) {
+      // Comment
+      if (html.indexOf('<!--') == 0) {
+        index = html.indexOf('-->');
+
+        if (index >= 0) {
+          if (handler.comment) {
+            handler.comment(html.substring(4, index));
+          }
+
+          html = html.substring(index + 3);
+          chars = false;
+        } // end tag
+
+      } else if (html.indexOf('</') == 0) {
+        match = html.match(endTag);
+
+        if (match) {
+          html = html.substring(match[0].length);
+          match[0].replace(endTag, parseEndTag);
+          chars = false;
+        } // start tag
+
+      } else if (html.indexOf('<') == 0) {
+        match = html.match(startTag);
+
+        if (match) {
+          html = html.substring(match[0].length);
+          match[0].replace(startTag, parseStartTag);
+          chars = false;
+        }
+      }
+
+      if (chars) {
+        index = html.indexOf('<');
+        var text = index < 0 ? html : html.substring(0, index);
+        html = index < 0 ? '' : html.substring(index);
+
+        if (handler.chars) {
+          handler.chars(text);
+        }
+      }
+    } else {
+      html = html.replace(new RegExp('([\\s\\S]*?)<\/' + stack.last() + '[^>]*>'), function (all, text) {
+        text = text.replace(/<!--([\s\S]*?)-->|<!\[CDATA\[([\s\S]*?)]]>/g, '$1$2');
+
+        if (handler.chars) {
+          handler.chars(text);
+        }
+
+        return '';
+      });
+      parseEndTag('', stack.last());
+    }
+
+    if (html == last) {
+      throw 'Parse Error: ' + html;
+    }
+
+    last = html;
+  } // Clean up any remaining tags
+
+
+  parseEndTag();
+
+  function parseStartTag(tag, tagName, rest, unary) {
+    tagName = tagName.toLowerCase();
+
+    if (block[tagName]) {
+      while (stack.last() && inline[stack.last()]) {
+        parseEndTag('', stack.last());
+      }
+    }
+
+    if (closeSelf[tagName] && stack.last() == tagName) {
+      parseEndTag('', tagName);
+    }
+
+    unary = empty[tagName] || !!unary;
+
+    if (!unary) {
+      stack.push(tagName);
+    }
+
+    if (handler.start) {
+      var attrs = [];
+      rest.replace(attr, function (match, name) {
+        var value = arguments[2] ? arguments[2] : arguments[3] ? arguments[3] : arguments[4] ? arguments[4] : fillAttrs[name] ? name : '';
+        attrs.push({
+          name: name,
+          value: value,
+          escaped: value.replace(/(^|[^\\])"/g, '$1\\\"') // "
+
+        });
+      });
+
+      if (handler.start) {
+        handler.start(tagName, attrs, unary);
+      }
+    }
+  }
+
+  function parseEndTag(tag, tagName) {
+    // If no tag name is provided, clean shop
+    if (!tagName) {
+      var pos = 0;
+    } // Find the closest opened tag of the same type
+    else {
+        for (var pos = stack.length - 1; pos >= 0; pos--) {
+          if (stack[pos] == tagName) {
+            break;
+          }
+        }
+      }
+
+    if (pos >= 0) {
+      // Close all the open elements, up the stack
+      for (var i = stack.length - 1; i >= pos; i--) {
+        if (handler.end) {
+          handler.end(stack[i]);
+        }
+      } // Remove the open elements from the stack
+
+
+      stack.length = pos;
+    }
+  }
+}
+
+function makeMap(str) {
+  var obj = {};
+  var items = str.split(',');
+
+  for (var i = 0; i < items.length; i++) {
+    obj[items[i]] = true;
+  }
+
+  return obj;
+}
+
+function removeDOCTYPE(html) {
+  return html.replace(/<\?xml.*\?>\n/, '').replace(/<!doctype.*>\n/, '').replace(/<!DOCTYPE.*>\n/, '');
+}
+
+function parseAttrs(attrs) {
+  return attrs.reduce(function (pre, attr) {
+    var value = attr.value;
+    var name = attr.name;
+
+    if (pre[name]) {
+			pre[name] = pre[name] + " " + value;
+    } else {
+			pre[name] = value;
+    }
+
+    return pre;
+  }, {});
+}
+
+function parseHtml(html) {
+  html = removeDOCTYPE(html);
+  var stacks = [];
+  var results = {
+    node: 'root',
+    children: []
+  };
+  HTMLParser(html, {
+    start: function start(tag, attrs, unary) {
+      var node = {
+        name: tag
+      };
+
+      if (attrs.length !== 0) {
+        node.attrs = parseAttrs(attrs);
+      }
+
+      if (unary) {
+        var parent = stacks[0] || results;
+
+        if (!parent.children) {
+          parent.children = [];
+        }
+
+        parent.children.push(node);
+      } else {
+        stacks.unshift(node);
+      }
+    },
+    end: function end(tag) {
+      var node = stacks.shift();
+      if (node.name !== tag) console.error('invalid state: mismatch end tag');
+
+      if (stacks.length === 0) {
+        results.children.push(node);
+      } else {
+        var parent = stacks[0];
+
+        if (!parent.children) {
+          parent.children = [];
+        }
+
+        parent.children.push(node);
+      }
+    },
+    chars: function chars(text) {
+      var node = {
+        type: 'text',
+        text: text
+      };
+
+      if (stacks.length === 0) {
+        results.children.push(node);
+      } else {
+        var parent = stacks[0];
+
+        if (!parent.children) {
+          parent.children = [];
+        }
+
+        parent.children.push(node);
+      }
+    },
+    comment: function comment(text) {
+      var node = {
+        node: 'comment',
+        text: text
+      };
+      var parent = stacks[0];
+
+      if (!parent.children) {
+        parent.children = [];
+      }
+
+      parent.children.push(node);
+    }
+  });
+  return results.children;
+}
+
+export default parseHtml;

+ 90 - 0
common/mixins/concern.js

@@ -0,0 +1,90 @@
+const util = require('../../static/utils/util.js');
+const config = require('../../static/config.js');
+var app = getApp(); //获取应用实例
+import requestConfig from '../../static/lib/requestConfig';
+export default {
+    data(){
+        return {
+            showConcernTips:false,
+            haveAlreadyCollectThisProject: false,
+        }
+    },
+    watch:{
+    },
+    methods:{
+        catchTouchMove: function() {
+            return false;
+        },
+        /**
+         * 查询项目的状态
+         */
+        async collectProjectStatus(successBack, failBack) {
+            let requestData = {
+                customerId: app.globalData.single && app.globalData.single.id,
+                houseId: this.houseId,
+            };
+            let checkCollectRes = await requestConfig('collectionStatus', requestData, true);
+            if (checkCollectRes && checkCollectRes.success) {
+                if (successBack) {
+                    successBack();
+                }
+                this.haveAlreadyCollectThisProject = true;
+            } else {
+                this.haveAlreadyCollectThisProject = false;
+                if (failBack) {
+                    failBack();
+                }
+            }
+        },
+        /**
+         * 收藏项目
+         */
+        async collectProject() {
+            let requestData = {
+                customerId: app.globalData.single && app.globalData.single.id,
+                houseId: this.houseId,
+            };
+            let res = await requestConfig('houseCollection', requestData, true);
+            if (res.success) {
+                this.haveAlreadyCollectThisProject = true;
+				this.updateShowConcernTips()
+            }
+        },
+        updateShowConcernTips(){
+            this.showConcernTips = true;
+            setTimeout(() => {
+                this.showConcernTips=false
+            }, 2500);
+        },
+        /**
+         * 关注项目
+         */
+        attendProject(e) {
+            let type = e.currentTarget.dataset.jumptype;
+            if (type == -1) {
+                let param = {
+                    type: 'CLK', //埋点类型
+                    clkName: 'follow', //点击前往的页面名称
+                    clkId: 'clk_2cmina_241', //点击ID
+                    clkParams: {
+                        "houseId": this.houseId,
+                    }
+                };
+                util.trackRequest(param, app);
+                this.collectProjectStatus(() => {
+                    let projectName = app.globalData.houseProject.projectName || '';
+                    let msg = "您已成功关注" + projectName + "项目";
+                    uni.showToast({
+                        title: msg,
+                        icon: "none",
+                        duration: 2000
+                    });
+                }, () => {
+                    this.collectProject();
+                });
+            } else if (type == -2) {
+                this.navigateFuc(e)
+            }
+        },
+    }
+}

+ 256 - 0
common/mixins/templateMethod.js

@@ -0,0 +1,256 @@
+var app = getApp(); //获取应用实例
+const util = require('@/static/utils/util.js');
+const config = require('@/static/config.js');
+import requestConfig from '@/static/lib/requestConfig';
+import Bus from '@/common/bus';
+export default {
+    data(){
+        return {
+            doubleClick:false,
+            height: 0,
+            selfTabbarBottom: '0px',
+            computerHeight: 'calc(100vh - 58px)',
+            statusBarHeight: 0,
+            _scrollDistance: 0, //滑动的距离
+            showShareOptions: false,
+            showOption: true,
+        }
+    },
+    watch:{
+    },
+    methods:{
+        scrollExp(e) {
+            if (e.detail) {
+                this._scrollDistance = e.detail.scrollTop;
+				if(this.old){
+					this.old.scrollTop = e.detail.scrollTop;
+				}
+				if(this._scrollDistance > app.globalData.systemInfo.screenHeight){
+				    this.goTopShow = true;
+				}
+				else{
+				    this.goTopShow = false;
+				}
+                if (this._scrollDistance >= 30) {
+                    this.showNav = true;
+                    if(this.hasOwnProperty('myNavBarData') && this.myNavBarData){
+                        this.myNavBarData.navBarColor = (this.globalCityListConfig && this.globalCityListConfig.backgroundColor) ? this.globalCityListConfig.backgroundColor: '#fff';
+                        if(!this.currentData){
+                            this.pageThemeColor = {
+                                textColor1:'#000'
+                            };
+                        }
+                        this.myNavBarData.titleColor = this.pageThemeColor.textColor1;
+                        this.myNavBarData.title = "城市列表";
+                    }
+                } else {
+                    this.showNav = false;
+                    if(this.hasOwnProperty('myNavBarData') && this.myNavBarData){
+                        this.myNavBarData.navBarColor = 0;
+                        this.myNavBarData.titleColor = '#fff';
+                        this.myNavBarData.title = "";
+                        if(!this.currentData){
+                            this.pageThemeColor = {
+                                textColor1:'#fff'
+                            };
+                        }
+                    }
+                }
+            }
+        },
+        //顶部搜索按钮
+        goToSearch(type=1) {
+            // let url = '/subPackage/pages/searchCondition/searchCondition';
+            let url = "/subPackage/pages/searchNewModule/searchNewModule";
+            if(type==2){//新版的搜索页面
+                url = "/subPackage/pages/searchNewModule/searchNewModule"
+            }
+            uni.navigateTo({
+                url: url,
+                success: () => {},
+            });
+        },
+        //切换城市
+        navigateToCity(e) {
+            uni.navigateTo({
+                url: '/subPackage/pages/addressModule/addressModule'
+            });
+        },
+        //跳转到筛选页面
+        // navigateToSearch(type) {
+        //     var _url = '/pages/screenSearchListModule/screenSearchListModule';
+        //     if (type) {
+        //         _url = _url + "?openType=" + type;
+        //     }
+        //     uni.navigateTo({
+        //         url: _url
+        //     });
+        // },
+        navigateFuc(e) {
+            let eventOption = {};
+            if (e) {
+                // this.$emit("navigateFuc", e, eventOption);
+                // 发送 navigateFucFromNav 主题 的Bus消息
+                Bus.$emit('navigateFucFromNav', e);
+            }
+        },
+        //颜色值转换
+        hexToRgba(color, opacity) {
+            return util.hexToRgba(color, opacity)
+        },
+        /**
+         * 单页模式
+         */
+        showSingleModel() {
+            app = getApp();
+            let _scene = (app.globalData.launchInfo && app.globalData.launchInfo.scene) ? app.globalData.launchInfo
+                .scene : '';
+            if (_scene == '1154') { //单页模式下设置自定义导航栏无效
+                this.specialHeight = (app.globalData.navigateStatusContainerHeight + 'px');
+                this.specialTop = (app.globalData.navigateStatusContainerHeight + 'px');
+                console.log("this.specialTop", this.specialTop);
+            }
+            this.singlePageStatus = app.globalData.singlePageStatus;
+        },
+        showShareOption() {
+            this.showShareOptions = true;
+        },
+        hidenOption() {
+            this.showOption = true;
+        },
+        getRandomArrayElements(arr, count) {
+            var shuffled = arr.slice(0),
+                i = arr.length,
+                min = i - count,
+                temp, index;
+            while (i-- > min) {
+                index = Math.floor((i + 1) * Math.random());
+                temp = shuffled[index];
+                shuffled[index] = shuffled[i];
+                shuffled[i] = temp;
+            }
+            return shuffled.slice(min);
+        },
+        catchTouchMove: function() {
+        	return false;
+        },
+        //跳转到项目
+        async navigateToProject(e) {
+            var self = this;
+            let houseId = e.currentTarget.dataset.houseid || e.currentTarget.dataset.houseId;
+            let brandId = e.currentTarget.dataset.brandid || e.currentTarget.dataset.brandId || "";
+            let requestData = {
+                houseId: houseId,
+                requestCount: 1,
+                componentCount: 1,
+            };
+            if(this.doubleClick){
+                return false;
+            }
+            this.doubleClick = true;
+            const res = await requestConfig('queryXcxPage', requestData, true);``
+            if (res && res.success && res.single && res.single.jsonString != null) {
+                uni.navigateTo({
+                    url: '/pages/index/index?houseId=' + houseId + "&brandId=" + brandId,
+                    success: function() {},
+                    fail: function(res) {
+                        config.brandId = brandId;
+                        console.log(res)
+                    },
+                    complete() {
+                        self.doubleClick = false;
+                    }
+                })
+            } else {
+                uni.showToast({
+                    title: '敬请期待',
+                    icon: 'none',
+                    duration: 1500,
+                })
+                this.doubleClick = false;
+            }
+        },
+        async queryCityNews() {
+            let res = await requestConfig('queryCityNews', {
+                brandId: config.brandId,
+                cityName: this.currentCity
+            })
+            if (res && res.success) {
+                res.list.sort((a, b) => {
+                    return a.orderNumber - b.orderNumber
+                })
+                this.newsList = res.list.splice(0, 2)
+            }
+        },
+        async goNews(e) {
+            const data = e;
+            var _link = "";
+            var _title = "";
+            if (data.type == 3) {
+                _link = data.newsUrl;
+                _title = data.title;
+            } else if (data.type == 1 || !data.type) {
+                _link = data.linkUrl;
+                _title = data.title;
+            } else {
+                let res = await requestConfig('queryNewsById', {
+                    id: data.referNewsId
+                })
+                if (res.success && res.single) {
+                    if (res.single.type == 5) {
+                        _link = res.single.newsUrl;
+                        _title = res.single.title;
+                    } else {
+                        _link = res.single.linkUrl;
+                        _title = res.single.title;
+                    }
+                }
+            }
+            let token = data.linkUrl.split('?newsToken=')[1] || '';
+            app.checkNewsStatus(token, () => {
+                if (data.content && data.content.length > 0) {
+                    let path = '/subPackage/pages/news/newsDetail/newsDetail?newsToken=' + token;
+                    console.log("path", path);
+                    uni.navigateTo({
+                        url: path,
+                        fail: function(res) {
+                            console.log(res)
+                        },
+                    })
+                } else {
+                    if (app.checkWebviewLink(_link)) {
+                        uni.showToast({
+                            title: '敬请期待',
+                            icon: 'none',
+                            duration: 1500,
+                        })
+                    } else {
+                        uni.navigateTo({
+                            url: '/pages/webView/webView?view=' + encodeURIComponent(_link) +
+                                '&title=' + _title,
+                            fail: function(res) {
+                                console.log(res)
+                            },
+                        })
+                    }
+                }
+            }, () => {
+                uni.showToast({
+                    title: '该页面已下线',
+                    icon: 'none',
+                    duration: 1500,
+                })
+            });
+        },
+        getDeviceInfor: function() {
+            const res = uni.getSystemInfoSync();
+            if (res.model.indexOf('iPhone') >= 0) {
+                this.selfTabbarBottom = '0px';
+                this.computerHeight = 'calc(100vh - 58px - 0px)';
+            }
+            this.height = app.globalData.navigateStatusContainerHeight;
+            this.statusBarHeight = app.globalData.statusBarHeight;
+            console.log("getDeviceInfor", res);
+        },
+    }
+}

+ 245 - 0
common/permission.js

@@ -0,0 +1,245 @@
+/// null = 未请求,1 = 已允许,0 = 拒绝|受限, 2 = 系统未开启
+
+var isIOS
+
+function album() {
+    var result = 0;
+    var PHPhotoLibrary = plus.ios.import("PHPhotoLibrary");
+    var authStatus = PHPhotoLibrary.authorizationStatus();
+    if (authStatus === 0) {
+        result = null;
+    } else if (authStatus == 3) {
+        result = 1;
+    } else {
+        result = 0;
+    }
+    plus.ios.deleteObject(PHPhotoLibrary);
+    return result;
+}
+
+function camera() {
+    var result = 0;
+    var AVCaptureDevice = plus.ios.import("AVCaptureDevice");
+    var authStatus = AVCaptureDevice.authorizationStatusForMediaType('vide');
+    if (authStatus === 0) {
+        result = null;
+    } else if (authStatus == 3) {
+        result = 1;
+    } else {
+        result = 0;
+    }
+    plus.ios.deleteObject(AVCaptureDevice);
+    return result;
+}
+
+function location() {
+    var result = 0;
+    var cllocationManger = plus.ios.import("CLLocationManager");
+    var enable = cllocationManger.locationServicesEnabled();
+    var status = cllocationManger.authorizationStatus();
+    if (!enable) {
+        result = 2;
+    } else if (status === 0) {
+        result = null;
+    } else if (status === 3 || status === 4) {
+        result = 1;
+    } else {
+        result = 0;
+    }
+    plus.ios.deleteObject(cllocationManger);
+    return result;
+}
+
+function push() {
+    var result = 0;
+    var UIApplication = plus.ios.import("UIApplication");
+    var app = UIApplication.sharedApplication();
+    var enabledTypes = 0;
+    if (app.currentUserNotificationSettings) {
+        var settings = app.currentUserNotificationSettings();
+        enabledTypes = settings.plusGetAttribute("types");
+        if (enabledTypes == 0) {
+            result = 0;
+            console.log("推送权限没有开启");
+        } else {
+            result = 1;
+            console.log("已经开启推送功能!")
+        }
+        plus.ios.deleteObject(settings);
+    } else {
+        enabledTypes = app.enabledRemoteNotificationTypes();
+        if (enabledTypes == 0) {
+            result = 3;
+            console.log("推送权限没有开启!");
+        } else {
+            result = 4;
+            console.log("已经开启推送功能!")
+        }
+    }
+    plus.ios.deleteObject(app);
+    plus.ios.deleteObject(UIApplication);
+    return result;
+}
+
+function contact() {
+    var result = 0;
+    var CNContactStore = plus.ios.import("CNContactStore");
+    var cnAuthStatus = CNContactStore.authorizationStatusForEntityType(0);
+    if (cnAuthStatus === 0) {
+        result = null;
+    } else if (cnAuthStatus == 3) {
+        result = 1;
+    } else {
+        result = 0;
+    }
+    plus.ios.deleteObject(CNContactStore);
+    return result;
+}
+
+function record() {
+    var result = null;
+    var avaudiosession = plus.ios.import("AVAudioSession");
+    var avaudio = avaudiosession.sharedInstance();
+    var status = avaudio.recordPermission();
+    console.log("permissionStatus:" + status);
+    if (status === 1970168948) {
+        result = null;
+    } else if (status === 1735552628) {
+        result = 1;
+    } else {
+        result = 0;
+    }
+    plus.ios.deleteObject(avaudiosession);
+    return result;
+}
+
+function calendar() {
+    var result = null;
+    var EKEventStore = plus.ios.import("EKEventStore");
+    var ekAuthStatus = EKEventStore.authorizationStatusForEntityType(0);
+    if (ekAuthStatus == 3) {
+        result = 1;
+        console.log("日历权限已经开启");
+    } else {
+        console.log("日历权限没有开启");
+    }
+    plus.ios.deleteObject(EKEventStore);
+    return result;
+}
+
+function memo() {
+    var result = null;
+    var EKEventStore = plus.ios.import("EKEventStore");
+    var ekAuthStatus = EKEventStore.authorizationStatusForEntityType(1);
+    if (ekAuthStatus == 3) {
+        result = 1;
+        console.log("备忘录权限已经开启");
+    } else {
+        console.log("备忘录权限没有开启");
+    }
+    plus.ios.deleteObject(EKEventStore);
+    return result;
+}
+
+
+function requestIOS(permissionID) {
+    return new Promise((resolve, reject) => {
+        switch (permissionID) {
+            case "push":
+                resolve(push());
+                break;
+            case "location":
+                resolve(location());
+                break;
+            case "record":
+                resolve(record());
+                break;
+            case "camera":
+                resolve(camera());
+                break;
+            case "album":
+                resolve(album());
+                break;
+            case "contact":
+                resolve(contact());
+                break;
+            case "calendar":
+                resolve(calendar());
+                break;
+            case "memo":
+                resolve(memo());
+                break;
+            default:
+                resolve(0);
+                break;
+        }
+    });
+}
+
+function requestAndroid(permissionID) {
+    return new Promise((resolve, reject) => {
+        plus.android.requestPermissions(
+            [permissionID],
+            function(resultObj) {
+                var result = 0;
+                for (var i = 0; i < resultObj.granted.length; i++) {
+                    var grantedPermission = resultObj.granted[i];
+                    console.log('已获取的权限:' + grantedPermission);
+                    result = 1
+                }
+                for (var i = 0; i < resultObj.deniedPresent.length; i++) {
+                    var deniedPresentPermission = resultObj.deniedPresent[i];
+                    console.log('拒绝本次申请的权限:' + deniedPresentPermission);
+                    result = 0
+                }
+                for (var i = 0; i < resultObj.deniedAlways.length; i++) {
+                    var deniedAlwaysPermission = resultObj.deniedAlways[i];
+                    console.log('永久拒绝申请的权限:' + deniedAlwaysPermission);
+                    result = -1
+                }
+                resolve(result);
+            },
+            function(error) {
+                console.log('result error: ' + error.message)
+                resolve({
+                    code: error.code,
+                    message: error.message
+                });
+            }
+        );
+    });
+}
+
+function gotoAppPermissionSetting() {
+    if (permission.isIOS) {
+        var UIApplication = plus.ios.import("UIApplication");
+        var application2 = UIApplication.sharedApplication();
+        var NSURL2 = plus.ios.import("NSURL");
+        var setting2 = NSURL2.URLWithString("app-settings:");
+        application2.openURL(setting2);
+        plus.ios.deleteObject(setting2);
+        plus.ios.deleteObject(NSURL2);
+        plus.ios.deleteObject(application2);
+    } else {
+        var Intent = plus.android.importClass("android.content.Intent");
+        var Settings = plus.android.importClass("android.provider.Settings");
+        var Uri = plus.android.importClass("android.net.Uri");
+        var mainActivity = plus.android.runtimeMainActivity();
+        var intent = new Intent();
+        intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
+        var uri = Uri.fromParts("package", mainActivity.getPackageName(), null);
+        intent.setData(uri);
+        mainActivity.startActivity(intent);
+    }
+}
+
+const permission = {
+    get isIOS(){
+        return typeof isIOS === 'boolean' ? isIOS : (isIOS = uni.getSystemInfoSync().platform === 'ios')
+    },
+    requestIOS: requestIOS,
+    requestAndroid: requestAndroid,
+    gotoAppSetting: gotoAppPermissionSetting
+}
+
+export default permission

File diff suppressed because it is too large
+ 3 - 0
common/static/crypto-js.min.js


+ 20 - 0
common/static/customicons.css

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

BIN
common/static/customicons.ttf


+ 463 - 0
common/static/im/newIM_init.js

@@ -0,0 +1,463 @@
+var TIM = require('./tim-wx.js');
+var config = require('@/static/config.js');
+import requestConfig from '@/static/lib/requestConfig.js';
+var app, tim;
+var SDKConfig = {
+    sdkappid: config.sdkAppID,
+    accountType: config.accType,
+    accountMode: 0 //帐号模式,0-表示独立模式,1-表示托管模式
+};
+var event = {
+    onIMReady: function() {
+
+    },
+    onGetPusherList: function() {}, // 初始化成员列表
+    onPusherJoin: function() {}, // 进房通知
+    onPusherQuit: function() {}, // 退房通知
+    onRoomClose: function() {}, // 群解散通知
+    onRecvRoomTextMsg: function() {}, // 消息通知
+    onMsgNotify: function() {}, // 监听新消息(私聊(包括普通消息和全员推送消息),普通群(非直播聊天室)消息)事件,必填
+    onDestoryGroupNotify: function() {}, //群被解散(全员接收) 5
+    onCustomGroupNotify: function() {}, //用户自定义通知(默认全员接收) 255
+    onLoginSuccess: function() {}, //IM 登录成功的回调
+    onBigGroupMsgNotify: function() {}, //监听新消息(大群)事件
+};
+
+function handleUnreadMsg(msg) {
+    console.log('处理全局未读消息')
+    if (msg.from === app.globalData.single.id + '_' + config.brandId + '_1') {
+        return false
+    }
+    var adviserHouseId = msg.from.split("_")[1];
+    if (!adviserHouseId) {
+        return
+    }
+    var unReadMsgs = uni.getStorageSync('unReadMsgs') || {};
+    var currentHouseUnReadMsgs = unReadMsgs[adviserHouseId] || {};//项目下消息数目
+    unReadMsgs.total = unReadMsgs.total ? unReadMsgs.total : 0;
+    currentHouseUnReadMsgs.total = currentHouseUnReadMsgs.total ? currentHouseUnReadMsgs.total : 0;
+    unReadMsgs['total']++;
+    currentHouseUnReadMsgs['total']++;
+    let list = getCurrentPages();
+    if (!currentHouseUnReadMsgs[msg.from]) {//项目未读消息下 对应 具体的顾问
+        currentHouseUnReadMsgs[msg.from] = 1;
+    } else {
+        currentHouseUnReadMsgs[msg.from]++;
+        console.log(unReadMsgs[msg.from], '今天你增加了吗')
+    }
+    console.log('未读计数增加', list)
+    unReadMsgs[adviserHouseId] = currentHouseUnReadMsgs
+    uni.setStorageSync('unReadMsgs', unReadMsgs)
+    list.forEach((item, index) => {
+        console.log(item, 'bpbpbpbpbp')
+        if (item.$vm && item.$vm.refreshNumber && typeof item.$vm.refreshNumber === 'function') {
+            console.log('户型图集增加', unReadMsgs)
+            item.$vm.refreshNumber()
+        }
+    })
+    console.log('全局未读消息处理完毕')
+}
+
+function msgReceived(msg) {
+	// 实例化 对象
+	var myMsg = JSON.parse(JSON.stringify(msg));
+    console.log('接收到最新消息', myMsg)
+    console.log('接收到最新消息', myMsg.data[0].payload)
+    if (myMsg.data instanceof Array) {
+        myMsg.data.forEach((item, index) => {
+            console.log(item.conversationType)
+            if (item.conversationType == 'GROUP') {
+                console.log('收到群消息,进去处理',item)
+                // event.onBigGroupMsgNotify(item)
+                event.onBigGroupMsgNotify(myMsg.data)
+                parseGroupSystemNotice(item.payload)
+            } else if (item.payload && item.payload.data && item.payload.data.includes('InputStatus')) {
+                console.log('对方键盘事件')
+            } else if (item.conversationType == '@TIM#SYSTEM') {
+                console.log('系统通知@_@')
+                parseGroupSystemNotice(item.payload)
+            } else {
+                console.log('传出去了')
+                handleUnreadMsg(item)
+                console.log('开始传回当前页notify')
+                event.onMsgNotify(item)
+
+            }
+        })
+    }
+
+    // 收到推送的单聊、群聊、群提示、群系统通知的新消息,可通过遍历 event.data 获取消息列表数据并渲染到页面
+    // event.name - TIM.EVENT.MESSAGE_RECEIVED
+    // event.data - 存储 Message 对象的数组 - [Message]
+
+}
+
+function parseGroupSystemNotice(payload) {
+    const groupName =
+        payload.groupProfile.groupName || payload.groupProfile.groupID
+    console.log('审判群组消息类型', payload.operationType)
+    switch (payload.operationType) {
+        case 1:
+            return `申请加入群组:${groupName}`
+        case 2:
+            event.onCustomGroupNotify(payload);
+        case 3:
+            return `申请加入群组:${groupName}被拒绝`
+        case 4:
+            return `被管理员${payload.operatorID}踢出群组:${groupName}`
+        case 5:
+            return `成功加入群组:${groupName}`
+        case 6:
+            return `${payload.operatorID}创建群:${groupName}`
+        case 7:
+            return `${payload.operatorID}邀请你加群:${groupName}`
+        case 8:
+            return `你退出群组:${groupName}`
+        case 9:
+            return `你被${payload.operatorID}设置为群:${groupName}的管理员`
+        case 10:
+            return `你被${payload.operatorID}撤销群:${groupName}的管理员身份`
+        case 255:
+            event.onCustomGroupNotify(payload);
+    }
+}
+
+function loginIM(data) {
+    app = getApp();
+    if (app.globalData.globalWebimhandler) {
+        event.onIMReady()
+        event.onLoginSuccess()
+        return
+    }
+    var loginInfo = {
+        'sdkAppID': SDKConfig.sdkappid, //用户所属应用id,必填
+        'appIDAt3rd': SDKConfig.sdkappid, //用户所属应用id,必填
+        'accountType': SDKConfig.accountType, //用户所属应用帐号类型,必填
+        'identifier': app.globalData.identifier, //当前用户ID,必须是否字符串类型,选填
+        'identifierNick': app.globalData.single.nickname || '小程序用户', //当前用户昵称,选填
+        'userSig': app.globalData.userSig, //当前用户身份凭证,必须是字符串类型,选填
+    };
+    console.log('开始创建im')
+    tim = TIM.create({
+        SDKAppID: config.sdkAppID
+    })
+    tim.setLogLevel(0);
+    // 监听事件,例如:
+    tim.on(TIM.EVENT.SDK_READY, function(msg) {
+        console.log('imReady***')
+        event.onIMReady()
+        console.log('%%%%', event.onIMReady)
+        event.onLoginSuccess && event.onLoginSuccess();
+        app.globalData.globalWebimhandler = true
+    });
+
+    tim.on(TIM.EVENT.MESSAGE_RECEIVED, msgReceived);
+
+    tim.on(TIM.EVENT.MESSAGE_REVOKED, function(event) {
+        // 收到消息被撤回的通知
+        // event.name - TIM.EVENT.MESSAGE_REVOKED
+        // event.data - 存储 Message 对象的数组 - [Message] - 每个 Message 对象的 isRevoked 属性值为 true
+    });
+
+    tim.on(TIM.EVENT.MESSAGE_READ_BY_PEER, function(event) {
+        // SDK 收到对端已读消息的通知,即已读回执。使用前需要将 SDK 版本升级至 v2.7.0 或以上。仅支持单聊会话。
+        // event.name - TIM.EVENT.MESSAGE_READ_BY_PEER
+        // event.data - event.data - 存储 Message 对象的数组 - [Message] - 每个 Message 对象的 isPeerRead 属性值为 true
+    });
+
+    tim.on(TIM.EVENT.CONVERSATION_LIST_UPDATED, function(event) {
+        console.log('监听到会话列表更新', event.data)
+        // 收到会话列表更新通知,可通过遍历 event.data 获取会话列表数据并渲染到页面
+        // event.name - TIM.EVENT.CONVERSATION_LIST_UPDATED
+        // event.data - 存储 Conversation 对象的数组 - [Conversation]
+    });
+
+    tim.on(TIM.EVENT.GROUP_LIST_UPDATED, function(event) {
+        // 收到群组列表更新通知,可通过遍历 event.data 获取群组列表数据并渲染到页面
+        // event.name - TIM.EVENT.GROUP_LIST_UPDATED
+        // event.data - 存储 Group 对象的数组 - [Group]
+    });
+
+    tim.on(TIM.EVENT.PROFILE_UPDATED, function(event) {
+        // 收到自己或好友的资料变更通知
+        // event.name - TIM.EVENT.PROFILE_UPDATED
+        // event.data - 存储 Profile 对象的数组 - [Profile]
+    });
+
+    tim.on(TIM.EVENT.BLACKLIST_UPDATED, function(event) {
+        // 收到黑名单列表更新通知
+        // event.name - TIM.EVENT.BLACKLIST_UPDATED
+        // event.data - 存储 userID 的数组 - [userID]
+    });
+
+    tim.on(TIM.EVENT.ERROR, function(event) {
+        console.log('SDKERROR!!', event)
+        // 收到 SDK 发生错误通知,可以获取错误码和错误信息
+        // event.name - TIM.EVENT.ERROR
+        // event.data.code - 错误码
+        // event.data.message - 错误信息
+    });
+
+    tim.on(TIM.EVENT.SDK_NOT_READY, function(event) {
+        console.log('SDK_NOT_READY!!', event)
+        app.globalData.globalWebimhandler = null;
+        // 收到 SDK 进入 not ready 状态通知,此时 SDK 无法正常工作
+        // event.name - TIM.EVENT.SDK_NOT_READY
+    });
+
+    tim.on(TIM.EVENT.KICKED_OUT, function(event) {
+        // 收到被踢下线通知
+        // event.name - TIM.EVENT.KICKED_OUT
+        // event.data.type - 被踢下线的原因,例如:
+        //    - TIM.TYPES.KICKED_OUT_MULT_ACCOUNT 多实例登录被踢
+        //    - TIM.TYPES.KICKED_OUT_MULT_DEVICE 多终端登录被踢
+        //    - TIM.TYPES.KICKED_OUT_USERSIG_EXPIRED 签名过期被踢 (v2.4.0起支持)。
+    });
+
+    tim.on(TIM.EVENT.NET_STATE_CHANGE, function(event) {
+        //  网络状态发生改变(v2.5.0 起支持)。
+        // event.name - TIM.EVENT.NET_STATE_CHANGE
+        // event.data.state 当前网络状态,枚举值及说明如下:
+        //     \- TIM.TYPES.NET_STATE_CONNECTED - 已接入网络
+        //     \- TIM.TYPES.NET_STATE_CONNECTING - 连接中。很可能遇到网络抖动,SDK 在重试。接入侧可根据此状态提示“当前网络不稳定”或“连接中”
+        //    \- TIM.TYPES.NET_STATE_DISCONNECTED - 未接入网络。接入侧可根据此状态提示“当前网络不可用”。SDK 仍会继续重试,若用户网络恢复,SDK 会自动同步消息
+    });
+    // 开始登录
+    let afterLoginIM = tim.login({
+        userID: loginInfo.identifier,
+        userSig: loginInfo.userSig
+    });
+    afterLoginIM.then((imResponse) => {
+        console.log(imResponse.data, 'niubi'); // 登录成功
+        if (imResponse.data.repeatLogin === true) {
+            data.callback && data.callback()
+            // 标识账号已登录,本次登录操作为重复登录。v2.5.1 起支持
+            console.log(imResponse.data.errorInfo);
+        }
+    }).catch((imError) => {
+        console.warn('login error:', imError); // 登录失败的相关信息
+    })
+}
+
+function logout(callback) {
+    app = getApp();
+    // 退出IM登录
+    tim.logout();
+    console.log('imRoom总im退出登录')
+    app.globalData.globalWebimhandler = null;
+    callback && callback()
+}
+
+function sendCustomMsg(data, type, nickname, callback) {
+    let message = tim.createCustomMessage({
+        to: type.myselToID,
+        conversationType: type.TYPE == 'C2C' ? TIM.TYPES.CONV_C2C : TIM.TYPES.GROUP,
+        // 消息优先级,用于群聊(v2.4.2起支持)。如果某个群的消息超过了频率限制,后台会优先下发高优先级的消息,详细请参考 消息优先级与频率控制
+        // 支持的枚举值:TIM.TYPES.MSG_PRIORITY_HIGH, TIM.TYPES.MSG_PRIORITY_NORMAL(默认), TIM.TYPES.MSG_PRIORITY_LOW, TIM.TYPES.MSG_PRIORITY_LOWEST
+        // priority: TIM.TYPES.MSG_PRIORITY_HIGH,
+        payload: {
+            data: '',
+            description: '',
+            extension: data.ext,
+        }
+    });
+    let promise = tim.sendMessage(message, {
+        // 如果接收方不在线,则消息将存入漫游,且进行离线推送(在接收方 App 退后台或者进程被 kill 的情况下)。接入侧可自定义离线推送的标题及内容
+        offlinePushInfo: {
+            disablePush: false,
+            title: '收到一条新消息', // 离线推送标题
+            description: '请进入APP内查看', // 离线推送内容
+            androidOPPOChannelID: '' // 离线推送设置 OPPO 手机 8.0 系统及以上的渠道 ID
+        }
+    });
+    promise.then((imResponse) => {
+        callback && callback(data)
+    })
+}
+
+function getC2CHistoryMsgs(options) {
+    console.log(tim)
+    let promise = tim.getMessageList({
+        conversationID: 'C2C' + options.adviserId,
+        count: 10,
+        nextReqMessageID: options.nextReqMessageID
+    })
+    promise.then((res) => {
+        console.log('拉取到顾问' + options.adviserId + '的历史消息当前第' + options.nextReqMessageID + '页')
+        console.log(res)
+        options.success && options.success(JSON.parse(JSON.stringify(res.data)))
+    }).catch((err) => {
+        options.fail && options.fail(err)
+    })
+}
+
+function onSendMsg(message, type, nickname, callback, fail) {
+    let Cmessage = tim.createTextMessage({
+        to: type.myselToID,
+        conversationType: type.TYPE == 'C2C' ? TIM.TYPES.CONV_C2C : TIM.TYPES.GROUP,
+        // 消息优先级,用于群聊(v2.4.2起支持)。如果某个群的消息超过了频率限制,后台会优先下发高优先级的消息,详细请参考 消息优先级与频率控制
+        // 支持的枚举值:TIM.TYPES.MSG_PRIORITY_HIGH, TIM.TYPES.MSG_PRIORITY_NORMAL(默认), TIM.TYPES.MSG_PRIORITY_LOW, TIM.TYPES.MSG_PRIORITY_LOWEST
+        // priority: TIM.TYPES.MSG_PRIORITY_HIGH,
+        payload: {
+            text: message
+        }
+    });
+    let promise = tim.sendMessage(Cmessage);
+    promise.then((imResponse) => {
+        console.log('发送完毕', imResponse)
+        callback && callback({
+            content: message
+        })
+    }).catch((err) => {})
+}
+
+function getRecentContactList(data, callback) {
+    let promise = tim.getConversationList();
+    promise.then(function(imResponse) {
+        const conversationList = imResponse.data.conversationList; // 会话列表,用该列表覆盖原有的会话列表
+        callback && callback(conversationList.filter((item) => {
+            return item.type !== '@TIM#SYSTEM'
+        }))
+    }).catch(function(imError) {
+        console.warn('getConversationList error:', imError); // 获取会话列表失败的相关信息
+    });
+}
+
+function onBigGroupMsgNotify() {
+
+}
+
+function parseGroupTipContent(payload) {
+
+    switch (payload.operationType) {
+        case this.TIM.TYPES.GRP_TIP_MBR_JOIN:
+            return `群成员:${payload.userIDList.join(',')},加入群组`
+        case this.TIM.TYPES.GRP_TIP_MBR_QUIT:
+            return `群成员:${payload.userIDList.join(',')},退出群组`
+        case this.TIM.TYPES.GRP_TIP_MBR_KICKED_OUT:
+            return `群成员:${payload.userIDList.join(',')},被${payload.operatorID}踢出群组`
+        case this.TIM.TYPES.GRP_TIP_MBR_SET_ADMIN:
+            return `群成员:${payload.userIDList.join(',')},成为管理员`
+        case this.TIM.TYPES.GRP_TIP_MBR_CANCELED_ADMIN:
+            return `群成员:${payload.userIDList.join(',')},被撤销管理员`
+        default:
+            return '[群提示消息]'
+    }
+}
+
+function setListener(options) {
+    if (!options) {
+        console.log('setListener参数错误', options);
+        return;
+    }
+    event.onGetPusherList = options.onGetPusherList || function() {};
+    event.onIMReady = options.onIMReady || function() {};
+    event.onPusherJoin = options.onPusherJoin || function() {};
+    event.onPusherQuit = options.onPusherQuit || function() {};
+    event.onRoomClose = options.onRoomClose || function() {};
+    event.onRecvRoomTextMsg = options.onRecvRoomTextMsg || function() {};
+    event.onMsgNotify = options.onMsgNotify || function() {}; // 监听新消息(私聊(包括普通消息和全员推送消息),普通群(非直播聊天室)消息)事件,必填
+    event.onDestoryGroupNotify = options.onDestoryGroupNotify || function() {}; //群被解散(全员接收) 5
+    event.onCustomGroupNotify = options.onCustomGroupNotify || function() {}; //用户自定义通知(默认全员接收) 255
+    event.onLoginSuccess = options.onLoginSuccess || function() {}; //IM 登录成功的回调
+    event.onBigGroupMsgNotify = options.onBigGroupMsgNotify || function() {}; //监听新消息(大群)事件,必填
+}
+
+function applyJoinBigGroup(roomID, callback, callbackOptions) {
+    let res = tim.joinGroup({
+        groupID: roomID,
+        applyMessage: '视频通话申请加群',
+        type: TIM.TYPES.GRP_AVCHATROOM
+    })
+    res.then((imResponse) => {
+        console.log(imResponse, '群组资料')
+        switch (imResponse.data.status) {
+            case TIM.TYPES.JOIN_STATUS_WAIT_APPROVAL:
+                break; // 等待管理员同意
+            case TIM.TYPES.JOIN_STATUS_SUCCESS: // 加群成功
+                console.log('关键步骤:加群成功!')
+                callback && callback({
+                    errCode: 0,
+                    callback: callbackOptions
+                });
+                console.log(imResponse.data.group, '00000'); // 加入的群组资料
+                break;
+            case TIM.TYPES.JOIN_STATUS_ALREADY_IN_GROUP: // 已经在群中
+                console.log('关键步骤:加群成功2!')
+                callback && callback({
+                    errCode: 0,
+                    callback: callbackOptions
+                });
+                break;
+            default:
+                console.log('关键步骤:加群成功3!')
+                break;
+        }
+    }).catch(function(imError) {
+        console.warn('joinGroup error:', imError); // 申请加群失败的相关信息
+    });
+}
+
+async function createBigGroup(options, cb) {
+	console.log(options.roomID, '为什么要自动id?')
+	// tim.createGroup({
+	//     groupID: options.roomID,
+	//     type: TIM.TYPES.GRP_AVCHATROOM,//直播群
+	//     maxMemberNum: 500,
+	//     name: options.roomName || '',
+	//     memberList: [],
+	// }).then((imResponse) => {
+	//     cb(imResponse)
+	//     console.log(imResponse.data.group, '新版建群成功'); // 创建的群的资料
+	// }).catch(function(imError) {
+	//     console.warn('createGroup error:', imError); // 创建群组失败的相关信息
+	// })
+	// 这里是管理员创建的群,已经不是当前用户创建群的逻辑了
+	var res = await requestConfig('createGroup', {
+		groupID: options.roomID,
+		type: TIM.TYPES.GRP_AVCHATROOM,//直播群
+		maxMemberNum: 500,
+		name: options.roomName || '',
+		memberList: []
+	}, true);
+	if (res && res.success) {
+		cb()
+		console.log(options, '新版建群成功'); // 创建的群的资料
+	}
+	else{
+		console.warn('createGroup error:', res.message); // 创建群组失败的相关信息
+	}
+}
+
+function quitBigGroup(groupID) {
+    tim.quitGroup(groupID).then((res) => {
+        console.log('退出群成功')
+    });
+	// var res = await requestConfig('deleteGroupMember', {
+	// 	groupId: groupID,
+	// }, true);
+}
+
+function destroyGroup(groupID) {
+    // tim.dismissGroup(groupID).then((res) => {
+    //     console.log('解散群成功')
+    // });
+	// 管理员建群,必须是管理员销毁-此处就是调接口,让管理员销毁群
+	requestConfig('destroyGroup', {
+		groupId: groupID,
+	}, true);
+}
+module.exports = {
+    loginIM: loginIM, // 登陆IM
+    setListener: setListener,
+    logout: logout,
+    sendCustomMsg: sendCustomMsg,
+    onSendMsg: onSendMsg,
+    applyJoinBigGroup: applyJoinBigGroup,
+    quitBigGroup: quitBigGroup,
+    destroyGroup: destroyGroup,
+    getRecentContactList: getRecentContactList,
+    onBigGroupMsgNotify: onBigGroupMsgNotify,
+    getC2CHistoryMsgs: getC2CHistoryMsgs,
+    createBigGroup: createBigGroup
+}

+ 284 - 0
common/static/im/tim-handler.js

@@ -0,0 +1,284 @@
+/**
+ * tim v2.7.6
+ */
+const TIM = require('./tim-wx.js');
+let selToID
+    ,loginInfo
+    ,sdkAppID
+    ,avChatRoomId
+    ,selSess
+    ,tim
+;
+
+function sdkLogin(userInfo,  avChatRoomId, callback, callbackOptions) {
+    tim.login({userID: userInfo.identifier, userSig: userInfo.userSig})
+        .then(()=> {
+            //登录成功 加入大群
+            loginInfo = userInfo;
+            avChatRoomId = avChatRoomId;
+            callback & callback({
+                callback: callbackOptions
+            });
+
+        })
+        .catch((err) => {
+            callback & callback({
+                errCode: err,
+                errMsg: err.ErrorInfo,
+                callback: callbackOptions
+            });
+        });
+}
+
+
+// 创建群
+function createBigGroup(options, callback, callbackOptions) {
+    avChatRoomId = options.roomID;
+    tim.createGroup({
+        groupID: options.roomID,
+        type: TIM.TYPES.GRP_AVCHATROOM,
+        maxMemberNum:500,
+        name: options.roomName || '',
+        memberList: [],
+    })
+        .then(()=> { // 创建成功
+            selToID = options.roomID;
+            tim.joinGroup({ groupID: options.roomID, type: TIM.TYPES.GRP_AVCHATROOM })
+                .then(function(imResponse) {
+                    callback && callback({
+                        errCode: 0,
+                        callback: callbackOptions
+                    });
+                })
+        })
+        .catch((ret) => {
+            callback && callback({
+                errCode: ret.ErrorCode,
+                errMsg: ret.err_msg,
+                callback: callbackOptions
+            });
+        })
+}
+
+//进入群
+function applyJoinBigGroup(groupId, callback, callbackOptions) {
+    selSess = null;
+    tim.joinGroup({ groupID: groupId, type: TIM.TYPES.GRP_AVCHATROOM })
+        .then(function(imResponse) {
+            console.log(imResponse.data,'申请加群核心回调')
+            switch (imResponse.data.status) {
+                case TIM.TYPES.JOIN_STATUS_WAIT_APPROVAL: // 等待管理员同意
+                    break;
+                case TIM.TYPES.JOIN_STATUS_SUCCESS: // 加群成功
+                    selToID = groupId;
+                    callback && callback({
+                        errCode: 0,
+                        callback: callbackOptions
+                    });
+                    console.log(imResponse.data.group); // 加入的群组资料
+                    break;
+                case TIM.TYPES.JOIN_STATUS_ALREADY_IN_GROUP: // 已经在群中
+                    callback && callback({
+                        errCode: 666,
+                        callback: callbackOptions
+                    });
+                    break;
+                default:
+                    break;
+            }
+        }).catch(function(err){
+        console.error('进群请求失败', err.ErrorInfo);
+        callback && callback({
+            errCode: 999,
+            errMsg: err.ErrorInfo || 'IM进群失败',
+            callback: callbackOptions
+        });
+        console.warn('joinGroup error:', err); // 申请加群失败的相关信息
+    });
+}
+
+
+// 连麦发送自定义消息
+function sendC2CCustomMsg(toUserID, msg, callback) {
+    console.log('tim-handler发送自定义',msg,toUserID)
+    let form = {
+        data: msg.data || '',
+        description: msg.desc || '',
+        extension: msg.ext || ''
+    }
+    if (
+        form.data.length === 0 &&
+        form.description.length === 0 &&
+        form.extension.length === 0
+    ) {
+        return
+    }
+    let conversationType = form.data.includes('linkmic') ? TIM.TYPES.CONV_C2C : TIM.TYPES.CONV_GROUP
+    const message = tim.createCustomMessage({
+        to: toUserID,
+        conversationType: conversationType,
+        payload: {
+            data: form.data,
+            description: form.description,
+            extension: form.extension
+        }
+    })
+    tim.sendMessage(message,{
+        // 如果接收方不在线,则消息将存入漫游,且进行离线推送(在接收方 App 退后台或者进程被 kill 的情况下)。接入侧可自定义离线推送的标题及内容
+        offlinePushInfo: {
+            disablePush:false,
+            title: '收到一条新消息', // 离线推送标题
+            description: '请进入APP内查看', // 离线推送内容
+            androidOPPOChannelID: '' // 离线推送设置 OPPO 手机 8.0 系统及以上的渠道 ID
+        }
+    })
+        .then(() => {
+            console.log('发自定义消息成功');
+            callback && callback({
+                errCode: 0,
+                errMsg: ""
+            });
+        })
+        .catch(err => {
+            console.error('发自定义消息失败:', err);
+            callback && callback({
+                errCode: -1,
+                errMsg: '发自定义消息失败:' + err.ErrorInfo
+            });
+        })
+    Object.assign(form, {
+        data: '',
+        description: '',
+        extension: ''
+    })
+}
+
+//发送文本消息
+function sendTextMessage(msg,userInfo,callback) {
+    console.log('tim-handler普通消息',msg)
+    let message = tim.createCustomMessage({
+        to: msg.to,
+        conversationType: TIM.TYPES.CONV_GROUP,
+        // 消息优先级,用于群聊(v2.4.2起支持)。如果某个群的消息超过了频率限制,后台会优先下发高优先级的消息,详细请参考:https://cloud.tencent.com/document/product/269/3663#.E6.B6.88.E6.81.AF.E4.BC.98.E5.85.88.E7.BA.A7.E4.B8.8E.E9.A2.91.E7.8E.87.E6.8E.A7.E5.88.B6)
+        // 支持的枚举值:TIM.TYPES.MSG_PRIORITY_HIGH, TIM.TYPES.MSG_PRIORITY_NORMAL(默认), TIM.TYPES.MSG_PRIORITY_LOW, TIM.TYPES.MSG_PRIORITY_LOWEST
+        // priority: TIM.TYPES.MSG_PRIORITY_NORMAL,
+        payload: {
+            description:msg.text,
+            extension: msg.text,
+            data:'{"cmd":"CustomTextMsg","data":{"nickName":"'+userInfo.userName+'","headPic":"'+userInfo.userAvatar+'"}}'
+        }
+    });
+// 2. 发送消息
+    tim.sendMessage(message,{
+        // 如果接收方不在线,则消息将存入漫游,且进行离线推送(在接收方 App 退后台或者进程被 kill 的情况下)。接入侧可自定义离线推送的标题及内容
+        offlinePushInfo: {
+            disablePush:false,
+            title: '收到一条新消息', // 离线推送标题
+            description: '请进入APP内查看', // 离线推送内容
+            androidOPPOChannelID: '' // 离线推送设置 OPPO 手机 8.0 系统及以上的渠道 ID
+        }
+    })
+        .then(function(imResponse) {
+            // 发送成功
+            console.log(imResponse,'文本消息发送成功');
+            callback && callback();
+        }).catch(function(imError) {
+        // 发送失败
+        console.warn('sendMessage error:', imError);
+    });
+
+}
+//发送文本消息
+function sendTextNewMessage(msg,userInfo,callback) {
+    console.log('tim-handler普通消息',msg)
+    let message = tim.createTextMessage({
+        to: msg.to,
+        conversationType: TIM.TYPES.CONV_GROUP,
+        // 消息优先级,用于群聊(v2.4.2起支持)。如果某个群的消息超过了频率限制,后台会优先下发高优先级的消息,详细请参考:https://cloud.tencent.com/document/product/269/3663#.E6.B6.88.E6.81.AF.E4.BC.98.E5.85.88.E7.BA.A7.E4.B8.8E.E9.A2.91.E7.8E.87.E6.8E.A7.E5.88.B6)
+        // 支持的枚举值:TIM.TYPES.MSG_PRIORITY_HIGH, TIM.TYPES.MSG_PRIORITY_NORMAL(默认), TIM.TYPES.MSG_PRIORITY_LOW, TIM.TYPES.MSG_PRIORITY_LOWEST
+        // priority: TIM.TYPES.MSG_PRIORITY_NORMAL,
+        payload: {
+            text:'{"cmd":"CustomTextMsg","data":{"userName":"'+userInfo.userName+'","nickName":"'+userInfo.userName+'","headPic":"'+userInfo.userAvatar+'","text":"'+msg.text+'"}}'
+        }
+    });
+// 2. 发送消息
+    tim.sendMessage(message,{
+        // 如果接收方不在线,则消息将存入漫游,且进行离线推送(在接收方 App 退后台或者进程被 kill 的情况下)。接入侧可自定义离线推送的标题及内容
+        offlinePushInfo: {
+            disablePush:false,
+            title: '', // 离线推送标题
+            description: '', // 离线推送内容
+            androidOPPOChannelID: '' // 离线推送设置 OPPO 手机 8.0 系统及以上的渠道 ID
+        }
+    })
+        .then(function(imResponse) {
+            // 发送成功
+            console.log(imResponse,'文本消息发送成功');
+            callback && callback();
+        }).catch(function(imError) {
+        // 发送失败
+        console.warn('sendMessage error:', imError);
+    });
+
+}
+
+
+
+// 解散群
+function destroyGroup() {
+    tim.dismissGroup(avChatRoomId)
+        .then(function(imResponse) { // 解散成功
+            avChatRoomId = '';
+            console.log(imResponse.data.groupID,'解散成功'); // 被解散的群组 ID
+        }).catch(function(imError) {
+        console.warn('dismissGroup error:', imError); // 解散群组失败的相关信息
+    });
+}
+
+
+//退出群
+function quitBigGroup() {
+    if(avChatRoomId){
+        tim.quitGroup(avChatRoomId)
+            .then(function(imResponse) {
+                console.log(imResponse.data.groupID, 'IM退群成功'); // 退出成功的群 ID
+            }).catch(function(imError){
+            console.warn('quitGroup error:', imError); // 退出群组失败的相关信息
+        })
+    }
+}
+
+//登出
+function logout() {
+    //登出
+    tim.logout()
+        .then(function(imResponse) {
+            console.log(imResponse.data,'IM登出成功'); // 登出成功
+            if(loginInfo) {
+                loginInfo.identifier = null;
+                loginInfo.userSig = null;
+            }
+        }).catch(function(imError) {
+        console.warn('logout error:', imError);
+    })
+}
+
+function init(opts){
+    sdkAppID = opts.sdkAppID;
+    avChatRoomId = opts.avChatRoomId || 0;
+    selToID = opts.selToID;
+    // 初始化 SDK 实例
+    tim = opts.tim;
+}
+module.exports = {
+    init : init,
+    sdkLogin : sdkLogin,
+    createBigGroup : createBigGroup,
+    applyJoinBigGroup : applyJoinBigGroup,
+    sendC2CCustomMsg : sendC2CCustomMsg,
+    sendTextMessage : sendTextMessage,
+    sendTextNewMessage : sendTextNewMessage,
+    quitBigGroup : quitBigGroup,
+    destroyGroup : destroyGroup,
+    logout : logout,
+};

File diff suppressed because it is too large
+ 1 - 0
common/static/im/tim-wx.js


File diff suppressed because it is too large
+ 1 - 0
common/static/json/wow-emoji.json


+ 76 - 0
common/static/lib/intersection-observer.js

@@ -0,0 +1,76 @@
+export default class BadIntersectionObserver {
+    constructor(options) {
+        this.$options = {
+            context: null,
+            selector: null,
+            onEach: res => res.dataset,
+            onFinal: () => null,
+            relativeTo: null,
+            threshold: 0.5,
+            delay: 200,
+            observeAll: false,
+            initialRatio: 0,
+            ...options,
+        }
+        this.$observer = null
+    }
+
+    connect() {
+        if (this.$observer) return this
+        this.$observer = this._createObserver()
+        return this
+    }
+
+    reconnect() {
+        this.disconnect()
+        this.connect()
+    }
+
+    disconnect() {
+        if (!this.$observer) return
+        const ob = this.$observer
+        if (ob.$timer) clearTimeout(ob.$timer)
+        ob.disconnect()
+        this.$observer = null
+    }
+
+    _createObserver() {
+        const opt = this.$options
+        const observerOptions = {
+            thresholds: [opt.threshold],
+            observeAll: opt.observeAll,
+            initialRatio: opt.initialRatio,
+        }
+
+        // 创建监听器
+        const ob = uni.createIntersectionObserver(opt.context, observerOptions)
+
+        // 相交区域设置
+        if (opt.relativeTo) ob.relativeTo(opt.relativeTo)
+        else ob.relativeToViewport()
+
+        // 开始监听
+        // let finalData = []
+        // let isCollecting = false
+        ob.observe(opt.selector, res => {
+            const {
+                intersectionRatio
+            } = res
+            const visible = intersectionRatio >= opt.threshold
+            if (!visible) return
+
+            const data = opt.onEach(res)
+            // finalData.push(data)
+
+            // if (isCollecting) return
+            // isCollecting = true
+
+            // setTimeout(() => {
+            //     opt.onFinal.call(null, finalData)
+            //     finalData = []
+            //     isCollecting = false
+            // }, opt.delay)
+        })
+        return ob
+    }
+}

File diff suppressed because it is too large
+ 10 - 0
common/static/lib/lottie-miniprogram.js


+ 7 - 0
common/static/lib/promisify.js

@@ -0,0 +1,7 @@
+module.exports = (api) => {
+    return (options, ...params) => {
+        return new Promise((resolve, reject) => {
+            api(Object.assign({}, options, { success: resolve, fail: reject }), ...params);
+        });
+    }
+}

+ 160 - 0
common/static/lib/report.js

@@ -0,0 +1,160 @@
+/**
+ * 连通上报js
+ */
+var str_appid = 1252463788,
+    str_platform = 'weixin',
+    str_appversion = '1.2.477',
+    str_sdkversion = '',
+    str_common_version = '',
+    str_nickname = '',
+    str_device = '',
+    str_device_type = '',
+    reportData = {
+      str_roomid: '',
+      str_room_creator: '',
+      str_userid: '',
+      str_play_info: '',
+      str_push_info: '',
+      int64_ts_enter_room: -99999,
+      int64_tc_join_group: -99999,
+      int64_tc_get_pushers: -99999,
+      int64_tc_play_stream: -99999,
+      int64_tc_get_pushurl: -99999,
+      int64_tc_push_stream: -99999,
+      int64_tc_add_pusher: -99999,
+      int64_tc_enter_room: -99999
+    },
+    streamData = {
+      int64_ts_add_pusher: 0,
+      int64_ts_play_stream: 0
+    }
+
+// 获取用户信息
+// wx.getUserInfo({
+//   withCredentials: false,
+//   success: function (ret) {
+//     str_nickname = ret.userInfo.nickName;
+//   }
+// });
+// 获取设备信息
+var systemInfo = wx.getSystemInfoSync();
+str_sdkversion = systemInfo.version;
+str_common_version = systemInfo.SDKVersion;
+str_device = systemInfo.model;
+str_device_type = systemInfo.system;
+
+
+/**
+ * 设置参数
+ */
+function setReportData(options) {
+  // 第一次进来重置数据
+  if (options.int64_ts_enter_room) {
+    console.log('第一次进来重置数据');
+    clearData();
+  }
+  for(var item in reportData) {
+    if(options[item]) {
+      reportData[item] = options[item];
+    }
+  }
+  for (var item in streamData) {
+    if (options[item]) {
+      streamData[item] = options[item];
+    }
+  }
+  // console.warn('上报数据: ', reportData, streamData);
+  // 连通率上报前做负值判断
+  for (var item in reportData) {
+    if (!isNaN(reportData[item]) && item != 'int64_tc_enter_room' && reportData[item] < 0)
+      return;
+  } 
+  if (streamData.int64_ts_add_pusher && streamData.int64_ts_play_stream) {
+    reportData.int64_tc_enter_room = Math.max(streamData.int64_ts_add_pusher, streamData.int64_ts_play_stream) - reportData.int64_ts_enter_room;
+    // 上报:只对进房进行上报
+    // console.log('走完所有流程上报');
+    reportData.str_room_creator && reportData.str_userid && reportData.str_room_creator != reportData.str_userid && report();
+  }
+}
+
+/**
+ * 上报cgi
+ */
+function report() {
+  // 有房间id与用户id才上报
+  if (!reportData.str_roomid || !reportData.str_userid) {
+    clearData();
+    return;
+  }
+  // 创建房间不加入上报
+  if (reportData.str_room_creator == reportData.str_userid) {
+    clearData();
+    return;
+  } 
+  var data = reportData;
+  data.str_appid = str_appid;
+  data.str_platform = str_platform;
+  data.str_appversion = str_appversion;
+  data.str_sdkversion = str_sdkversion;
+  data.str_common_version = str_common_version;
+  data.str_nickname = str_nickname;
+  data.str_device = str_device;
+  data.str_device_type = str_device_type;
+  console.log('真正上报数据: ', data);
+  wx.request({
+    url: 'https://roomtest.qcloud.com/weapp/utils/report',
+    data: {
+      reportID: 1,
+      data: data
+    },
+    method: 'POST',
+    header: {
+      'content-type': 'application/json' // 默认值
+    },
+    success: function (ret) { 
+      if(ret.data.code) {
+        console.log('上报失败:' + ret.data.code + ret.data.message);
+      } else {
+        console.log('上报成功');
+      }
+    },
+    fail: function () { console.log('report error') },
+    complete: function () {}
+  });
+  clearData();
+}
+
+/**
+ * 重置参数
+ */
+function clearData() {
+  reportData = {
+    str_roomid: '',
+    str_room_creator: '',
+    str_userid: '',
+    str_play_info: '',
+    str_push_info: '',
+    int64_ts_enter_room: -99999,
+    int64_tc_join_group: -99999,
+    int64_tc_get_pushers: -99999,
+    int64_tc_play_stream: -99999,
+    int64_tc_get_pushurl: -99999,
+    int64_tc_push_stream: -99999,
+    int64_tc_add_pusher: -99999,
+    int64_tc_enter_room: -99999
+  };
+  streamData = {
+    int64_ts_add_pusher: 0,
+    int64_ts_play_stream: 0
+  };
+}
+
+/**
+ * 对外暴露函数
+ * @type {Object}
+ */
+module.exports = {
+  setReportData: setReportData,
+  report: report,
+  clearData: clearData
+}

+ 622 - 0
common/static/lib/runtime.js

@@ -0,0 +1,622 @@
+/**
+ * Copyright (c) 2014-present, Facebook, Inc.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+var regeneratorRuntime = (function (exports) {
+    "use strict";
+
+    var Op = Object.prototype;
+    var hasOwn = Op.hasOwnProperty;
+    var undefined; // More compressible than void 0.
+    var $Symbol = typeof Symbol === "function" ? Symbol : {};
+    var iteratorSymbol = $Symbol.iterator || "@@iterator";
+    var asyncIteratorSymbol = $Symbol.asyncIterator || "@@asyncIterator";
+    var toStringTagSymbol = $Symbol.toStringTag || "@@toStringTag";
+
+    function wrap(innerFn, outerFn, self, tryLocsList) {
+        // If outerFn provided and outerFn.prototype is a Generator, then outerFn.prototype instanceof Generator.
+        var protoGenerator = outerFn && outerFn.prototype instanceof Generator ? outerFn : Generator;
+        var generator = Object.create(protoGenerator.prototype);
+        var context = new Context(tryLocsList || []);
+
+        // The ._invoke method unifies the implementations of the .next,
+        // .throw, and .return methods.
+        generator._invoke = makeInvokeMethod(innerFn, self, context);
+
+        return generator;
+    }
+    exports.wrap = wrap;
+
+    function tryCatch(fn, obj, arg) {
+        try {
+            return { type: "normal", arg: fn.call(obj, arg) };
+        } catch (err) {
+            return { type: "throw", arg: err };
+        }
+    }
+
+    var GenStateSuspendedStart = "suspendedStart";
+    var GenStateSuspendedYield = "suspendedYield";
+    var GenStateExecuting = "executing";
+    var GenStateCompleted = "completed";
+
+    var ContinueSentinel = {};
+
+    function Generator() {}
+    function GeneratorFunction() {}
+    function GeneratorFunctionPrototype() {}
+
+    // This is a polyfill for %IteratorPrototype% for environments that
+    // don't natively support it.
+    var IteratorPrototype = {};
+    IteratorPrototype[iteratorSymbol] = function () {
+        return this;
+    };
+
+    var getProto = Object.getPrototypeOf;
+    var NativeIteratorPrototype = getProto && getProto(getProto(values([])));
+    if (NativeIteratorPrototype &&
+        NativeIteratorPrototype !== Op &&
+        hasOwn.call(NativeIteratorPrototype, iteratorSymbol)) {
+        // This environment has a native %IteratorPrototype%; use it instead
+        // of the polyfill.
+        IteratorPrototype = NativeIteratorPrototype;
+    }
+
+    var Gp = GeneratorFunctionPrototype.prototype =
+        Generator.prototype = Object.create(IteratorPrototype);
+    GeneratorFunction.prototype = Gp.constructor = GeneratorFunctionPrototype;
+    GeneratorFunctionPrototype.constructor = GeneratorFunction;
+    GeneratorFunctionPrototype[toStringTagSymbol] =
+        GeneratorFunction.displayName = "GeneratorFunction";
+
+    // Helper for defining the .next, .throw, and .return methods of the
+    // Iterator interface in terms of a single ._invoke method.
+    function defineIteratorMethods(prototype) {
+        ["next", "throw", "return"].forEach(function(method) {
+            prototype[method] = function(arg) {
+                return this._invoke(method, arg);
+            };
+        });
+    }
+
+    exports.isGeneratorFunction = function(genFun) {
+        var ctor = typeof genFun === "function" && genFun.constructor;
+        return ctor
+            ? ctor === GeneratorFunction ||
+            // For the native GeneratorFunction constructor, the best we can
+            // do is to check its .name property.
+            (ctor.displayName || ctor.name) === "GeneratorFunction"
+            : false;
+    };
+
+    exports.mark = function(genFun) {
+        if (Object.setPrototypeOf) {
+            Object.setPrototypeOf(genFun, GeneratorFunctionPrototype);
+        } else {
+            genFun.__proto__ = GeneratorFunctionPrototype;
+            if (!(toStringTagSymbol in genFun)) {
+                genFun[toStringTagSymbol] = "GeneratorFunction";
+            }
+        }
+        genFun.prototype = Object.create(Gp);
+        return genFun;
+    };
+
+    // Within the body of any async function, `await x` is transformed to
+    // `yield regeneratorRuntime.awrap(x)`, so that the runtime can test
+    // `hasOwn.call(value, "__await")` to determine if the yielded value is
+    // meant to be awaited.
+    exports.awrap = function(arg) {
+        return { __await: arg };
+    };
+
+    function AsyncIterator(generator) {
+        function invoke(method, arg, resolve, reject) {
+            var record = tryCatch(generator[method], generator, arg);
+            if (record.type === "throw") {
+                reject(record.arg);
+            } else {
+                var result = record.arg;
+                var value = result.value;
+                if (value &&
+                    typeof value === "object" &&
+                    hasOwn.call(value, "__await")) {
+                    return Promise.resolve(value.__await).then(function(value) {
+                        invoke("next", value, resolve, reject);
+                    }, function(err) {
+                        invoke("throw", err, resolve, reject);
+                    });
+                }
+
+                return Promise.resolve(value).then(function(unwrapped) {
+                    // When a yielded Promise is resolved, its final value becomes
+                    // the .value of the Promise<{value,done}> result for the
+                    // current iteration.
+                    result.value = unwrapped;
+                    resolve(result);
+                }, function(error) {
+                    // If a rejected Promise was yielded, throw the rejection back
+                    // into the async generator function so it can be handled there.
+                    return invoke("throw", error, resolve, reject);
+                });
+            }
+        }
+
+        var previousPromise;
+
+        function enqueue(method, arg) {
+            function callInvokeWithMethodAndArg() {
+                return new Promise(function(resolve, reject) {
+                    invoke(method, arg, resolve, reject);
+                });
+            }
+
+            return previousPromise =
+                previousPromise ? previousPromise.then(
+                    callInvokeWithMethodAndArg,
+                    callInvokeWithMethodAndArg
+                ) : callInvokeWithMethodAndArg();
+        }
+        this._invoke = enqueue;
+    }
+
+    defineIteratorMethods(AsyncIterator.prototype);
+    AsyncIterator.prototype[asyncIteratorSymbol] = function () {
+        return this;
+    };
+    exports.AsyncIterator = AsyncIterator;
+
+    // Note that simple async functions are implemented on top of
+    // AsyncIterator objects; they just return a Promise for the value of
+    // the final result produced by the iterator.
+    exports.async = function(innerFn, outerFn, self, tryLocsList) {
+        var iter = new AsyncIterator(
+            wrap(innerFn, outerFn, self, tryLocsList)
+        );
+
+        return exports.isGeneratorFunction(outerFn)
+            ? iter // If outerFn is a generator, return the full iterator.
+            : iter.next().then(function(result) {
+                return result.done ? result.value : iter.next();
+            });
+    };
+
+    function makeInvokeMethod(innerFn, self, context) {
+        var state = GenStateSuspendedStart;
+
+        return function invoke(method, arg) {
+            if (state === GenStateExecuting) {
+                throw new Error("Generator is already running");
+            }
+
+            if (state === GenStateCompleted) {
+                if (method === "throw") {
+                    throw arg;
+                }
+
+                // Be forgiving, per 25.3.3.3.3 of the spec:
+                // https://people.mozilla.org/~jorendorff/es6-draft.html#sec-generatorresume
+                return doneResult();
+            }
+
+            context.method = method;
+            context.arg = arg;
+
+            while (true) {
+                var delegate = context.delegate;
+                if (delegate) {
+                    var delegateResult = maybeInvokeDelegate(delegate, context);
+                    if (delegateResult) {
+                        if (delegateResult === ContinueSentinel) continue;
+                        return delegateResult;
+                    }
+                }
+
+                if (context.method === "next") {
+                    // Setting context._sent for legacy support of Babel's
+                    // function.sent implementation.
+                    context.sent = context._sent = context.arg;
+
+                } else if (context.method === "throw") {
+                    if (state === GenStateSuspendedStart) {
+                        state = GenStateCompleted;
+                        throw context.arg;
+                    }
+
+                    context.dispatchException(context.arg);
+
+                } else if (context.method === "return") {
+                    context.abrupt("return", context.arg);
+                }
+
+                state = GenStateExecuting;
+
+                var record = tryCatch(innerFn, self, context);
+                if (record.type === "normal") {
+                    // If an exception is thrown from innerFn, we leave state ===
+                    // GenStateExecuting and loop back for another invocation.
+                    state = context.done
+                        ? GenStateCompleted
+                        : GenStateSuspendedYield;
+
+                    if (record.arg === ContinueSentinel) {
+                        continue;
+                    }
+
+                    return {
+                        value: record.arg,
+                        done: context.done
+                    };
+
+                } else if (record.type === "throw") {
+                    state = GenStateCompleted;
+                    context.method = "throw";
+                    context.arg = record.arg;
+                }
+            }
+        };
+    }
+
+    function maybeInvokeDelegate(delegate, context) {
+        var method = delegate.iterator[context.method];
+        if (method === undefined) {
+            context.delegate = null;
+
+            if (context.method === "throw") {
+                if (delegate.iterator.return) {
+                    context.method = "return";
+                    context.arg = undefined;
+                    maybeInvokeDelegate(delegate, context);
+
+                    if (context.method === "throw") {
+                        return ContinueSentinel;
+                    }
+                }
+
+                context.method = "throw";
+                context.arg = new TypeError(
+                    "The iterator does not provide a 'throw' method");
+            }
+
+            return ContinueSentinel;
+        }
+
+        var record = tryCatch(method, delegate.iterator, context.arg);
+
+        if (record.type === "throw") {
+            context.method = "throw";
+            context.arg = record.arg;
+            context.delegate = null;
+            return ContinueSentinel;
+        }
+
+        var info = record.arg;
+
+        if (! info) {
+            context.method = "throw";
+            context.arg = new TypeError("iterator result is not an object");
+            context.delegate = null;
+            return ContinueSentinel;
+        }
+
+        if (info.done) {
+            context[delegate.resultName] = info.value;
+
+            context.next = delegate.nextLoc;
+
+            if (context.method !== "return") {
+                context.method = "next";
+                context.arg = undefined;
+            }
+
+        } else {
+            // Re-yield the result returned by the delegate method.
+            return info;
+        }
+
+        // The delegate iterator is finished, so forget it and continue with
+        // the outer generator.
+        context.delegate = null;
+        return ContinueSentinel;
+    }
+
+    // Define Generator.prototype.{next,throw,return} in terms of the
+    // unified ._invoke helper method.
+    defineIteratorMethods(Gp);
+
+    Gp[toStringTagSymbol] = "Generator";
+    Gp[iteratorSymbol] = function() {
+        return this;
+    };
+
+    Gp.toString = function() {
+        return "[object Generator]";
+    };
+
+    function pushTryEntry(locs) {
+        var entry = { tryLoc: locs[0] };
+
+        if (1 in locs) {
+            entry.catchLoc = locs[1];
+        }
+
+        if (2 in locs) {
+            entry.finallyLoc = locs[2];
+            entry.afterLoc = locs[3];
+        }
+
+        this.tryEntries.push(entry);
+    }
+
+    function resetTryEntry(entry) {
+        var record = entry.completion || {};
+        record.type = "normal";
+        delete record.arg;
+        entry.completion = record;
+    }
+
+    function Context(tryLocsList) {
+        this.tryEntries = [{ tryLoc: "root" }];
+        tryLocsList.forEach(pushTryEntry, this);
+        this.reset(true);
+    }
+
+    exports.keys = function(object) {
+        var keys = [];
+        for (var key in object) {
+            keys.push(key);
+        }
+        keys.reverse();
+        return function next() {
+            while (keys.length) {
+                var key = keys.pop();
+                if (key in object) {
+                    next.value = key;
+                    next.done = false;
+                    return next;
+                }
+            }
+
+            next.done = true;
+            return next;
+        };
+    };
+
+    function values(iterable) {
+        if (iterable) {
+            var iteratorMethod = iterable[iteratorSymbol];
+            if (iteratorMethod) {
+                return iteratorMethod.call(iterable);
+            }
+
+            if (typeof iterable.next === "function") {
+                return iterable;
+            }
+
+            if (!isNaN(iterable.length)) {
+                var i = -1, next = function next() {
+                    while (++i < iterable.length) {
+                        if (hasOwn.call(iterable, i)) {
+                            next.value = iterable[i];
+                            next.done = false;
+                            return next;
+                        }
+                    }
+
+                    next.value = undefined;
+                    next.done = true;
+
+                    return next;
+                };
+
+                return next.next = next;
+            }
+        }
+
+        // Return an iterator with no values.
+        return { next: doneResult };
+    }
+    exports.values = values;
+
+    function doneResult() {
+        return { value: undefined, done: true };
+    }
+
+    Context.prototype = {
+        constructor: Context,
+
+        reset: function(skipTempReset) {
+            this.prev = 0;
+            this.next = 0;
+            this.sent = this._sent = undefined;
+            this.done = false;
+            this.delegate = null;
+
+            this.method = "next";
+            this.arg = undefined;
+
+            this.tryEntries.forEach(resetTryEntry);
+
+            if (!skipTempReset) {
+                for (var name in this) {
+                    // Not sure about the optimal order of these conditions:
+                    if (name.charAt(0) === "t" &&
+                        hasOwn.call(this, name) &&
+                        !isNaN(+name.slice(1))) {
+                        this[name] = undefined;
+                    }
+                }
+            }
+        },
+
+        stop: function() {
+            this.done = true;
+
+            var rootEntry = this.tryEntries[0];
+            var rootRecord = rootEntry.completion;
+            if (rootRecord.type === "throw") {
+                throw rootRecord.arg;
+            }
+
+            return this.rval;
+        },
+
+        dispatchException: function(exception) {
+            if (this.done) {
+                throw exception;
+            }
+
+            var context = this;
+            function handle(loc, caught) {
+                record.type = "throw";
+                record.arg = exception;
+                context.next = loc;
+
+                if (caught) {
+                    context.method = "next";
+                    context.arg = undefined;
+                }
+
+                return !! caught;
+            }
+
+            for (var i = this.tryEntries.length - 1; i >= 0; --i) {
+                var entry = this.tryEntries[i];
+                var record = entry.completion;
+
+                if (entry.tryLoc === "root") {
+                    return handle("end");
+                }
+
+                if (entry.tryLoc <= this.prev) {
+                    var hasCatch = hasOwn.call(entry, "catchLoc");
+                    var hasFinally = hasOwn.call(entry, "finallyLoc");
+
+                    if (hasCatch && hasFinally) {
+                        if (this.prev < entry.catchLoc) {
+                            return handle(entry.catchLoc, true);
+                        } else if (this.prev < entry.finallyLoc) {
+                            return handle(entry.finallyLoc);
+                        }
+
+                    } else if (hasCatch) {
+                        if (this.prev < entry.catchLoc) {
+                            return handle(entry.catchLoc, true);
+                        }
+
+                    } else if (hasFinally) {
+                        if (this.prev < entry.finallyLoc) {
+                            return handle(entry.finallyLoc);
+                        }
+
+                    } else {
+                        throw new Error("try statement without catch or finally");
+                    }
+                }
+            }
+        },
+
+        abrupt: function(type, arg) {
+            for (var i = this.tryEntries.length - 1; i >= 0; --i) {
+                var entry = this.tryEntries[i];
+                if (entry.tryLoc <= this.prev &&
+                    hasOwn.call(entry, "finallyLoc") &&
+                    this.prev < entry.finallyLoc) {
+                    var finallyEntry = entry;
+                    break;
+                }
+            }
+
+            if (finallyEntry &&
+                (type === "break" ||
+                    type === "continue") &&
+                finallyEntry.tryLoc <= arg &&
+                arg <= finallyEntry.finallyLoc) {
+                finallyEntry = null;
+            }
+
+            var record = finallyEntry ? finallyEntry.completion : {};
+            record.type = type;
+            record.arg = arg;
+
+            if (finallyEntry) {
+                this.method = "next";
+                this.next = finallyEntry.finallyLoc;
+                return ContinueSentinel;
+            }
+
+            return this.complete(record);
+        },
+
+        complete: function(record, afterLoc) {
+            if (record.type === "throw") {
+                throw record.arg;
+            }
+
+            if (record.type === "break" ||
+                record.type === "continue") {
+                this.next = record.arg;
+            } else if (record.type === "return") {
+                this.rval = this.arg = record.arg;
+                this.method = "return";
+                this.next = "end";
+            } else if (record.type === "normal" && afterLoc) {
+                this.next = afterLoc;
+            }
+
+            return ContinueSentinel;
+        },
+
+        finish: function(finallyLoc) {
+            for (var i = this.tryEntries.length - 1; i >= 0; --i) {
+                var entry = this.tryEntries[i];
+                if (entry.finallyLoc === finallyLoc) {
+                    this.complete(entry.completion, entry.afterLoc);
+                    resetTryEntry(entry);
+                    return ContinueSentinel;
+                }
+            }
+        },
+
+        "catch": function(tryLoc) {
+            for (var i = this.tryEntries.length - 1; i >= 0; --i) {
+                var entry = this.tryEntries[i];
+                if (entry.tryLoc === tryLoc) {
+                    var record = entry.completion;
+                    if (record.type === "throw") {
+                        var thrown = record.arg;
+                        resetTryEntry(entry);
+                    }
+                    return thrown;
+                }
+            }
+
+            throw new Error("illegal catch attempt");
+        },
+
+        delegateYield: function(iterable, resultName, nextLoc) {
+            this.delegate = {
+                iterator: values(iterable),
+                resultName: resultName,
+                nextLoc: nextLoc
+            };
+
+            if (this.method === "next") {
+                // Deliberately forget the last sent value so that we don't
+                // accidentally pass it on to the delegate.
+                this.arg = undefined;
+            }
+
+            return ContinueSentinel;
+        }
+    };
+
+    return exports;
+
+}(
+    typeof module === "object" ? module.exports : {}
+));

+ 245 - 0
common/static/templateMethod.js

@@ -0,0 +1,245 @@
+var app = getApp(); //获取应用实例
+const util = require('@/static/utils/util.js');
+const config = require('@/static/config.js');
+import requestConfig from '@/static/lib/requestConfig';
+import Bus from '@/common/bus';
+export default {
+    data(){
+        return {
+            doubleClick:false,
+            height: 0,
+            selfTabbarBottom: '0px',
+            computerHeight: 'calc(100vh - 58px)',
+            statusBarHeight: 0,
+            _scrollDistance: 0, //滑动的距离
+            showShareOptions: false,
+            showOption: true,
+        }
+    },
+    watch:{
+    },
+    methods:{
+        scrollExp(e) {
+            if (e.detail) {
+                this._scrollDistance = e.detail.scrollTop;
+                if (this._scrollDistance >= 30) {
+                    this.showNav = true;
+                    if(this.hasOwnProperty('myNavBarData') && this.myNavBarData){
+                        this.myNavBarData.navBarColor = (this.globalCityListConfig && this.globalCityListConfig.backgroundColor) ? this.globalCityListConfig.backgroundColor: '#fff';
+                        if(!this.currentData){
+                            this.pageThemeColor = {
+                                textColor1:'#000'
+                            };
+                        }
+                        this.myNavBarData.titleColor = this.pageThemeColor.textColor1;
+                        this.myNavBarData.title = "城市列表";
+                    }
+                } else {
+                    this.showNav = false;
+                    if(this.hasOwnProperty('myNavBarData') && this.myNavBarData){
+                        this.myNavBarData.navBarColor = 0;
+                        this.myNavBarData.titleColor = '#fff';
+                        this.myNavBarData.title = "";
+                        if(!this.currentData){
+                            this.pageThemeColor = {
+                                textColor1:'#fff'
+                            };
+                        }
+                    }
+                }
+            }
+        },
+        //顶部搜索按钮
+        goToSearch(type=1) {
+            // let url = '/subPackage/pages/searchCondition/searchCondition';
+            let url = "/subPackage/pages/searchNewModule/searchNewModule";
+            if(type==2){//新版的搜索页面
+                url = "/subPackage/pages/searchNewModule/searchNewModule"
+            }
+            uni.navigateTo({
+                url: url,
+                success: () => {},
+            });
+        },
+        //切换城市
+        navigateToCity(e) {
+            uni.navigateTo({
+                url: '/subPackage/pages/addressModule/addressModule'
+            });
+        },
+        //跳转到筛选页面
+        // navigateToSearch(type) {
+        //     var _url = '/pages/screenSearchListModule/screenSearchListModule';
+        //     if (type) {
+        //         _url = _url + "?openType=" + type;
+        //     }
+        //     uni.navigateTo({
+        //         url: _url
+        //     });
+        // },
+        navigateFuc(e) {
+            let eventOption = {};
+            if (e) {
+                // this.$emit("navigateFuc", e, eventOption);
+                // 发送 navigateFucFromNav 主题 的Bus消息
+                Bus.$emit('navigateFucFromNav', e);
+            }
+        },
+        //颜色值转换
+        hexToRgba(color, opacity) {
+            return util.hexToRgba(color, opacity)
+        },
+        /**
+         * 单页模式
+         */
+        showSingleModel() {
+            app = getApp();
+            let _scene = (app.globalData.launchInfo && app.globalData.launchInfo.scene) ? app.globalData.launchInfo
+                .scene : '';
+            if (_scene == '1154') { //单页模式下设置自定义导航栏无效
+                this.specialHeight = (app.globalData.navigateStatusContainerHeight + 'px');
+                this.specialTop = (app.globalData.navigateStatusContainerHeight + 'px');
+                console.log("this.specialTop", this.specialTop);
+            }
+            this.singlePageStatus = app.globalData.singlePageStatus;
+        },
+        showShareOption() {
+            this.showShareOptions = true;
+        },
+        hidenOption() {
+            this.showOption = true;
+        },
+        getRandomArrayElements(arr, count) {
+            var shuffled = arr.slice(0),
+                i = arr.length,
+                min = i - count,
+                temp, index;
+            while (i-- > min) {
+                index = Math.floor((i + 1) * Math.random());
+                temp = shuffled[index];
+                shuffled[index] = shuffled[i];
+                shuffled[i] = temp;
+            }
+            return shuffled.slice(min);
+        },
+        catchTouchMove: function() {
+        	return false;
+        },
+        //跳转到项目
+        async navigateToProject(e) {
+            var self = this;
+            let houseId = e.currentTarget.dataset.houseid || e.currentTarget.dataset.houseid;
+            let requestData = {
+                houseId: houseId,
+                requestCount: 1,
+                componentCount: 1,
+            };
+            if(this.doubleClick){
+                return false;
+            }
+            this.doubleClick = true;
+            const res = await requestConfig('queryXcxPage', requestData, true);
+            if (res && res.success && res.single && res.single.jsonString != null) {
+                uni.navigateTo({
+                    url: '/pages/index/index?houseId=' + houseId,
+                    success: function() {},
+                    fail: function(res) {
+                        console.log(res)
+                    },
+                    complete() {
+                        self.doubleClick = false;
+                    }
+                })
+            } else {
+                uni.showToast({
+                    title: '敬请期待',
+                    icon: 'none',
+                    duration: 1500,
+                })
+                this.doubleClick = false;
+            }
+        },
+        async queryCityNews() {
+            let res = await requestConfig('queryCityNews', {
+                brandId: config.brandId,
+                cityName: this.currentCity
+            })
+            if (res && res.success) {
+                res.list.sort((a, b) => {
+                    return a.orderNumber - b.orderNumber
+                })
+                this.newsList = res.list.splice(0, 2)
+            }
+        },
+        async goNews(e) {
+            const data = e;
+            var _link = "";
+            var _title = "";
+            if (data.type == 3) {
+                _link = data.newsUrl;
+                _title = data.title;
+            } else if (data.type == 1 || !data.type) {
+                _link = data.linkUrl;
+                _title = data.title;
+            } else {
+                let res = await requestConfig('queryNewsById', {
+                    id: data.referNewsId
+                })
+                if (res.success && res.single) {
+                    if (res.single.type == 5) {
+                        _link = res.single.newsUrl;
+                        _title = res.single.title;
+                    } else {
+                        _link = res.single.linkUrl;
+                        _title = res.single.title;
+                    }
+                }
+            }
+            let token = data.linkUrl.split('?newsToken=')[1] || '';
+            app.checkNewsStatus(token, () => {
+                if (data.content && data.content.length > 0) {
+                    let path = '/subPackage/pages/news/newsDetail/newsDetail?newsToken=' + token;
+                    console.log("path", path);
+                    uni.navigateTo({
+                        url: path,
+                        fail: function(res) {
+                            console.log(res)
+                        },
+                    })
+                } else {
+                    if (app.checkWebviewLink(_link)) {
+                        uni.showToast({
+                            title: '敬请期待',
+                            icon: 'none',
+                            duration: 1500,
+                        })
+                    } else {
+                        uni.navigateTo({
+                            url: '/pages/webView/webView?view=' + encodeURIComponent(_link) +
+                                '&title=' + _title,
+                            fail: function(res) {
+                                console.log(res)
+                            },
+                        })
+                    }
+                }
+            }, () => {
+                uni.showToast({
+                    title: '该页面已下线',
+                    icon: 'none',
+                    duration: 1500,
+                })
+            });
+        },
+        getDeviceInfor: function() {
+            const res = uni.getSystemInfoSync();
+            if (res.model.indexOf('iPhone') >= 0) {
+                this.selfTabbarBottom = '0px';
+                this.computerHeight = 'calc(100vh - 58px - 0px)';
+            }
+            this.height = app.globalData.navigateStatusContainerHeight;
+            this.statusBarHeight = app.globalData.statusBarHeight;
+            console.log("getDeviceInfor", res);
+        },
+    }
+}

+ 136 - 0
common/uni-nvue.css

@@ -0,0 +1,136 @@
+/* #ifndef APP-PLUS-NVUE */
+page {
+    min-height: 100%;
+    height: auto;
+}
+/* #endif */
+
+/* 解决头条小程序字体图标不显示问题,因为头条运行时自动插入了span标签,且有全局字体 */
+/* #ifdef MP-TOUTIAO */
+/* text :not(view) {
+    font-family: uniicons;
+} */
+/* #endif */
+
+.uni-icon {
+    font-family: uniicons;
+    font-weight: normal;
+}
+
+.uni-container {
+    padding: 15px;
+    background-color: #f8f8f8;
+}
+
+.uni-header-logo {
+	/* #ifdef H5 */
+	display: flex;
+	/* #endif */
+    padding: 15px 15px;
+    flex-direction: column;
+    justify-content: center;
+    align-items: center;
+    margin-top: 10rpx;
+}
+
+.uni-header-image {
+    width: 80px;
+    height: 80px;
+}
+
+.uni-hello-text {
+    margin-bottom: 20px;
+}
+
+.hello-text {
+    color: #7A7E83;
+    font-size: 14px;
+    line-height: 20px;
+}
+
+.hello-link {
+    color: #7A7E83;
+    font-size: 14px;
+    line-height: 20px;
+}
+
+.uni-panel {
+    margin-bottom: 12px;
+}
+
+.uni-panel-h {
+	/* #ifdef H5 */
+	display: flex;
+	/* #endif */
+    background-color: #ffffff;
+    flex-direction: row !important;
+	/* justify-content: space-between !important; */
+    align-items: center !important;
+    padding: 12px;
+	/* #ifdef H5 */
+	cursor: pointer;
+	/* #endif */
+}
+/*
+.uni-panel-h:active {
+    background-color: #f8f8f8;
+}
+ */
+.uni-panel-h-on {
+    background-color: #f0f0f0;
+}
+
+.uni-panel-text {
+    flex: 1;
+    color: #000000;
+    font-size: 14px;
+    font-weight: normal;
+}
+
+.uni-panel-icon {
+    margin-left: 15px;
+    color: #999999;
+    font-size: 14px;
+    font-weight: normal;
+    transform: rotate(0deg);
+    transition-duration: 0s;
+    transition-property: transform;
+}
+
+.uni-panel-icon-on {
+    transform: rotate(180deg);
+}
+
+.uni-navigate-item {
+	/* #ifdef H5 */
+	display: flex;
+	/* #endif */
+    flex-direction: row;
+    align-items: center;
+    background-color: #FFFFFF;
+    border-top-style: solid;
+    border-top-color: #f0f0f0;
+    border-top-width: 1px;
+    padding: 12px;
+	/* #ifdef H5 */
+	cursor: pointer;
+	/* #endif */
+}
+
+.uni-navigate-item:active {
+    background-color: #f8f8f8;
+}
+
+.uni-navigate-text {
+    flex: 1;
+    color: #000000;
+    font-size: 14px;
+    font-weight: normal;
+}
+
+.uni-navigate-icon {
+    margin-left: 15px;
+    color: #999999;
+    font-size: 14px;
+    font-weight: normal;
+}

File diff suppressed because it is too large
+ 1458 - 0
common/uni.css


+ 73 - 0
common/util.js

@@ -0,0 +1,73 @@
+function formatTime(time) {
+	if (typeof time !== 'number' || time < 0) {
+		return time
+	}
+
+	var hour = parseInt(time / 3600)
+	time = time % 3600
+	var minute = parseInt(time / 60)
+	time = time % 60
+	var second = time
+
+	return ([hour, minute, second]).map(function(n) {
+		n = n.toString()
+		return n[1] ? n : '0' + n
+	}).join(':')
+}
+
+function formatLocation(longitude, latitude) {
+	if (typeof longitude === 'string' && typeof latitude === 'string') {
+		longitude = parseFloat(longitude)
+		latitude = parseFloat(latitude)
+	}
+
+	longitude = longitude.toFixed(2)
+	latitude = latitude.toFixed(2)
+
+	return {
+		longitude: longitude.toString().split('.'),
+		latitude: latitude.toString().split('.')
+	}
+}
+var dateUtils = {
+	UNITS: {
+		'年': 31557600000,
+		'月': 2629800000,
+		'天': 86400000,
+		'小时': 3600000,
+		'分钟': 60000,
+		'秒': 1000
+	},
+	humanize: function(milliseconds) {
+		var humanize = '';
+		for (var key in this.UNITS) {
+			if (milliseconds >= this.UNITS[key]) {
+				humanize = Math.floor(milliseconds / this.UNITS[key]) + key + '前';
+				break;
+			}
+		}
+		return humanize || '刚刚';
+	},
+	format: function(dateStr) {
+		var date = this.parse(dateStr)
+		var diff = Date.now() - date.getTime();
+		if (diff < this.UNITS['天']) {
+			return this.humanize(diff);
+		}
+		var _format = function(number) {
+			return (number < 10 ? ('0' + number) : number);
+		};
+		return date.getFullYear() + '/' + _format(date.getMonth() + 1) + '/' + _format(date.getDate()) + '-' +
+			_format(date.getHours()) + ':' + _format(date.getMinutes());
+	},
+	parse: function(str) { //将"yyyy-mm-dd HH:MM:ss"格式的字符串,转化为一个Date对象
+		var a = str.split(/[^0-9]/);
+		return new Date(a[0], a[1] - 1, a[2], a[3], a[4], a[5]);
+	}
+};
+
+export {
+	formatTime,
+	formatLocation,
+	dateUtils
+}

+ 181 - 0
components/amap-wx/js/util.js

@@ -0,0 +1,181 @@
+import amap from '@/components/amap-wx/lib/amap-wx.js';
+// 地铁颜色图
+const line = {
+	'1号线': '#C43B33',
+	'2号线': '#016299',
+	'4号线/大兴线': '#008E9C',
+	'5号线': '#A42380',
+	'6号线': '#D09900',
+	'7号线': '#F2C172',
+	'8号线': '#009D6A',
+	'9号线': '#8FC41E',
+	'10号线': '#009DBE',
+	'13号线': '#F9E701',
+	'14号线东段': '#D4A7A2',
+	'14号线西段': '#D4A7A2',
+	'15号线': '#5D2D69',
+	'八通线': '#C33A32',
+	'昌平线': '#DE82B1',
+	'亦庄线': '#E40177',
+	'房山线': '#E66021',
+	'机场线': '#A29BBC',
+}
+
+// 150500:地铁站 ,150700:公交站 , 190700:地名地址
+const typecode = [{
+	id: '150500',
+	icon: 'icon-ditie'
+}, {
+	id: '150700',
+	icon: 'icon-gongjiao'
+}, {
+	id: '190700',
+	icon: 'icon-gonglu'
+}];
+
+const util = {
+	key:'b526b09b86cd2996e7732be8ab8c4430',
+	/**
+	 * 初始化高德地图api
+	 */
+	mapInit() {
+		return new amap.AMapWX({
+			key: this.key
+		});
+	},
+	// 服务状态吗
+	typecode,
+	/**
+	 * 获取地图颜色
+	 */
+	lineColor(name) {
+		if (line[name]) {
+			return line[name];
+		} else {
+			return '#ccc';
+		}
+	},
+	/**
+	 * 关键字颜色变化
+	 */
+	serachNmme(val, name) {
+		let namestr = new RegExp(val);
+		let nameresult =
+			`<div style="font-size: 14px;color: #333;line-height: 1.5;">
+		    ${name.replace(namestr, "<span style='color:#66ccff;'>" + val + '</span>')}
+		    </div>`
+			.trim();
+
+		return nameresult;
+	},
+	/**
+	 *  地址转地铁线路
+	 */
+	addressToLine(address, type) {
+		let addr = address.split(';');
+		let dt = '';
+		addr.forEach(elm => {
+			let color = '#cccccc';
+			if (type === typecode[0].id) {
+				color = this.lineColor(elm)
+			} else if (type === typecode[1].id) {
+				color = '#4075cb'
+			}
+			let style = 'margin:5px 0;margin-right:5px;padding:0 5px;background:' + color +
+				';font-size:12px;color:#fff;border-radius:3px;';
+			dt += `<div style=\'${style}\'>${elm}</div>`;
+
+		});
+		return `<div style="display:flex;flex-wrap: wrap;">${dt}</div>`;
+	},
+	/**
+	 * 数据处理
+	 */
+	dataHandle(item, val) {
+		// 改变字体颜色
+		if (val) {
+			item.nameNodes = util.serachNmme(val, item.name);
+		} else {
+			item.nameNodes = `<div style="font-size: 14px;color: #333;line-height: 1.5;">${item.name}</div>`;
+
+		}
+		// 地址解析 地铁
+		if (
+			item.typecode === util.typecode[0].id ||
+			item.typecode === util.typecode[1].id
+		) {
+			item.addressNodes = util.addressToLine(item.address, item.typecode);
+			if (item.typecode === util.typecode[0].id) {
+				item.icon = util.typecode[0].icon;
+			} else if (item.typecode === util.typecode[1].id) {
+				item.icon = util.typecode[1].icon;
+			}
+		} else {
+			item.addressNodes = `<span>${item.district}${
+				item.address.length > 0 ? '·' + item.address : ''
+			}</span>`.trim();
+			item.icon = 'icon-weizhi';
+		}
+
+		if (item.location && item.location.length === 0) {
+			item.icon = 'icon-sousuo';
+		}
+
+		return item;
+	},
+	/**
+	 * 存储历史数据
+	 * val [string | object]需要存储的内容
+	 */
+	setHistory(val) {
+		let searchHistory = uni.getStorageSync('search:history');
+		if (!searchHistory) searchHistory = [];
+		let serachData = {};
+		if (typeof(val) === 'string') {
+			serachData = {
+				adcode: [],
+				address: [],
+				city: [],
+				district: [],
+				id: [],
+				location: [],
+				name: val,
+				typecode: []
+			};
+		} else {
+			serachData = val
+		}
+
+		// 判断数组是否存在,如果存在,那么将放到最前面
+		for (var i = 0; i < searchHistory.length; i++) {
+			if (searchHistory[i].name === serachData.name) {
+				searchHistory.splice(i, 1);
+				break;
+			}
+		}
+
+		searchHistory.unshift(util.dataHandle(serachData));
+		uni.setStorage({
+			key: 'search:history',
+			data: searchHistory,
+			success: function() {
+				// console.log('success');
+			}
+		});
+	},
+	getHistory() {
+
+	},
+	removeHistory() {
+		uni.removeStorage({
+			key: 'search:history',
+			success: function(res) {
+				console.log('success');
+			}
+		});
+		return []
+	}
+
+}
+
+export default util;

File diff suppressed because it is too large
+ 1 - 0
components/amap-wx/lib/amap-wx.js


+ 156 - 0
components/api-set-tabbar.nvue

@@ -0,0 +1,156 @@
+<template>
+	<view class="uni-padding-wrap">
+		<page-head :title="title"></page-head>
+		<button class="button" @click="setTabBarBadge">{{ !hasSetTabBarBadge ? '设置tab徽标' : '移除tab徽标' }}</button>
+		<button class="button" @click="showTabBarRedDot">{{ !hasShownTabBarRedDot ?  '显示红点' : '移除红点'}}</button>
+		<button class="button" @click="customStyle">{{ !hasCustomedStyle ? '自定义Tab样式' : '移除自定义样式'}}</button>
+		<button class="button" @click="customItem">{{ !hasCustomedItem ? '自定义Tab信息' : '移除自定义信息' }}</button>
+		<button class="button" @click="hideTabBar">{{ !hasHiddenTabBar ? '隐藏TabBar' : '显示TabBar' }}</button>
+		<view class="btn-area">
+			<button class="button" type="primary" @click="navigateBack">返回上一级</button>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				title: 'tababr',
+				hasSetTabBarBadge: false,
+				hasShownTabBarRedDot: false,
+				hasCustomedStyle: false,
+				hasCustomedItem: false,
+				hasHiddenTabBar: false
+			}
+		},
+		destroyed() {
+			if (this.hasSetTabBarBadge) {
+				uni.removeTabBarBadge({
+					index: 1
+				})
+			}
+			if (this.hasShownTabBarRedDot) {
+				uni.hideTabBarRedDot({
+					index: 1
+				})
+			}
+			if (this.hasHiddenTabBar) {
+				uni.showTabBar()
+			}
+			if (this.hasCustomedStyle) {
+				uni.setTabBarStyle({
+					color: '#7A7E83',
+					selectedColor: '#007AFF',
+					backgroundColor: '#F8F8F8',
+					borderStyle: 'black'
+				})
+			}
+
+			if (this.hasCustomedItem) {
+				let tabBarOptions = {
+					index: 1,
+					text: '接口',
+					iconPath: '/static/api.png',
+					selectedIconPath: '/static/apiHL.png'
+				}
+				uni.setTabBarItem(tabBarOptions)
+			}
+		},
+		methods: {
+			navigateBack() {
+				this.$emit('unmount')
+			},
+			setTabBarBadge() {
+				if(this.hasShownTabBarRedDot){
+					uni.hideTabBarRedDot({
+						index: 1
+					})
+					this.hasShownTabBarRedDot = !this.hasShownTabBarRedDot
+				}
+				if (!this.hasSetTabBarBadge) {
+					uni.setTabBarBadge({
+						index: 1,
+						text: '1'
+					})
+				} else {
+					uni.removeTabBarBadge({
+						index: 1
+					})
+				}
+				this.hasSetTabBarBadge = !this.hasSetTabBarBadge
+			},
+			showTabBarRedDot() {
+				if(this.hasSetTabBarBadge) {
+					uni.removeTabBarBadge({
+						index: 1
+					})
+					this.hasSetTabBarBadge = !this.hasSetTabBarBadge
+				}
+				if (!this.hasShownTabBarRedDot) {
+					uni.showTabBarRedDot({
+						index: 1
+					})
+				} else {
+					uni.hideTabBarRedDot({
+						index: 1
+					})
+				}
+				this.hasShownTabBarRedDot = !this.hasShownTabBarRedDot
+			},
+			hideTabBar() {
+				if (!this.hasHiddenTabBar) {
+					uni.hideTabBar()
+				} else {
+					uni.showTabBar()
+				}
+				this.hasHiddenTabBar = !this.hasHiddenTabBar
+			},
+			customStyle() {
+				if (this.hasCustomedStyle) {
+					uni.setTabBarStyle({
+						color: '#7A7E83',
+						selectedColor: '#007AFF',
+						backgroundColor: '#F8F8F8',
+						borderStyle: 'black'
+					})
+				} else {
+					uni.setTabBarStyle({
+						color: '#FFF',
+						selectedColor: '#007AFF',
+						backgroundColor: '#000000',
+						borderStyle: 'black'
+					})
+				}
+				this.hasCustomedStyle = !this.hasCustomedStyle
+			},
+			customItem() {
+				let tabBarOptions = {
+					index: 1,
+					text: '接口',
+					iconPath: '/static/api.png',
+					selectedIconPath: '/static/apiHL.png'
+				}
+				if (this.hasCustomedItem) {
+					uni.setTabBarItem(tabBarOptions)
+				} else {
+					tabBarOptions.text = 'API'
+					uni.setTabBarItem(tabBarOptions)
+				}
+				this.hasCustomedItem = !this.hasCustomedItem
+			}
+		}
+	}
+</script>
+
+<style>
+	.button {
+		margin-top: 30rpx;
+        margin-left: 0;
+        margin-right: 0;
+	}
+
+	.btn-area {
+		padding-top: 30rpx;
+	}
+</style>

+ 1 - 0
components/marked/index.js

@@ -0,0 +1 @@
+export default './lib/marked'

File diff suppressed because it is too large
+ 1573 - 0
components/marked/lib/marked.js


File diff suppressed because it is too large
+ 12542 - 0
components/mpvue-citypicker/city-data/area.js


File diff suppressed because it is too large
+ 1503 - 0
components/mpvue-citypicker/city-data/city.js


+ 139 - 0
components/mpvue-citypicker/city-data/province.js

@@ -0,0 +1,139 @@
+/* eslint-disable */
+var provinceData = [{
+    "label": "北京市",
+    "value": "11"
+  },
+  {
+    "label": "天津市",
+    "value": "12"
+  },
+  {
+    "label": "河北省",
+    "value": "13"
+  },
+  {
+    "label": "山西省",
+    "value": "14"
+  },
+  {
+    "label": "内蒙古自治区",
+    "value": "15"
+  },
+  {
+    "label": "辽宁省",
+    "value": "21"
+  },
+  {
+    "label": "吉林省",
+    "value": "22"
+  },
+  {
+    "label": "黑龙江省",
+    "value": "23"
+  },
+  {
+    "label": "上海市",
+    "value": "31"
+  },
+  {
+    "label": "江苏省",
+    "value": "32"
+  },
+  {
+    "label": "浙江省",
+    "value": "33"
+  },
+  {
+    "label": "安徽省",
+    "value": "34"
+  },
+  {
+    "label": "福建省",
+    "value": "35"
+  },
+  {
+    "label": "江西省",
+    "value": "36"
+  },
+  {
+    "label": "山东省",
+    "value": "37"
+  },
+  {
+    "label": "河南省",
+    "value": "41"
+  },
+  {
+    "label": "湖北省",
+    "value": "42"
+  },
+  {
+    "label": "湖南省",
+    "value": "43"
+  },
+  {
+    "label": "广东省",
+    "value": "44"
+  },
+  {
+    "label": "广西壮族自治区",
+    "value": "45"
+  },
+  {
+    "label": "海南省",
+    "value": "46"
+  },
+  {
+    "label": "重庆市",
+    "value": "50"
+  },
+  {
+    "label": "四川省",
+    "value": "51"
+  },
+  {
+    "label": "贵州省",
+    "value": "52"
+  },
+  {
+    "label": "云南省",
+    "value": "53"
+  },
+  {
+    "label": "西藏自治区",
+    "value": "54"
+  },
+  {
+    "label": "陕西省",
+    "value": "61"
+  },
+  {
+    "label": "甘肃省",
+    "value": "62"
+  },
+  {
+    "label": "青海省",
+    "value": "63"
+  },
+  {
+    "label": "宁夏回族自治区",
+    "value": "64"
+  },
+  {
+    "label": "新疆维吾尔自治区",
+    "value": "65"
+  },
+  {
+    "label": "台湾",
+    "value": "66"
+  },
+  {
+    "label": "香港",
+    "value": "67"
+  },
+  {
+    "label": "澳门",
+    "value": "68"
+  }
+]
+export default provinceData;

+ 230 - 0
components/mpvue-citypicker/mpvueCityPicker.vue

@@ -0,0 +1,230 @@
+<template>
+	<div class="mpvue-picker">
+		<div :class="{'pickerMask':showPicker}" @click="maskClick" catchtouchmove="true"></div>
+		<div class="mpvue-picker-content " :class="{'mpvue-picker-view-show':showPicker}">
+			<div class="mpvue-picker__hd" catchtouchmove="true">
+				<div class="mpvue-picker__action" @click="pickerCancel">取消</div>
+				<div class="mpvue-picker__action" :style="{color:themeColor}" @click="pickerConfirm">确定</div>
+			</div>
+			<picker-view indicator-style="height: 40px;" class="mpvue-picker-view" :value="pickerValue" @change="pickerChange">
+				<block>
+					<picker-view-column>
+						<div class="picker-item" v-for="(item,index) in provinceDataList" :key="index">{{item.label}}</div>
+					</picker-view-column>
+					<picker-view-column>
+						<div class="picker-item" v-for="(item,index) in cityDataList" :key="index">{{item.label}}</div>
+					</picker-view-column>
+					<picker-view-column>
+						<div class="picker-item" v-for="(item,index) in areaDataList" :key="index">{{item.label}}</div>
+					</picker-view-column>
+				</block>
+			</picker-view>
+		</div>
+	</div>
+</template>
+
+<script>
+	import provinceData from './city-data/province.js';
+	import cityData from './city-data/city.js';
+	import areaData from './city-data/area.js';
+	export default {
+		data() {
+			return {
+				pickerValue: [0, 0, 0],
+				provinceDataList: provinceData,
+				cityDataList: cityData[0],
+				areaDataList: areaData[0][0],
+				/* 是否显示控件 */
+				showPicker: false,
+			};
+		},
+		created() {
+			this.init()
+		},
+		props: {
+			/* 默认值 */
+			pickerValueDefault: {
+				type: Array,
+				default () {
+					return [0, 0, 0]
+				}
+			},
+			/* 主题色 */
+			themeColor: String
+		},
+		watch: {
+			pickerValueDefault() {
+				this.init();
+			}
+		},
+		methods: {
+			init() {
+				this.handPickValueDefault(); // 对 pickerValueDefault 做兼容处理
+
+				const pickerValueDefault = this.pickerValueDefault
+
+				this.cityDataList = cityData[pickerValueDefault[0]];
+				this.areaDataList = areaData[pickerValueDefault[0]][pickerValueDefault[1]];
+				this.pickerValue = pickerValueDefault;
+			},
+			show() {
+				setTimeout(() => {
+					this.showPicker = true;
+				}, 0);
+			},
+			maskClick() {
+				this.pickerCancel();
+			},
+			pickerCancel() {
+				this.showPicker = false;
+				this._$emit('onCancel');
+			},
+			pickerConfirm(e) {
+				this.showPicker = false;
+				this._$emit('onConfirm');
+			},
+			showPickerView() {
+				this.showPicker = true;
+			},
+			handPickValueDefault() {
+				const pickerValueDefault = this.pickerValueDefault
+
+				let provinceIndex = pickerValueDefault[0]
+				let cityIndex = pickerValueDefault[1]
+				const areaIndex = pickerValueDefault[2]
+				if (
+					provinceIndex !== 0 ||
+					cityIndex !== 0 ||
+					areaIndex !== 0
+				) {
+					if (provinceIndex > provinceData.length - 1) {
+						this.pickerValueDefault[0] = provinceIndex = provinceData.length - 1;
+					}
+					if (cityIndex > cityData[provinceIndex].length - 1) {
+						this.pickerValueDefault[1] = cityIndex = cityData[provinceIndex].length - 1;
+					}
+					if (areaIndex > areaData[provinceIndex][cityIndex].length - 1) {
+						this.pickerValueDefault[2] = areaData[provinceIndex][cityIndex].length - 1;
+					}
+				}
+			},
+			pickerChange(e) {
+				let changePickerValue = e.mp.detail.value;
+				if (this.pickerValue[0] !== changePickerValue[0]) {
+					// 第一级发生滚动
+					this.cityDataList = cityData[changePickerValue[0]];
+					this.areaDataList = areaData[changePickerValue[0]][0];
+					changePickerValue[1] = 0;
+					changePickerValue[2] = 0;
+				} else if (this.pickerValue[1] !== changePickerValue[1]) {
+					// 第二级滚动
+					this.areaDataList =
+						areaData[changePickerValue[0]][changePickerValue[1]];
+					changePickerValue[2] = 0;
+				}
+				this.pickerValue = changePickerValue;
+				this._$emit('onChange');
+			},
+			_$emit(emitName) {
+				let pickObj = {
+					label: this._getLabel(),
+					value: this.pickerValue,
+					cityCode: this._getCityCode()
+				};
+				this.$emit(emitName, pickObj);
+			},
+			_getLabel() {
+				let pcikerLabel =
+					this.provinceDataList[this.pickerValue[0]].label +
+					'-' +
+					this.cityDataList[this.pickerValue[1]].label +
+					'-' +
+					this.areaDataList[this.pickerValue[2]].label;
+				return pcikerLabel;
+			},
+			_getCityCode() {
+				return this.areaDataList[this.pickerValue[2]].value;
+			}
+		}
+	};
+</script>
+
+<style>
+	.pickerMask {
+		position: fixed;
+		z-index: 1000;
+		top: 0;
+		right: 0;
+		left: 0;
+		bottom: 0;
+		background: rgba(0, 0, 0, 0.6);
+	}
+
+	.mpvue-picker-content {
+		position: fixed;
+		bottom: 0;
+		left: 0;
+		width: 100%;
+		transition: all 0.3s ease;
+		transform: translateY(100%);
+		z-index: 3000;
+	}
+
+	.mpvue-picker-view-show {
+		transform: translateY(0);
+	}
+
+	.mpvue-picker__hd {
+		display: flex;
+		padding: 9px 15px;
+		background-color: #fff;
+		position: relative;
+		text-align: center;
+		font-size: 17px;
+	}
+
+	.mpvue-picker__hd:after {
+		content: ' ';
+		position: absolute;
+		left: 0;
+		bottom: 0;
+		right: 0;
+		height: 1px;
+		border-bottom: 1px solid #e5e5e5;
+		color: #e5e5e5;
+		transform-origin: 0 100%;
+		transform: scaleY(0.5);
+	}
+
+	.mpvue-picker__action {
+		display: block;
+		flex: 1;
+		color: #1aad19;
+	}
+
+	.mpvue-picker__action:first-child {
+		text-align: left;
+		color: #888;
+	}
+
+	.mpvue-picker__action:last-child {
+		text-align: right;
+	}
+
+	.picker-item {
+		text-align: center;
+		line-height: 40px;
+		text-overflow: ellipsis;
+		white-space: nowrap;
+		font-size: 16px;
+	}
+
+	.mpvue-picker-view {
+		position: relative;
+		bottom: 0;
+		left: 0;
+		width: 100%;
+		height: 238px;
+		background-color: rgba(255, 255, 255, 1);
+	}
+</style>

+ 123 - 0
components/mpvue-echarts/src/echarts.vue

@@ -0,0 +1,123 @@
+<template>
+	<canvas v-if="canvasId" class="ec-canvas" :id="canvasId" :canvasId="canvasId" @touchstart="touchStart" @touchmove="touchMove" @touchend="touchEnd" @error="error"></canvas>
+</template>
+
+<script>
+import WxCanvas from './wx-canvas';
+
+export default {
+	props: {
+		canvasId: {
+			type: String,
+			default: 'ec-canvas'
+		},
+		lazyLoad: {
+			type: Boolean,
+			default: false
+		},
+		disableTouch: {
+			type: Boolean,
+			default: false
+		},
+		throttleTouch: {
+			type: Boolean,
+			default: false
+		}
+	},
+	// #ifdef H5
+	mounted() {
+		if (!this.lazyLoad) this.init();
+	},
+	// #endif
+	// #ifndef H5
+	onReady() {
+		if (!this.lazyLoad) this.init();
+	},
+	// #endif
+	methods: {
+		setChart(chart){
+			this.chart = chart
+		},
+		init() {
+			const { canvasId } = this;
+			this.ctx = wx.createCanvasContext(canvasId, this);
+
+			this.canvas = new WxCanvas(this.ctx, canvasId);
+
+			const query = wx.createSelectorQuery().in(this);
+			query
+				.select(`#${canvasId}`)
+				.boundingClientRect(res => {
+					if (!res) {
+						setTimeout(() => this.init(), 50);
+						return;
+					}
+					this.$emit('onInit', {
+						width: res.width,
+						height: res.height
+					});
+				})
+				.exec();
+		},
+		canvasToTempFilePath(opt) {
+			const { canvasId } = this;
+			this.ctx.draw(true, () => {
+				wx.canvasToTempFilePath({
+					canvasId,
+					...opt
+				});
+			});
+		},
+		touchStart(e) {
+			const { disableTouch, chart } = this;
+			if (disableTouch || !chart || !e.mp.touches.length) return;
+			const touch = e.mp.touches[0];
+			chart._zr.handler.dispatch('mousedown', {
+				zrX: touch.x,
+				zrY: touch.y
+			});
+			chart._zr.handler.dispatch('mousemove', {
+				zrX: touch.x,
+				zrY: touch.y
+			});
+		},
+		touchMove(e) {
+			const { disableTouch, throttleTouch, chart, lastMoveTime } = this;
+			if (disableTouch || !chart || !e.mp.touches.length) return;
+
+			if (throttleTouch) {
+				const currMoveTime = Date.now();
+				if (currMoveTime - lastMoveTime < 240) return;
+				this.lastMoveTime = currMoveTime;
+			}
+
+			const touch = e.mp.touches[0];
+			chart._zr.handler.dispatch('mousemove', {
+				zrX: touch.x,
+				zrY: touch.y
+			});
+		},
+		touchEnd(e) {
+			const { disableTouch, chart } = this;
+			if (disableTouch || !chart) return;
+			const touch = e.mp.changedTouches ? e.mp.changedTouches[0] : {};
+			chart._zr.handler.dispatch('mouseup', {
+				zrX: touch.x,
+				zrY: touch.y
+			});
+			chart._zr.handler.dispatch('click', {
+				zrX: touch.x,
+				zrY: touch.y
+			});
+		}
+	}
+};
+</script>
+
+<style scoped>
+.ec-canvas {
+	width: 100%;
+	height: 100%;
+	flex: 1;
+}
+</style>

+ 73 - 0
components/mpvue-echarts/src/wx-canvas.js

@@ -0,0 +1,73 @@
+export default class WxCanvas {
+  constructor(ctx, canvasId) {
+    this.ctx = ctx;
+    this.canvasId = canvasId;
+    this.chart = null;
+
+    WxCanvas.initStyle(ctx);
+    this.initEvent();
+  }
+
+  getContext(contextType) {
+    return contextType === '2d' ? this.ctx : null;
+  }
+
+  setChart(chart) {
+    this.chart = chart;
+  }
+
+  attachEvent() {
+    // noop
+  }
+
+  detachEvent() {
+    // noop
+  }
+
+  static initStyle(ctx) {
+    const styles = ['fillStyle', 'strokeStyle', 'globalAlpha',
+      'textAlign', 'textBaseAlign', 'shadow', 'lineWidth',
+      'lineCap', 'lineJoin', 'lineDash', 'miterLimit', 'fontSize'];
+
+    styles.forEach((style) => {
+      Object.defineProperty(ctx, style, {
+        set: (value) => {
+          if ((style !== 'fillStyle' && style !== 'strokeStyle')
+            || (value !== 'none' && value !== null)
+          ) {
+            ctx[`set${style.charAt(0).toUpperCase()}${style.slice(1)}`](value);
+          }
+        },
+      });
+    });
+
+    ctx.createRadialGradient = () => ctx.createCircularGradient(arguments);
+  }
+
+  initEvent() {
+    this.event = {};
+    const eventNames = [{
+      wxName: 'touchStart',
+      ecName: 'mousedown',
+    }, {
+      wxName: 'touchMove',
+      ecName: 'mousemove',
+    }, {
+      wxName: 'touchEnd',
+      ecName: 'mouseup',
+    }, {
+      wxName: 'touchEnd',
+      ecName: 'click',
+    }];
+
+    eventNames.forEach((name) => {
+      this.event[name.wxName] = (e) => {
+        const touch = e.mp.touches[0];
+        this.chart._zr.handler.dispatch(name.ecName, {
+          zrX: name.wxName === 'tap' ? touch.clientX : touch.x,
+          zrY: name.wxName === 'tap' ? touch.clientY : touch.y,
+        });
+      };
+    });
+  }
+}

+ 483 - 0
components/mpvue-picker/mpvuePicker.vue

@@ -0,0 +1,483 @@
+<template>
+	<view class="mpvue-picker">
+		<view :class="{'pickerMask':showPicker}" @click="maskClick" catchtouchmove="true"></view>
+		<view class="mpvue-picker-content " :class="{'mpvue-picker-view-show':showPicker}">
+			<view class="mpvue-picker__hd" catchtouchmove="true">
+				<view class="mpvue-picker__action" @click="pickerCancel">取消</view>
+				<view class="mpvue-picker__action" :style="{color:themeColor}" @click="pickerConfirm">确定</view>
+			</view>
+			<!-- 单列 -->
+			<picker-view indicator-style="height: 40px;" class="mpvue-picker-view" :value="pickerValue"
+				@change="pickerChange" v-if="mode==='selector' && pickerValueSingleArray.length > 0">
+				<picker-view-column>
+					<view class="picker-item" v-for="(item,index) in pickerValueSingleArray" :key="index">{{item.label}}
+					</view>
+				</picker-view-column>
+			</picker-view>
+			<!-- 时间选择器 -->
+			<picker-view indicator-style="height: 40px;" class="mpvue-picker-view" :value="pickerValue"
+				@change="pickerChange" v-if="mode==='timeSelector'">
+				<picker-view-column>
+					<view class="picker-item" v-for="(item,index) in pickerValueHour" :key="index">{{item.label}}</view>
+				</picker-view-column>
+				<picker-view-column>
+					<view class="picker-item" v-for="(item,index) in pickerValueMinute" :key="index">{{item.label}}
+					</view>
+				</picker-view-column>
+			</picker-view>
+			<!-- 多列选择 -->
+			<picker-view indicator-style="height: 40px;" class="mpvue-picker-view" :value="pickerValue"
+				@change="pickerChange" v-if="mode==='multiSelector'">
+				<!-- #ifdef VUE3 -->
+				<template v-for="(n,index) in pickerValueMulArray.length" :key="index">
+					<picker-view-column>
+						<view class="picker-item" v-for="(item,index1) in pickerValueMulArray[n]" :key="index1">
+							{{item.label}}
+						</view>
+					</picker-view-column>
+				</template>
+				<!-- #endif -->
+				<!-- #ifndef VUE3 -->
+				<block v-for="(n,index) in pickerValueMulArray.length" :key="index">
+					<picker-view-column>
+						<view class="picker-item" v-for="(item,index1) in pickerValueMulArray[n]" :key="index1">
+							{{item.label}}
+						</view>
+					</picker-view-column>
+				</block>
+				<!-- #endif -->
+			</picker-view>
+			<!-- 二级联动 -->
+			<picker-view indicator-style="height: 40px;" class="mpvue-picker-view" :value="pickerValue"
+				@change="pickerChangeMul" v-if="mode==='multiLinkageSelector' && deepLength===2">
+				<picker-view-column>
+					<view class="picker-item" v-for="(item,index) in pickerValueMulTwoOne" :key="index">{{item.label}}
+					</view>
+				</picker-view-column>
+				<picker-view-column>
+					<view class="picker-item" v-for="(item,index) in pickerValueMulTwoTwo" :key="index">{{item.label}}
+					</view>
+				</picker-view-column>
+			</picker-view>
+			<!-- 三级联动 -->
+			<picker-view indicator-style="height: 40px;" class="mpvue-picker-view" :value="pickerValue"
+				@change="pickerChangeMul" v-if="mode==='multiLinkageSelector' && deepLength===3">
+				<picker-view-column>
+					<view class="picker-item" v-for="(item,index) in pickerValueMulThreeOne" :key="index">{{item.label}}
+					</view>
+				</picker-view-column>
+				<picker-view-column>
+					<view class="picker-item" v-for="(item,index) in pickerValueMulThreeTwo" :key="index">{{item.label}}
+					</view>
+				</picker-view-column>
+				<picker-view-column>
+					<view class="picker-item" v-for="(item,index) in pickerValueMulThreeThree" :key="index">
+						{{item.label}}
+					</view>
+				</picker-view-column>
+			</picker-view>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				pickerChangeValue: [],
+				pickerValue: [],
+				pickerValueArrayChange: true,
+				modeChange: false,
+				pickerValueSingleArray: [],
+				pickerValueHour: [],
+				pickerValueMinute: [],
+				pickerValueMulArray: [],
+				pickerValueMulTwoOne: [],
+				pickerValueMulTwoTwo: [],
+				pickerValueMulThreeOne: [],
+				pickerValueMulThreeTwo: [],
+				pickerValueMulThreeThree: [],
+				/* 是否显示控件 */
+				showPicker: false,
+			};
+		},
+		props: {
+			/* mode */
+			mode: {
+				type: String,
+				default: 'selector'
+			},
+			/* picker 数值 */
+			pickerValueArray: {
+				type: Array,
+				default () {
+					return []
+				}
+			},
+			/* 默认值 */
+			pickerValueDefault: {
+				type: Array,
+				default () {
+					return []
+				}
+			},
+			/* 几级联动 */
+			deepLength: {
+				type: Number,
+				default: 2
+			},
+			/* 主题色 */
+			themeColor: String
+		},
+		watch: {
+			pickerValueArray(oldVal, newVal) {
+				this.pickerValueArrayChange = true;
+			},
+			mode(oldVal, newVal) {
+				this.modeChange = true;
+			},
+			pickerValueArray(val) {
+				this.initPicker(val);
+			}
+		},
+		methods: {
+			initPicker(valueArray) {
+				let pickerValueArray = valueArray;
+				this.pickerValue = this.pickerValueDefault;
+				// 初始化多级联动
+				if (this.mode === 'selector') {
+					this.pickerValueSingleArray = valueArray;
+				} else if (this.mode === 'timeSelector') {
+					this.modeChange = false;
+					let hourArray = [];
+					let minuteArray = [];
+					for (let i = 0; i < 24; i++) {
+						hourArray.push({
+							value: i,
+							label: i > 9 ? `${i} 时` : `0${i} 时`
+						});
+					}
+					for (let i = 0; i < 60; i++) {
+						minuteArray.push({
+							value: i,
+							label: i > 9 ? `${i} 分` : `0${i} 分`
+						});
+					}
+					this.pickerValueHour = hourArray;
+					this.pickerValueMinute = minuteArray;
+				} else if (this.mode === 'multiSelector') {
+					this.pickerValueMulArray = valueArray;
+				} else if (this.mode === 'multiLinkageSelector' && this.deepLength === 2) {
+					// 两级联动
+					let pickerValueMulTwoOne = [];
+					let pickerValueMulTwoTwo = [];
+					// 第一列
+					for (let i = 0, length = pickerValueArray.length; i < length; i++) {
+						pickerValueMulTwoOne.push(pickerValueArray[i]);
+					}
+					// 渲染第二列
+					// 如果有设定的默认值
+					if (this.pickerValueDefault.length === 2) {
+						let num = this.pickerValueDefault[0];
+						for (
+							let i = 0, length = pickerValueArray[num].children.length; i < length; i++
+						) {
+							pickerValueMulTwoTwo.push(pickerValueArray[num].children[i]);
+						}
+					} else {
+						for (
+							let i = 0, length = pickerValueArray[0].children.length; i < length; i++
+						) {
+							pickerValueMulTwoTwo.push(pickerValueArray[0].children[i]);
+						}
+					}
+					this.pickerValueMulTwoOne = pickerValueMulTwoOne;
+					this.pickerValueMulTwoTwo = pickerValueMulTwoTwo;
+				} else if (
+					this.mode === 'multiLinkageSelector' &&
+					this.deepLength === 3
+				) {
+					let pickerValueMulThreeOne = [];
+					let pickerValueMulThreeTwo = [];
+					let pickerValueMulThreeThree = [];
+					// 第一列
+					for (let i = 0, length = pickerValueArray.length; i < length; i++) {
+						pickerValueMulThreeOne.push(pickerValueArray[i]);
+					}
+					// 渲染第二列
+					this.pickerValueDefault =
+						this.pickerValueDefault.length === 3 ?
+						this.pickerValueDefault : [0, 0, 0];
+					if (this.pickerValueDefault.length === 3) {
+						let num = this.pickerValueDefault[0];
+						for (
+							let i = 0, length = pickerValueArray[num].children.length; i < length; i++
+						) {
+							pickerValueMulThreeTwo.push(pickerValueArray[num].children[i]);
+						}
+						// 第三列
+						let numSecond = this.pickerValueDefault[1];
+						for (let i = 0, length = pickerValueArray[num].children[numSecond].children.length; i <
+							length; i++) {
+							pickerValueMulThreeThree.push(
+								pickerValueArray[num].children[numSecond].children[i]
+							);
+						}
+					}
+					this.pickerValueMulThreeOne = pickerValueMulThreeOne;
+					this.pickerValueMulThreeTwo = pickerValueMulThreeTwo;
+					this.pickerValueMulThreeThree = pickerValueMulThreeThree;
+				}
+			},
+			show() {
+				setTimeout(() => {
+					if (this.pickerValueArrayChange || this.modeChange) {
+						this.initPicker(this.pickerValueArray);
+						this.showPicker = true;
+						this.pickerValueArrayChange = false;
+						this.modeChange = false;
+					} else {
+						this.showPicker = true;
+					}
+				}, 0);
+			},
+			maskClick() {
+				this.pickerCancel();
+			},
+			pickerCancel() {
+				this.showPicker = false;
+				this._initPickerVale();
+				let pickObj = {
+					index: this.pickerValue,
+					value: this._getPickerLabelAndValue(this.pickerValue, this.mode).value,
+					label: this._getPickerLabelAndValue(this.pickerValue, this.mode).label
+				};
+				this.$emit('onCancel', pickObj);
+			},
+			pickerConfirm(e) {
+				this.showPicker = false;
+				this._initPickerVale();
+				let pickObj = {
+					index: this.pickerValue,
+					value: this._getPickerLabelAndValue(this.pickerValue, this.mode).value,
+					label: this._getPickerLabelAndValue(this.pickerValue, this.mode).label
+				};
+				this.$emit('onConfirm', pickObj);
+			},
+			showPickerView() {
+				this.showPicker = true;
+			},
+			pickerChange(e) {
+				this.pickerValue = e.mp.detail.value;
+				let pickObj = {
+					index: this.pickerValue,
+					value: this._getPickerLabelAndValue(this.pickerValue, this.mode).value,
+					label: this._getPickerLabelAndValue(this.pickerValue, this.mode).label
+				};
+				this.$emit('onChange', pickObj);
+			},
+			pickerChangeMul(e) {
+				if (this.deepLength === 2) {
+					let pickerValueArray = this.pickerValueArray;
+					let changeValue = e.mp.detail.value;
+					// 处理第一列滚动
+					if (changeValue[0] !== this.pickerValue[0]) {
+						let pickerValueMulTwoTwo = [];
+						// 第一列滚动第二列数据更新
+						for (let i = 0, length = pickerValueArray[changeValue[0]].children.length; i < length; i++) {
+							pickerValueMulTwoTwo.push(pickerValueArray[changeValue[0]].children[i]);
+						}
+						this.pickerValueMulTwoTwo = pickerValueMulTwoTwo;
+						// 第二列初始化为 0
+						changeValue[1] = 0;
+					}
+					this.pickerValue = changeValue;
+				} else if (this.deepLength === 3) {
+					let pickerValueArray = this.pickerValueArray;
+					let changeValue = e.mp.detail.value;
+					let pickerValueMulThreeTwo = [];
+					let pickerValueMulThreeThree = [];
+					// 重新渲染第二列
+					// 如果是第一列滚动
+					if (changeValue[0] !== this.pickerValue[0]) {
+						this.pickerValueMulThreeTwo = [];
+						for (let i = 0, length = pickerValueArray[changeValue[0]].children.length; i < length; i++) {
+							pickerValueMulThreeTwo.push(pickerValueArray[changeValue[0]].children[i]);
+						}
+						// 重新渲染第三列
+						for (let i = 0, length = pickerValueArray[changeValue[0]].children[0].children.length; i <
+							length; i++) {
+							pickerValueMulThreeThree.push(pickerValueArray[changeValue[0]].children[0].children[i]);
+						}
+						changeValue[1] = 0;
+						changeValue[2] = 0;
+						this.pickerValueMulThreeTwo = pickerValueMulThreeTwo;
+						this.pickerValueMulThreeThree = pickerValueMulThreeThree;
+					} else if (changeValue[1] !== this.pickerValue[1]) {
+						// 第二列滚动
+						// 重新渲染第三列
+						this.pickerValueMulThreeThree = [];
+						pickerValueMulThreeTwo = this.pickerValueMulThreeTwo;
+						for (let i = 0, length = pickerValueArray[changeValue[0]].children[changeValue[1]].children
+								.length; i <
+							length; i++) {
+							pickerValueMulThreeThree.push(pickerValueArray[changeValue[0]].children[changeValue[1]]
+								.children[
+									i]);
+						}
+						changeValue[2] = 0;
+						this.pickerValueMulThreeThree = pickerValueMulThreeThree;
+					}
+					this.pickerValue = changeValue;
+				}
+				let pickObj = {
+					index: this.pickerValue,
+					value: this._getPickerLabelAndValue(this.pickerValue, this.mode).value,
+					label: this._getPickerLabelAndValue(this.pickerValue, this.mode).label
+				};
+				this.$emit('onChange', pickObj);
+			},
+			// 获取 pxikerLabel
+			_getPickerLabelAndValue(value, mode) {
+				let pickerLable;
+				let pickerGetValue = [];
+				// selector
+				if (mode === 'selector') {
+					pickerLable = this.pickerValueSingleArray[value].label;
+					pickerGetValue.push(this.pickerValueSingleArray[value].value);
+				} else if (mode === 'timeSelector') {
+					pickerLable = `${this.pickerValueHour[value[0]].label}-${this.pickerValueMinute[value[1]].label}`;
+					pickerGetValue.push(this.pickerValueHour[value[0]].value);
+					pickerGetValue.push(this.pickerValueHour[value[1]].value);
+				} else if (mode === 'multiSelector') {
+					for (let i = 0; i < value.length; i++) {
+						if (i > 0) {
+							pickerLable += this.pickerValueMulArray[i][value[i]].label + (i === value.length - 1 ? '' :
+								'-');
+						} else {
+							pickerLable = this.pickerValueMulArray[i][value[i]].label + '-';
+						}
+						pickerGetValue.push(this.pickerValueMulArray[i][value[i]].value);
+					}
+				} else if (mode === 'multiLinkageSelector') {
+					/* eslint-disable indent */
+					pickerLable =
+						this.deepLength === 2 ?
+						`${this.pickerValueMulTwoOne[value[0]].label}-${this.pickerValueMulTwoTwo[value[1]].label}` :
+						`${this.pickerValueMulThreeOne[value[0]].label}-${this.pickerValueMulThreeTwo[value[1]].label}-${this.pickerValueMulThreeThree[value[2]].label}`;
+					if (this.deepLength === 2) {
+						pickerGetValue.push(this.pickerValueMulTwoOne[value[0]].value);
+						pickerGetValue.push(this.pickerValueMulTwoTwo[value[1]].value);
+					} else {
+						pickerGetValue.push(this.pickerValueMulThreeOne[value[0]].value);
+						pickerGetValue.push(this.pickerValueMulThreeTwo[value[1]].value);
+						pickerGetValue.push(this.pickerValueMulThreeThree[value[2]].value);
+					}
+					/* eslint-enable indent */
+				}
+				return {
+					label: pickerLable,
+					value: pickerGetValue
+				};
+			},
+			// 初始化 pickerValue 默认值
+			_initPickerVale() {
+				if (this.pickerValue.length === 0) {
+					if (this.mode === 'selector') {
+						this.pickerValue = [0];
+					} else if (this.mode === 'multiSelector') {
+						this.pickerValue = new Int8Array(this.pickerValueArray.length);
+					} else if (
+						this.mode === 'multiLinkageSelector' &&
+						this.deepLength === 2
+					) {
+						this.pickerValue = [0, 0];
+					} else if (
+						this.mode === 'multiLinkageSelector' &&
+						this.deepLength === 3
+					) {
+						this.pickerValue = [0, 0, 0];
+					}
+				}
+			}
+		}
+	};
+</script>
+
+<style>
+	.pickerMask {
+		position: fixed;
+		z-index: 1000;
+		top: 0;
+		right: 0;
+		left: 0;
+		bottom: 0;
+		background: rgba(0, 0, 0, 0.6);
+	}
+
+	.mpvue-picker-content {
+		position: fixed;
+		bottom: 0;
+		left: 0;
+		width: 100%;
+		transition: all 0.3s ease;
+		transform: translateY(100%);
+		z-index: 3000;
+	}
+
+	.mpvue-picker-view-show {
+		transform: translateY(0);
+	}
+
+	.mpvue-picker__hd {
+		display: flex;
+		padding: 9px 15px;
+		background-color: #fff;
+		position: relative;
+		text-align: center;
+		font-size: 17px;
+	}
+
+	.mpvue-picker__hd:after {
+		content: ' ';
+		position: absolute;
+		left: 0;
+		bottom: 0;
+		right: 0;
+		height: 1px;
+		border-bottom: 1px solid #e5e5e5;
+		color: #e5e5e5;
+		transform-origin: 0 100%;
+		transform: scaleY(0.5);
+	}
+
+	.mpvue-picker__action {
+		display: block;
+		flex: 1;
+		color: #1aad19;
+	}
+
+	.mpvue-picker__action:first-child {
+		text-align: left;
+		color: #888;
+	}
+
+	.mpvue-picker__action:last-child {
+		text-align: right;
+	}
+
+	.picker-item {
+		text-align: center;
+		line-height: 40px;
+		font-size: 16px;
+	}
+
+	.mpvue-picker-view {
+		position: relative;
+		bottom: 0;
+		left: 0;
+		width: 100%;
+		height: 238px;
+		background-color: rgba(255, 255, 255, 1);
+	}
+</style>

+ 175 - 0
components/mpvueGestureLock/gestureLock.js

@@ -0,0 +1,175 @@
+class GestureLock {
+
+    constructor(containerWidth, cycleRadius) {
+        this.containerWidth = containerWidth; // 容器宽度
+        this.cycleRadius = cycleRadius; // 圆的半径
+
+        this.circleArray = []; // 全部圆的对象数组
+        this.checkPoints = []; // 选中的圆的对象数组
+        this.lineArray = []; // 已激活锁之间的线段数组
+        this.lastCheckPoint = 0; // 最后一个激活的锁
+        this.offsetX = 0; // 容器的 X 偏移
+        this.offsetY = 0; // 容器的 Y 偏移
+        this.activeLine = {}; // 最后一个激活的锁与当前位置之间的线段
+
+        this.windowWidth = wx.getSystemInfoSync().windowWidth; // 窗口大小(用于rpx 和 px 转换)
+
+        this.initCircleArray();
+    }
+
+    // 初始化 画布上的 9个圆
+    initCircleArray() {
+        const cycleMargin = (this.containerWidth - 6 * this.cycleRadius) / 6;
+        let count = 0;
+        for (let i = 0; i < 3; i++) {
+            for (let j = 0; j < 3; j++) {
+                count++;
+                this.circleArray.push({
+                    count: count,
+                    x: this.rpxTopx((cycleMargin + this.cycleRadius) * (j * 2 + 1)),
+                    y: this.rpxTopx((cycleMargin + this.cycleRadius) * (i * 2 + 1)),
+                    radius: this.rpxTopx(this.cycleRadius),
+                    check: false,
+                    style: {
+                        left: (cycleMargin + this.cycleRadius) * (j * 2 + 1) - this.cycleRadius + 'rpx',
+                        top: (cycleMargin + this.cycleRadius) * (i * 2 + 1) - this.cycleRadius + 'rpx',
+                        width: this.cycleRadius * 2 + 'rpx',
+                    }
+                });
+            }
+        }
+    }
+
+    onTouchStart(e) {
+        this.setOffset(e);
+        this.checkTouch({
+            x: e.touches[0].pageX - this.offsetX,
+            y: e.touches[0].pageY - this.offsetY
+        });
+    }
+
+    onTouchMove(e) {
+        this.moveDraw(e)
+    }
+
+    onTouchEnd(e) {
+        const checkPoints = this.checkPoints;
+        this.reset();
+        return checkPoints;
+    }
+
+    // 初始化 偏移量
+    setOffset(e) {
+        this.offsetX = e.currentTarget.offsetLeft;
+        this.offsetY = e.currentTarget.offsetTop;
+    }
+
+    // 检测当时 触摸位置是否位于 锁上
+    checkTouch({
+        x,
+        y
+    }) {
+        for (let i = 0; i < this.circleArray.length; i++) {
+            let point = this.circleArray[i];
+            if (this.isPointInCycle(x, y, point.x, point.y, point.radius)) {
+                if (!point.check) {
+                    this.checkPoints.push(point.count);
+                    if (this.lastCheckPoint != 0) {
+                        // 已激活锁之间的线段
+                        const line = this.drawLine(this.lastCheckPoint, point);
+                        this.lineArray.push(line);
+                    }
+                    this.lastCheckPoint = point;
+                }
+                point.check = true;
+                return;
+            }
+        }
+    }
+
+    // 画线 - 返回 样式 对象
+    drawLine(start, end) {
+        const width = this.getPointDis(start.x, start.y, end.x, end.y);
+        const rotate = this.getAngle(start, end);
+
+        return {
+            activeLeft: start.x + 'px',
+            activeTop: start.y + 'px',
+            activeWidth: width + 'px',
+            activeRotate: rotate + 'deg'
+        }
+
+    }
+
+    // 获取 画线的 角度
+    getAngle(start, end) {
+        var diff_x = end.x - start.x,
+            diff_y = end.y - start.y;
+        if (diff_x >= 0) {
+            return 360 * Math.atan(diff_y / diff_x) / (2 * Math.PI);
+        } else {
+            return 180 + 360 * Math.atan(diff_y / diff_x) / (2 * Math.PI);
+        }
+    }
+
+    // 判断 当前点是否位于 锁内
+    isPointInCycle(x, y, circleX, circleY, radius) {
+        return (this.getPointDis(x, y, circleX, circleY) < radius) ? true : false;
+    }
+
+    // 获取两点之间距离
+    getPointDis(ax, ay, bx, by) {
+        return Math.sqrt(Math.pow(ax - bx, 2) + Math.pow(ay - by, 2));
+    }
+
+    // 移动 绘制
+    moveDraw(e) {
+        // 画经过的圆
+        const x = e.touches[0].pageX - this.offsetX;
+        const y = e.touches[0].pageY - this.offsetY;
+        this.checkTouch({
+            x,
+            y
+        });
+
+        // 画 最后一个激活的锁与当前位置之间的线段
+        this.activeLine = this.drawLine(this.lastCheckPoint, {
+            x,
+            y
+        });
+    }
+
+    // 使 画布 恢复初始状态
+    reset() {
+        this.circleArray.forEach((item) => {
+            item.check = false;
+        });
+        this.checkPoints = [];
+        this.lineArray = [];
+        this.activeLine = {};
+        this.lastCheckPoint = 0;
+    }
+
+
+    // 获取 最后一个激活的锁与当前位置之间的线段
+    getActiveLine() {
+        return this.activeLine;
+    }
+
+    // 获取 圆对象数组
+    getCycleArray() {
+        return this.circleArray;
+    }
+
+    // 获取 已激活锁之间的线段
+    getLineArray() {
+        return this.lineArray;
+    }
+
+    // 将 RPX 转换成 PX
+    rpxTopx(rpx) {
+        return rpx / 750 * this.windowWidth;
+    }
+}
+
+export default GestureLock;

+ 138 - 0
components/mpvueGestureLock/index.vue

@@ -0,0 +1,138 @@
+<template>
+	<view class="gesture-lock" :class="{error:error}" :style="{width: containerWidth +'rpx', height:containerWidth +'rpx'}"
+	 @touchstart.stop="onTouchStart" @touchmove.stop="onTouchMove" @touchend.stop="onTouchEnd">
+		<!-- 同级 v-for 的 key 重复会有问题,需要套一层。 -->
+		<!-- 9 个圆 -->
+		<view>
+			<view v-for="(item,i) in circleArray" :key="i" class="cycle" :class="{check:item.check}" :style="{left:item.style.left,top:item.style.top,width:item.style.width,height:item.style.width}">
+			</view>
+		</view>
+		<view>
+			<!-- 已激活锁之间的线段 -->
+			<view v-for="(item,i) in lineArray" :key="i" class="line" :style="{left:item.activeLeft,top:item.activeTop,width:item.activeWidth,'-webkit-transform':'rotate('+item.activeRotate+')',transform:'rotate('+item.activeRotate+')'}">
+			</view>
+		</view>
+		<!-- 最后一个激活的锁与当前位置之间的线段 -->
+		<view class="line" :style="{left:activeLine.activeLeft,top:activeLine.activeTop,width:activeLine.activeWidth,'-webkit-transform':'rotate('+activeLine.activeRotate+')',transform:'rotate('+activeLine.activeRotate+')'}">
+		</view>
+	</view>
+</template>
+<script>
+	import GestureLock from './gestureLock';
+
+	export default {
+		name: 'index',
+		props: {
+			/**
+			 * 容器宽度
+			 */
+			containerWidth: {
+				type: [Number, String],
+				default: 0
+			},
+			/**
+			 * 圆的半径
+			 */
+			cycleRadius: {
+				type: [Number, String],
+				default: 0
+			},
+			/**
+			 * 已设定的密码
+			 */
+			password: {
+				type: Array,
+				default () {
+					return []
+				}
+			},
+		},
+		data() {
+			return {
+				gestureLock: {}, // 锁对象
+				circleArray: [], // 圆对象数组
+				lineArray: [], // 已激活锁之间的线段
+				activeLine: {}, // 最后一个激活的锁与当前位置之间的线段
+				error: false
+			}
+		},
+		methods: {
+			onTouchStart(e) {
+				this.gestureLock.onTouchStart(e);
+				this.refesh();
+			},
+
+			onTouchMove(e) {
+				this.gestureLock.onTouchMove(e);
+				this.refesh();
+			},
+
+			onTouchEnd(e) {
+				const checkPoints = this.gestureLock.onTouchEnd(e);
+				if (!this.password.length || checkPoints.join('') == this.password.join('')) {
+					this.refesh();
+					this.$emit('end', checkPoints);
+				} else {
+					this.error = true;
+					setTimeout(() => {
+						this.refesh();
+						this.$emit('end', checkPoints);
+					}, 800);
+				}
+
+			},
+			refesh() {
+				this.error = false;
+				this.circleArray = this.gestureLock.getCycleArray();
+				this.lineArray = this.gestureLock.getLineArray();
+				this.activeLine = this.gestureLock.getActiveLine();
+			}
+		},
+		mounted() {
+			this.gestureLock = new GestureLock(this.containerWidth, this.cycleRadius);
+			this.refesh();
+		}
+	}
+</script>
+
+<style scoped>
+	.gesture-lock {
+		margin: 0 auto;
+		position: relative;
+		box-sizing: border-box;
+		overflow: auto;
+	}
+
+	.gesture-lock .cycle {
+		box-sizing: border-box;
+		position: absolute;
+		border: 2px solid #66aaff;
+		border-radius: 50%;
+	}
+
+	.gesture-lock .cycle.check:after {
+		content: "";
+		display: block;
+		position: absolute;
+		width: 32%;
+		height: 32%;
+		border: 2px solid #66aaff;
+		border-radius: 50%;
+		top: 50%;
+		left: 50%;
+		transform: translate(-50%, -50%);
+	}
+
+	.gesture-lock .line {
+		height: 0;
+		border-top: 2px solid #66aaff;
+		position: absolute;
+		transform-origin: left center;
+	}
+
+	.gesture-lock.error .cycle.check,
+	.gesture-lock.error .cycle.check:after,
+	.gesture-lock.error .line {
+		border-color: #ffa197;
+	}
+</style>

+ 38 - 0
components/page-foot/page-foot.vue

@@ -0,0 +1,38 @@
+<template name="page-foot">
+	<view class="page-share-title">
+		<text>感谢{{name}}提供本示例,</text>
+		<text class="submit" @click="submit">我也提交</text>
+	</view>
+</template>
+
+<script>
+	export default {
+		name: "page-foot",
+		props: {
+			name: {
+				type: String,
+				default: ""
+			}
+		},
+		methods: {
+			submit() {
+				uni.showModal({
+					content:"hello uni-app开源地址为https://github.com/dcloudio/uni-app/tree/master/examples,请在这个开源项目上贡献你的代码",
+					showCancel:false
+				})
+			}
+		}
+	}
+</script>
+<style>
+	.page-share-title {
+		text-align: center;
+		font-size: 30rpx;
+		color: #BEBEBE;
+		padding: 20rpx 0;
+	}
+
+	.submit {
+		border-bottom: 1rpx solid #BEBEBE;
+	}
+</style>

+ 16 - 0
components/page-head/page-head.vue

@@ -0,0 +1,16 @@
+<template name="page-head">
+	<view class="common-page-head">
+		<view class="common-page-head-title">{{title}}</view>
+	</view>
+</template>
+<script>
+	export default {
+		name: "page-head",
+		props: {
+			title: {
+				type: String,
+				default: ""
+			}
+		}
+	}
+</script>

+ 66 - 0
components/product.vue

@@ -0,0 +1,66 @@
+<template>
+	<view class="product">
+		<image class="product-image" :src="image ? image : 'https://via.placeholder.com/150x200'"></image>
+		<view class="product-title">{{title}}</view>
+		<view class="product-price">
+			<text class="product-price-favour">¥{{originalPrice}}</text>
+			<text class="product-price-original">¥{{favourPrice}}</text>
+			<text class="product-tip">{{tip}}</text>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		name: 'product',
+		props: ['image', 'title', 'originalPrice', 'favourPrice', 'tip']
+	}
+</script>
+
+<style>
+	.product {
+		padding: 10rpx 20rpx;
+		display: flex;
+		flex-direction: column;
+	}
+
+	.product-image {
+		height: 330rpx;
+		width: 330rpx;
+	}
+
+	.product-title {
+		width: 300rpx;
+		font-size: 32rpx;
+		word-break: break-all;
+		display: -webkit-box;
+		overflow: hidden;
+		text-overflow: ellipsis;
+		-webkit-box-orient: vertical;
+		-webkit-line-clamp: 2;
+	}
+
+	.product-price {
+		font-size: 28rpx;
+		position: relative;
+	}
+
+	.product-price-original {
+		color: #E80080;
+	}
+
+	.product-price-favour {
+		color: #888888;
+		text-decoration: line-through;
+		margin-left: 10rpx;
+	}
+
+	.product-tip {
+		position: absolute;
+		right: 10rpx;
+		background-color: #FF3333;
+		color: #FFFFFF;
+		padding: 0 10rpx;
+		border-radius: 5rpx;
+	}
+</style>

+ 175 - 0
components/tab-nvue/mediaList.vue

@@ -0,0 +1,175 @@
+<template>
+	<view class="view">
+		<view class="list-cell view" hover-class="uni-list-cell-hover" @click="bindClick">
+			<view class="media-list view" v-if="options.title">
+				<view class="view" :class="{'media-image-right': options.article_type === 2, 'media-image-left': options.article_type === 1}">
+					<text class="media-title" :class="{'media-title2': options.article_type === 1 || options.article_type === 2}">{{options.title}}</text>
+					<view v-if="options.image_list || options.image_url" class="image-section view" :class="{'image-section-right': options.article_type === 2, 'image-section-left': options.article_type === 1}">
+						<image class="image-list1" :class="{'image-list2': options.article_type === 1 || options.article_type === 2}"
+						 v-if="options.image_url" :src="options.image_url"></image>
+						<image class="image-list3" v-if="options.image_list" :src="source.url" v-for="(source, i) in options.image_list"
+						 :key="i" />
+					</view>
+				</view>
+				<view class="media-foot view">
+					<view class="media-info view">
+						<text class="info-text">{{options.source}}</text>
+						<text class="info-text">{{options.comment_count}}条评论</text>
+						<text class="info-text">{{options.datetime}}</text>
+					</view>
+					<view class="max-close-view view" @click.stop="close">
+						<view class="close-view view"><text class="close">×</text></view>
+					</view>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		props: {
+			options: {
+				type: Object,
+				default: function(e) {
+					return {}
+				}
+			}
+		},
+		methods: {
+			close(e) {
+				this.$emit('close');
+			},
+			bindClick() {
+				this.$emit('click');
+			}
+		}
+	}
+</script>
+
+<style>
+	.view {
+		display: flex;
+		flex-direction: column;
+		box-sizing: border-box;
+	}
+
+	.list-cell {
+		width: 750rpx;
+		padding: 0 30rpx;
+	}
+
+	.uni-list-cell-hover {
+		background-color: #eeeeee;
+	}
+
+	.media-list {
+		flex: 1;
+		flex-direction: column;
+		border-bottom-width: 1rpx;
+		border-bottom-style: solid;
+		border-bottom-color: #c8c7cc;
+		padding: 20rpx 0;
+	}
+
+	.media-image-right {
+		flex-direction: row;
+	}
+
+	.media-image-left {
+		flex-direction: row-reverse;
+	}
+
+	.media-title {
+		flex: 1;
+	}
+
+	.media-title {
+		lines: 3;
+		text-overflow: ellipsis;
+		font-size: 32rpx;
+		color: #555555;
+	}
+
+	.media-title2 {
+		flex: 1;
+		margin-top: 6rpx;
+		line-height: 40rpx;
+	}
+
+	.image-section {
+		margin-top: 20rpx;
+		flex-direction: row;
+		justify-content: space-between;
+	}
+
+	.image-section-right {
+		margin-top: 0rpx;
+		margin-left: 10rpx;
+		width: 225rpx;
+		height: 146rpx;
+	}
+
+	.image-section-left {
+		margin-top: 0rpx;
+		margin-right: 10rpx;
+		width: 225rpx;
+		height: 146rpx;
+	}
+
+	.image-list1 {
+		width: 690rpx;
+		height: 481rpx;
+	}
+
+	.image-list2 {
+		width: 225rpx;
+		height: 146rpx;
+	}
+
+	.image-list3 {
+		width: 225rpx;
+		height: 146rpx;
+	}
+
+	.media-info {
+		flex-direction: row;
+	}
+
+	.info-text {
+		margin-right: 20rpx;
+		color: #999999;
+		font-size: 24rpx;
+	}
+
+	.media-foot {
+		margin-top: 20rpx;
+		flex-direction: row;
+		justify-content: space-between;
+	}
+
+	.max-close-view {
+		align-items: center;
+		justify-content: flex-end;
+		flex-direction: row;
+		height: 40rpx;
+		width: 80rpx;
+	}
+
+	.close-view {
+		border-style: solid;
+		border-width: 1px;
+		border-color: #999999;
+		border-radius: 10rpx;
+		justify-content: center;
+		height: 30rpx;
+		width: 40rpx;
+		line-height: 30rpx;
+	}
+
+	.close {
+		text-align: center;
+		color: #999999;
+		font-size: 28rpx;
+	}
+</style>

File diff suppressed because it is too large
+ 5046 - 0
components/u-charts/u-charts.js


+ 59 - 0
components/u-link/u-link.vue

@@ -0,0 +1,59 @@
+<template>
+	<text style="text-decoration:underline" :href="href" @click="openURL" :inWhiteList="inWhiteList">{{text}}</text>
+</template>
+
+<script>
+	/**
+	 * @description u-link是一个外部网页超链接组件,在小程序内打开内部web-view组件或复制url,在app内打开外部浏览器,在h5端打开新网页
+	 * @property {String} href 点击后打开的外部网页url,小程序中必须以https://开头
+	 * @property {String} text 显示的文字
+	 * @property {Boolean} inWhiteList 是否在小程序白名单中,如果在的话,在小程序端会直接打开内置web-view,否则会只会复制url,提示在外部打开
+	 * @example * <u-link href="https://ext.dcloud.net.cn" text="https://ext.dcloud.net.cn" :inWhiteList="true"></u-link>
+	 */
+	export default {
+		name: 'u-link',
+		props: {
+			href: {
+				type: String,
+				default: ''
+			},
+			text: {
+				type: String,
+				default: ''
+			},
+			inWhiteList: {
+				type: Boolean,
+				default: false
+			}
+		},
+		methods: {
+			openURL() {
+				// #ifdef APP-PLUS
+				plus.runtime.openURL(this.href) //这里默认使用外部浏览器打开而不是内部web-view组件打开
+				// #endif
+				// #ifdef H5
+				window.open(this.href)
+				// #endif
+				// #ifdef MP
+				if (this.inWhiteList) { //如果在小程序的网址白名单中,会走内置webview打开,否则会复制网址提示在外部浏览器打开
+					uni.navigateTo({
+						url: '/pages/component/web-view/web-view?url=' + this.href
+					});
+				} else {
+					uni.setClipboardData({
+						data: this.href
+					});
+					uni.showModal({
+						content: '本网址无法直接在小程序内打开。已自动复制网址,请在手机浏览器里粘贴该网址',
+						showCancel: false
+					});
+				}
+				// #endif
+			}
+		}
+	}
+</script>
+
+<style>
+
+</style>

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

@@ -0,0 +1,140 @@
+<template>
+	<view class="uni-section">
+		<view class="uni-section-header" nvue>
+			<view v-if="type" class="uni-section__head">
+				<view :class="type" class="uni-section__head-tag"/>
+			</view>
+			<view class="uni-section__content">
+				<text :class="{'distraction':!subTitle}" :style="{color:color}" class="uni-section__content-title">{{ title }}</text>
+				<text v-if="subTitle" class="uni-section__content-sub">{{ subTitle }}</text>
+			</view>
+		</view>
+		<view :style="{padding: padding ? '10px' : ''}">
+			<slot/>
+		</view>
+	</view>
+</template>
+
+<script>
+
+	/**
+	 * Section 标题栏
+	 * @description 标题栏
+	 * @property {String} type = [line|circle] 标题装饰类型
+	 * 	@value line 竖线
+	 * 	@value circle 圆形
+	 * @property {String} title 主标题
+	 * @property {String} subTitle 副标题
+	 */
+
+	export default {
+		name: 'UniSection',
+		emits:['click'],
+		props: {
+			type: {
+				type: String,
+				default: ''
+			},
+			title: {
+				type: String,
+				default: ''
+			},
+			color:{
+				type: String,
+				default: '#333'
+			},
+			subTitle: {
+				type: String,
+				default: ''
+			},
+			padding: {
+				type: Boolean,
+				default: false
+			}
+		},
+		data() {
+			return {}
+		},
+		watch: {
+			title(newVal) {
+				if (uni.report && newVal !== '') {
+					uni.report('title', newVal)
+				}
+			}
+		},
+		methods: {
+			onClick() {
+				this.$emit('click')
+			}
+		}
+	}
+</script>
+<style lang="scss" scoped>
+	$uni-primary: #2979ff !default;
+	
+	.uni-section {
+		background-color: #fff;
+		// overflow: hidden;
+		margin-top: 10px;
+	}
+	.uni-section-header {
+		position: relative;
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+		align-items: center;
+		padding: 12px 10px;
+		// height: 50px;
+		font-weight: normal;
+	}
+	.uni-section__head {
+		flex-direction: row;
+		justify-content: center;
+		align-items: center;
+		margin-right: 10px;
+	}
+
+	.line {
+		height: 12px;
+		background-color: $uni-primary;
+		border-radius: 10px;
+		width: 4px;
+	}
+
+	.circle {
+		width: 8px;
+		height: 8px;
+		border-top-right-radius: 50px;
+		border-top-left-radius: 50px;
+		border-bottom-left-radius: 50px;
+		border-bottom-right-radius: 50px;
+		background-color: $uni-primary;
+	}
+
+	.uni-section__content {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: column;
+		flex: 1;
+		color: #333;
+	}
+
+	.uni-section__content-title {
+		font-size: 14px;
+		color: $uni-primary;
+	}
+
+	.distraction {
+		flex-direction: row;
+		align-items: center;
+	}
+
+	.uni-section__content-sub {
+		font-size: 12px;
+		color: #999;
+		line-height: 16px;
+		margin-top: 2px;
+	}
+</style>

+ 88 - 0
hybrid/html/local.html

@@ -0,0 +1,88 @@
+<!DOCTYPE html>
+<html>
+	<head>
+		<meta charset="utf-8" />
+		<meta name="viewport" content="width=device-width, initial-scale=1">
+		<title>本地网页</title>
+		<style type="text/css">
+			.btn {
+				display: block;
+				margin: 20px auto;
+				padding: 5px;
+				background-color: #007aff;
+				border: 0;
+				color: #ffffff;
+				height: 40px;
+				width: 200px;
+			}
+
+			.btn-red {
+				background-color: #dd524d;
+			}
+
+			.btn-yellow {
+				background-color: #f0ad4e;
+			}
+
+			.desc {
+				padding: 10px;
+				color: #999999;
+			}
+		</style>
+	</head>
+	<body>
+		<p class="desc">web-view 组件加载本地 html 示例,仅在 App 环境下生效。点击下列按钮,跳转至其它页面。</p>
+		<div class="btn-list">
+			<button class="btn" type="button" data-action="navigateTo">navigateTo</button>
+			<button class="btn" type="button" data-action="redirectTo">redirectTo</button>
+			<button class="btn" type="button" data-action="navigateBack">navigateBack</button>
+			<button class="btn" type="button" data-action="reLaunch">reLaunch</button>
+			<button class="btn" type="button" data-action="switchTab">switchTab</button>
+		</div>
+		<p class="desc">网页向应用发送消息。注意:小程序端应用会在此页面后退时接收到消息。</p>
+		<div class="btn-list">
+			<button class="btn btn-red" type="button" id="postMessage">postMessage</button>
+		</div>
+		<!-- uni 的 SDK -->
+		<script type="text/javascript" src="https://unpkg.com/@dcloudio/uni-webview-js@0.0.1/index.js"></script>
+		<script type="text/javascript">
+			document.addEventListener('UniAppJSBridgeReady', function() {
+				document.querySelector('.btn-list').addEventListener('click', function(evt) {
+					var target = evt.target;
+					if (target.tagName === 'BUTTON') {
+						var action = target.getAttribute('data-action');
+						switch (action) {
+							case 'switchTab':
+								uni.switchTab({
+									url: '/pages/tabBar/API/API'
+								});
+								break;
+							case 'reLaunch':
+								uni.reLaunch({
+									url: '/pages/tabBar/API/API'
+								});
+								break;
+							case 'navigateBack':
+								uni.navigateBack({
+									delta: 1
+								});
+								break;
+							default:
+								uni[action]({
+									url: '/pages/component/button/button'
+								});
+								break;
+						}
+					}
+				});
+				document.querySelector("#postMessage").addEventListener('click', function() {
+					uni.postMessage({
+						data: {
+							action: 'message'
+						}
+					});
+				})
+			});
+		</script>
+	</body>
+</html>

+ 14 - 0
index.html

@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0" />
+    <title></title>
+    <!--preload-links-->
+    <!--app-context-->
+  </head>
+  <body>
+    <div id="app"><!--app-html--></div>
+    <script type="module" src="/main.js"></script>
+  </body>
+</html>

+ 39 - 0
main.js

@@ -0,0 +1,39 @@
+import App from './App'
+import store from './store'
+
+// #ifndef VUE3
+import Vue from 'vue'
+Vue.config.productionTip = false
+Vue.prototype.$store = store
+Vue.prototype.$adpid = "1111111111"
+Vue.prototype.$backgroundAudioData = {
+	playing: false,
+	playTime: 0,
+	formatedPlayTime: '00:00:00'
+}
+App.mpType = 'app'
+const app = new Vue({
+	store,
+	...App
+})
+app.$mount()
+// #endif
+
+// #ifdef VUE3
+import {
+	createSSRApp
+} from 'vue'
+export function createApp() {
+	const app = createSSRApp(App)
+	app.use(store)
+	app.config.globalProperties.$adpid = "1111111111"
+	app.config.globalProperties.$backgroundAudioData = {
+		playing: false,
+		playTime: 0,
+		formatedPlayTime: '00:00:00'
+	}
+	return {
+		app
+	}
+}
+// #endif

+ 144 - 0
manifest.json

@@ -0,0 +1,144 @@
+{
+    "name" : "uni-app",
+    "appid" : "__UNI__3B208E0",
+    "description": "应用描述",
+    "versionName": "1.0.0",
+    "versionCode": "100",
+    "transformPx": false,
+    "app-plus": {
+        "usingComponents": true,
+        "nvueCompiler": "uni-app",
+        "nvueStyleCompiler": "uni-app",
+        "compilerVersion": 3,
+        "nvueLaunchMode": "fast",
+        "splashscreen": {
+            "alwaysShowBeforeRender": true,
+            "waiting": true,
+            "autoclose": true,
+            "delay": 0
+        },
+        "modules": {
+            "OAuth": {},
+            "Payment": {},
+            "Push": {},
+            "Share": {},
+            "Speech": {},
+            "VideoPlayer": {}
+        },
+        "distribute": {
+            "android": {
+                "permissions": [
+                    "<uses-feature android:name=\"android.hardware.camera\"/>",
+                    "<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
+                    "<uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\"/>",
+                    "<uses-permission android:name=\"android.permission.VIBRATE\"/>",
+                    "<uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\"/>",
+                    "<uses-permission android:name=\"android.permission.ACCESS_MOCK_LOCATION\"/>",
+                    "<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.CALL_PHONE\"/>",
+                    "<uses-permission android:name=\"android.permission.CAMERA\"/>",
+                    "<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
+                    "<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
+                    "<uses-permission android:name=\"android.permission.GET_TASKS\"/>",
+                    "<uses-permission android:name=\"android.permission.INTERNET\"/>",
+                    "<uses-permission android:name=\"android.permission.MODIFY_AUDIO_SETTINGS\"/>",
+                    "<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
+                    "<uses-permission android:name=\"android.permission.READ_CONTACTS\"/>",
+                    "<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
+                    "<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.READ_SMS\"/>",
+                    "<uses-permission android:name=\"android.permission.RECEIVE_BOOT_COMPLETED\"/>",
+                    "<uses-permission android:name=\"android.permission.RECORD_AUDIO\"/>",
+                    "<uses-permission android:name=\"android.permission.SEND_SMS\"/>",
+                    "<uses-permission android:name=\"android.permission.SYSTEM_ALERT_WINDOW\"/>",
+                    "<uses-permission android:name=\"android.permission.VIBRATE\"/>",
+                    "<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
+                    "<uses-permission android:name=\"android.permission.WRITE_CONTACTS\"/>",
+                    "<uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\"/>",
+                    "<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>",
+                    "<uses-permission android:name=\"android.permission.WRITE_SMS\"/>",
+                    "<uses-permission android:name=\"android.permission.RECEIVE_USER_PRESENT\"/>"
+                ]
+            },
+            "ios": {
+                "UIBackgroundModes": [
+                    "audio"
+                ],
+                "urlschemewhitelist": [
+                    "baidumap",
+                    "iosamap"
+                ]
+            },
+            "sdkConfigs": {
+                "speech": {
+                    "ifly": {}
+                }
+            },
+            "orientation": [
+                "portrait-primary"
+            ]
+        }
+    },
+    "quickapp": {},
+    "quickapp-native": {
+        "icon": "/static/logo.png",
+        "package": "com.example.demo",
+        "features": [
+            {
+                "name": "system.clipboard"
+            }
+        ]
+    },
+    "quickapp-webview": {
+        "icon": "/static/logo.png",
+        "package": "com.example.demo",
+        "minPlatformVersion": 1070,
+        "versionName": "1.0.0",
+        "versionCode": 100
+    },
+    "mp-weixin": {
+        "appid": "",
+        "setting": {
+            "urlCheck": false
+        },
+        "usingComponents": true,
+        "permission": {
+            "scope.userLocation": {
+                "desc": "演示定位能力"
+            }
+        }
+    },
+    "mp-alipay": {
+        "usingComponents": true
+    },
+    "mp-baidu": {
+        "usingComponents": true
+    },
+    "mp-toutiao": {
+        "usingComponents": true
+    },
+    "mp-jd": {
+        "usingComponents": true
+    },
+    "h5": {
+        "template": "template.h5.html",
+        "router": {
+            "mode": "history",
+            "base": ""
+        },
+        "sdkConfigs": {
+            "maps": {
+                "qqmap": {
+                    "key": "TKUBZ-D24AF-GJ4JY-JDVM2-IBYKK-KEBCU"
+                }
+            }
+        },
+        "async": {
+            "timeout": 20000
+        }
+    },
+    "vueVersion": "2"
+}

+ 106 - 0
package.json

@@ -0,0 +1,106 @@
+{
+	"id": "hello-uniapp",
+	"name": "hello-uniapp",
+	"displayName": "hello-uniapp 示例工程",
+	"version": "3.3.2",
+	"description": "uni-app 框架示例,一套代码,同时发行到iOS、Android、H5、小程序等多个平台,请使用手机扫码快速体验 uni-app 的强大功能",
+	"scripts": {
+		"test": "echo \"Error: no test specified\" && exit 1"
+	},
+	"repository": "https://github.com/dcloudio/hello-uniapp.git",
+	"keywords": [
+        "hello-uniapp",
+        "uni-app",
+        "uni-ui",
+        "示例工程"
+    ],
+	"author": "",
+	"license": "MIT",
+	"bugs": {
+		"url": "https://github.com/dcloudio/hello-uniapp/issues"
+	},
+	"homepage": "https://github.com/dcloudio/hello-uniapp#readme",
+	"dependencies": {},
+	"dcloudext": {
+		"category": [
+			"前端页面模板",
+			"uni-app前端项目模板"
+		],
+		"sale": {
+			"regular": {
+				"price": "0.00"
+			},
+			"sourcecode": {
+				"price": "0.00"
+			}
+		},
+		"contact": {
+			"qq": ""
+		},
+		"declaration": {
+			"ads": "无",
+			"data": "无",
+			"permissions": "无"
+		},
+		"npmurl": ""
+	},
+	"uni_modules": {
+		"dependencies": [],
+		"encrypt": [],
+		"platforms": {
+			"cloud": {
+				"tcb": "y",
+				"aliyun": "y"
+			},
+			"client": {
+				"App": {
+					"app-vue": "y",
+					"app-nvue": "y"
+				},
+				"H5-mobile": {
+					"Safari": "y",
+					"Android Browser": "y",
+					"微信浏览器(Android)": "y",
+					"QQ浏览器(Android)": "y"
+				},
+				"H5-pc": {
+					"Chrome": "y",
+					"IE": "y",
+					"Edge": "y",
+					"Firefox": "y",
+					"Safari": "y"
+				},
+				"小程序": {
+					"微信": "y",
+					"阿里": "y",
+					"百度": "y",
+					"字节跳动": "y",
+					"QQ": "y",
+					"京东": "y"
+				},
+				"快应用": {
+					"华为": "u",
+					"联盟": "u"
+                },
+                "Vue": {
+                    "vue2": "y",
+                    "vue3": "y"
+                }
+			}
+		}
+	},
+
+	"uni-app": {
+		"scripts": {
+			"mp-dingtalk": {
+				"title": "钉钉小程序",
+				"env": {
+					"UNI_PLATFORM": "mp-alipay"
+				},
+				"define": {
+					"MP-DINGTALK": true
+				}
+			}
+		}
+	}
+}

File diff suppressed because it is too large
+ 1338 - 0
pages.json


+ 60 - 0
pages/API/action-sheet/action-sheet.vue

@@ -0,0 +1,60 @@
+<template>
+	<view>
+		<page-head :title="title"></page-head>
+		<view class="uni-padding-wrap">
+			<view class="uni-btn-v">
+				<button class="target" type="default" @tap="actionSheetTap">弹出action sheet</button>
+			</view>
+		</view>
+	</view>
+</template>
+<script>
+	export default {
+		data() {
+			return {
+				title: 'action-sheet',
+				buttonRect: {}
+			}
+		},
+		// #ifdef H5
+		onReady() {
+			this.getNodeInfo()
+			window.addEventListener('resize', this.getNodeInfo)
+		},
+		beforeDestroy() {
+			window.removeEventListener('resize', this.getNodeInfo)
+		},
+		// #endif
+		methods: {
+			actionSheetTap() {
+				const that = this
+				uni.showActionSheet({
+					title: '标题',
+					itemList: ['item1', 'item2', 'item3', 'item4'],
+					popover: {
+						// 104: navbar + topwindow 高度,暂时 fix createSelectorQuery 在 pc 上获取 top 不准确的 bug
+						top: that.buttonRect.top + 104  + that.buttonRect.height,
+						left: that.buttonRect.left + that.buttonRect.width / 2
+					},
+					success: (e) => {
+						console.log(e.tapIndex);
+						uni.showToast({
+							title: "点击了第" + e.tapIndex + "个选项",
+							icon: "none"
+						})
+					}
+				})
+			},
+			// #ifdef H5
+			getNodeInfo() {
+				uni.createSelectorQuery().select('.target').boundingClientRect().exec((ret) => {
+					const rect = ret[0]
+					if (rect) {
+						this.buttonRect = rect
+					}
+				});
+			}
+			// #endif
+		}
+	}
+</script>

+ 102 - 0
pages/API/add-phone-contact/add-phone-contact.vue

@@ -0,0 +1,102 @@
+<template>
+	<view>
+		<page-head :title="title"></page-head>
+		<view class="uni-common-mt">
+			<view class="uni-list">
+				<view class="uni-list-cell">
+					<view class="uni-list-cell-left">
+						<view class="uni-label">名称</view>
+					</view>
+					<view class="uni-list-cell-db">
+						<input class="uni-input" type="text" placeholder="请输入联系人名称" name="name" :value="name" @input="nameChange"/>
+					</view>
+				</view>
+				<view class="uni-list-cell">
+					<view class="uni-list-cell-left">
+						<view class="uni-label">手机号</view>
+					</view>
+					<view class="uni-list-cell-db">
+						<input class="uni-input" type="text" placeholder="请输入联系人手机号" name="phone" :value="phone" @input="phoneChange"/>
+					</view>
+				</view>
+			</view>
+			<view class="uni-padding-wrap">
+				<view class="uni-btn-v">
+					<button type="primary" class="btn-setstorage" @tap="add">添加联系人</button>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+<script>
+	// #ifdef APP-PLUS
+	import permision from "@/common/permission.js"
+	// #endif
+	export default {
+		data() {
+			return {
+				title: 'addPhoneContact',
+				name: '',
+				phone: ''
+			}
+		},
+		methods: {
+			nameChange: function(e) {
+				this.name = e.detail.value
+			},
+			phoneChange: function(e) {
+				this.phone = e.detail.value
+			},
+			async add() {
+				// #ifdef APP-PLUS
+				let status = await this.checkPermission();
+				if (status !== 1) {
+				    return;
+				}
+				// #endif
+
+				uni.addPhoneContact({
+					firstName: this.name,
+					mobilePhoneNumber: this.phone,
+					success: function() {
+						uni.showModal({
+							content: '已成功添加联系人!',
+							showCancel: false
+						})
+					},
+					fail: function() {
+						uni.showModal({
+							content: '添加联系人失败!',
+							showCancel: false
+						})
+					}
+				});
+			}
+			// #ifdef APP-PLUS
+			,
+			async checkPermission() {
+				let status = permision.isIOS ? await permision.requestIOS('contact') :
+					await permision.requestAndroid('android.permission.WRITE_CONTACTS');
+
+				if (status === null || status === 1) {
+					status = 1;
+				} else {
+					uni.showModal({
+						content: "需要联系人权限",
+						confirmText: "设置",
+						success: function(res) {
+							if (res.confirm) {
+								permision.gotoAppSetting();
+							}
+						}
+					})
+				}
+				return status;
+			}
+			// #endif
+		}
+	}
+</script>
+
+<style>
+</style>

+ 125 - 0
pages/API/animation/animation.vue

@@ -0,0 +1,125 @@
+<template>
+	<view>
+		<page-head :title="title"></page-head>
+		<view class="uni-padding-wrap uni-common-mt">
+			<view class="animation-element-wrapper">
+				<view class="animation-element" :animation="animationData"></view>
+			</view>
+			<scroll-view class="animation-buttons" scroll-y="true">
+				<button class="animation-button" @tap="rotate">旋转</button>
+				<button class="animation-button" @tap="scale">缩放</button>
+				<button class="animation-button" @tap="translate">移动</button>
+				<button class="animation-button" @tap="skew">倾斜</button>
+				<button class="animation-button" @tap="rotateAndScale">旋转并缩放</button>
+				<button class="animation-button" @tap="rotateThenScale">旋转后缩放</button>
+				<button class="animation-button" @tap="all">同时展示全部</button>
+				<button class="animation-button" @tap="allInQueue">顺序展示全部</button>
+				<button class="animation-button animation-button-reset" @tap="reset">还原</button>
+			</scroll-view>
+		</view>
+	</view>
+</template>
+<script>
+	export default {
+		data() {
+			return {
+				title: 'createAnimation',
+				animationData: ''
+			}
+		},
+		onUnload(){
+			this.animationData = ''
+		},
+		onLoad() {
+			this.animation = uni.createAnimation()
+		},
+		methods: {
+			rotate: function () {
+				this.animation.rotate(Math.random() * 720 - 360).step()
+				this.animationData = this.animation.export()
+			},
+			scale: function () {
+				this.animation.scale(Math.random() * 2).step()
+				this.animationData = this.animation.export()
+			},
+			translate: function () {
+				this.animation.translate(Math.random() * 100 - 50, Math.random() * 100 - 50).step()
+				this.animationData = this.animation.export()
+			},
+			skew: function () {
+				this.animation.skew(Math.random() * 90, Math.random() * 90).step()
+				this.animationData = this.animation.export()
+			},
+			rotateAndScale: function () {
+				this.animation.rotate(Math.random() * 720 - 360)
+					.scale(Math.random() * 2)
+					.step()
+				this.animationData = this.animation.export()
+			},
+			rotateThenScale: function () {
+				this.animation.rotate(Math.random() * 720 - 360).step()
+					.scale(Math.random() * 2).step()
+				this.animationData = this.animation.export()
+			},
+			all: function () {
+				this.animation.rotate(Math.random() * 720 - 360)
+					.scale(Math.random() * 2)
+					.translate(Math.random() * 100 - 50, Math.random() * 100 - 50)
+					.skew(Math.random() * 90, Math.random() * 90)
+					.step()
+				this.animationData = this.animation.export()
+			},
+			allInQueue: function () {
+				this.animation.rotate(Math.random() * 720 - 360).step()
+					.scale(Math.random() * 2).step()
+					.translate(Math.random() * 100 - 50, Math.random() * 100 - 50).step()
+					.skew(Math.random() * 90, Math.random() * 90).step()
+				this.animationData = this.animation.export()
+			},
+			reset: function () {
+				this.animation.rotate(0, 0)
+					.scale(1)
+					.translate(0, 0)
+					.skew(0, 0)
+					.step({
+						duration: 0
+					})
+				this.animationData = this.animation.export()
+			}
+		}
+	}
+</script>
+
+<style>
+	.animation-element-wrapper {
+		display: flex;
+		width: 100%;
+		padding-top: 150rpx;
+		padding-bottom: 150rpx;
+		justify-content: center;
+		overflow: hidden;
+		background-color: #ffffff;
+	}
+
+	.animation-element {
+		width: 200rpx;
+		height: 200rpx;
+		background-color: #1AAD19;
+	}
+
+	.animation-buttons {
+		padding:30rpx 0;
+		width: 100%;
+		/* height: 360rpx; */
+	}
+
+	.animation-button {
+		float: left;
+		width: 44%;
+		margin: 15rpx 3%;
+	}
+
+	.animation-button-reset {
+		width: 94%;
+	}
+</style>

+ 163 - 0
pages/API/background-audio/background-audio.vue

@@ -0,0 +1,163 @@
+<template>
+	<view>
+		<page-head :title="title"></page-head>
+		<view class="uni-padding-wrap">
+			<view class="uni-center">
+				<text class="time-big">{{formatedPlayTime}}</text>
+			</view>
+			<view class="uni-common-mt">
+				<slider class="slider" min="0" max="21" step="1" :value="playTime" @change="seek"></slider>
+			</view>
+			<view class="play-time">
+				<text>00:00</text>
+				<text>00:21</text>
+			</view>
+			<view class="uni-hello-text">注意:离开当前页面后背景音乐将保持播放,但退出uni-app将停止</view>
+			<view class="page-body-buttons">
+				<block v-if="playing">
+					<view class="page-body-button" @tap="stop">
+						<image src="/static/stop.png"></image>
+					</view>
+					<view class="page-body-button" @tap="pause">
+						<image src="/static/pause.png"></image>
+					</view>
+				</block>
+				<block v-if="!playing">
+					<view class="page-body-button"></view>
+					<view class="page-body-button" @tap="play">
+						<image src="/static/play.png"></image>
+					</view>
+				</block>
+				<view class="page-body-button"></view>
+			</view>
+		</view>
+	</view>
+</template>
+<script>
+
+	import * as util from '../../../common/util.js';
+
+	export default {
+		data() {
+			return {
+				title: 'backgroundAudio',
+				bgAudioMannager: null,
+				dataUrl: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-hello-uniapp/2cc220e0-c27a-11ea-9dfb-6da8e309e0d8.mp3',
+				playing: false,
+				playTime: 0,
+				formatedPlayTime: '00:00:00'
+			}
+		},
+		onLoad: function () {
+			this.playing = this.$backgroundAudioData.playing;
+			this.playTime = this.$backgroundAudioData.playTime;
+			this.formatedPlayTime = this.$backgroundAudioData.formatedPlayTime;
+
+			let bgAudioMannager = uni.getBackgroundAudioManager();
+			if(!bgAudioMannager.title){
+				bgAudioMannager.title = '致爱丽丝';
+			}
+			if(!bgAudioMannager.singer) {
+				bgAudioMannager.singer = '暂无';
+			}
+			if(!bgAudioMannager.coverImgUrl){
+				bgAudioMannager.coverImgUrl = 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/c517b410-5184-11eb-b997-9918a5dda011.jpeg';
+			}
+
+			bgAudioMannager.onPlay(() => {
+				console.log("开始播放");
+				this.$backgroundAudioData.playing = this.playing = true;
+			})
+			bgAudioMannager.onPause(() => {
+				console.log("暂停播放");
+				this.$backgroundAudioData.playing = this.playing = false;
+			})
+			bgAudioMannager.onEnded(() => {
+				this.playing = false;
+				this.$backgroundAudioData.playing = false;
+				this.$backgroundAudioData.playTime = this.playTime = 0;
+				this.$backgroundAudioData.formatedPlayTime = this.formatedPlayTime = util.formatTime(0);
+			})
+
+			bgAudioMannager.onTimeUpdate((e) => {
+				if (Math.floor(bgAudioMannager.currentTime) > Math.floor(this.playTime)) {
+					this.$backgroundAudioData.formatedPlayTime = this.formatedPlayTime = util.formatTime(Math.floor(bgAudioMannager.currentTime));
+				}
+				this.$backgroundAudioData.playTime = this.playTime = bgAudioMannager.currentTime;
+			})
+
+			this.bgAudioMannager = bgAudioMannager;
+		},
+		methods: {
+			play: function (res) {
+				if (!this.bgAudioMannager.src) {
+					this.bgAudioMannager.startTime = this.playTime;
+					this.bgAudioMannager.src = this.dataUrl;
+				} else {
+					this.bgAudioMannager.seek(this.playTime);
+					this.bgAudioMannager.play();
+				}
+			},
+			seek: function (e) {
+				this.bgAudioMannager.seek(e.detail.value);
+			},
+			pause: function () {
+				this.bgAudioMannager.pause();
+			},
+			stop: function () {
+				this.bgAudioMannager.stop();
+				this.$backgroundAudioData.playing = this.playing = false;
+				this.$backgroundAudioData.playTime = this.playTime = 0;
+				this.$backgroundAudioData.formatedPlayTime = this.formatedPlayTime = util.formatTime(0);
+			}
+		}
+	}
+</script>
+
+<style>
+	image {
+		width: 150rpx;
+		height: 150rpx;
+	}
+
+	.page-body-text {
+		padding: 0 30rpx;
+	}
+
+	.page-body-wrapper {
+		margin-top: 0;
+	}
+
+	.page-body-info {
+		padding-bottom: 50rpx;
+	}
+
+	.time-big {
+		font-size: 60rpx;
+		margin: 20rpx;
+	}
+
+	.slider {
+		width:630rpx;
+	}
+
+	.play-time {
+		font-size: 28rpx;
+		width:100%;
+		padding: 20rpx 0;
+		display: flex;
+		justify-content: space-between;
+		box-sizing: border-box;
+	}
+
+	.page-body-buttons {
+		display: flex;
+		justify-content: space-around;
+		margin-top: 100rpx;
+	}
+
+	.page-body-button {
+		width: 250rpx;
+		text-align: center;
+	}
+</style>

+ 723 - 0
pages/API/bluetooth/bluetooth.vue

@@ -0,0 +1,723 @@
+<template>
+	<view>
+		<page-head :title="title"></page-head>
+		<view class="uni-padding-wrap uni-common-mt">
+			<view>
+				本蓝牙协议只支持低功耗蓝牙协议ble。如果想连接非ble蓝牙设备,请在社区搜索 Native.js 蓝牙。
+			</view>
+			<view class="uni-btn-v">
+				<button type="primary" :disabled="disabled[0]" @click="openBluetoothAdapter">
+					初始化蓝牙模块
+				</button>
+				<view v-if="!adapterState.available">
+					{{ '蓝牙适配器不可用,请初始化蓝牙模块' }}
+				</view>
+				<button
+					type="primary"
+					:loading="searchLoad"
+					:disabled="disabled[1]"
+					@click="startBluetoothDevicesDiscovery"
+				>
+					开始搜索蓝牙设备
+				</button>
+				<button
+					type="primary"
+					:disabled="disabled[2]"
+					@click="stopBluetoothDevicesDiscovery(false)"
+				>
+					停止搜索蓝牙设备
+				</button>
+				<button
+					type="primary"
+					:loading="newDeviceLoad"
+					:disabled="disabled[3]"
+					@click="queryDevices"
+				>
+					选择设备
+				</button>
+				<view v-if="equipment.length > 0">
+					{{
+						(connected ? '已连接设备' : '已选择设备') +
+							' : ' +
+							equipment[0].name +
+							' (' +
+							equipment[0].deviceId +
+							')'
+					}}
+				</view>
+				<button type="primary" :disabled="disabled[4]" @click="createBLEConnection">
+					连接蓝牙设备
+				</button>
+				<button type="primary" :disabled="disabled[5]" @click="getBLEDeviceServices">
+					选择设备服务
+				</button>
+				<view v-if="servicesData.length > 0">已选服务uuid:{{ servicesData[0].uuid }}</view>
+				<button type="primary" :disabled="disabled[6]" @click="getBLEDeviceCharacteristics">
+					获取服务的特征值
+				</button>
+				<view v-if="characteristicsData.length > 0">
+					<view class="uni-list_name">uuid:{{ characteristicsData[0].uuid }}</view>
+					<view class="uni-list_item">
+						是否支持 read 操作:{{ characteristicsData[0].properties.read }}
+					</view>
+					<view class="uni-list_item">
+						是否支持 write 操作:{{ characteristicsData[0].properties.write }}
+					</view>
+					<view class="uni-list_item">
+						是否支持 notify 操作:{{ characteristicsData[0].properties.notify }}
+					</view>
+					<view class="uni-list_item">
+						是否支持 indicate 操作:{{ characteristicsData[0].properties.indicate }}
+					</view>
+				</view>
+				<!-- <button type="primary" :disabled="disabled[7]" @click="readBLECharacteristicValue">
+					读取特征值数据
+				</button>
+				<view v-if="valueChangeData.serviceId">
+					<view class="list-name">
+						特征值最新的值:{{ valueChangeData.value || '还没有最新值' }}
+					</view>
+				</view> -->
+				<!-- <button type="primary" :disabled="disabled[8]" @click="w">写入特征值数据</button> -->
+				<button type="primary" :disabled="disabled[9]" @click="closeBLEConnection">
+					断开蓝牙设备
+				</button>
+				<button type="primary" :disabled="disabled[10]" @click="closeBluetoothAdapter">
+					关闭蓝牙模块
+				</button>
+			</view>
+		</view>
+		<!-- 遮罩 -->
+		<view v-if="maskShow" class="uni-mask" @touchmove.stop.prevent="moveHandle" @click="maskclose">
+			<scroll-view class="uni-scroll_box" scroll-y @touchmove.stop.prevent="moveHandle" @click.stop="moveHandle">
+				<view class="uni-title">
+					已经发现{{ list.length }}{{ showMaskType === 'device' ? '台设备' : '个服务' }}:
+				</view>
+				<view
+					class="uni-list-box"
+					v-for="(item, index) in list"
+					:key="index"
+					@click="tapQuery(item)"
+				>
+					<view v-if="showMaskType === 'device'">
+						<view class="uni-list_name">{{ item.name || item.localName }}</view>
+						<view class="uni-list_item">信号强度:{{ item.RSSI }}dBm</view>
+						<view class="uni-list_item">UUID:{{ item.deviceId }}</view>
+						<!-- <view class="list-item" v-if="showMaskType === 'device'">
+							Service数量:{{ item.advertisServiceUUIDs.length }}
+						</view> -->
+					</view>
+					<view v-if="showMaskType === 'service'">
+						<view class="uni-list_item" style="line-height:2.2;">
+							UUID: {{ item.uuid }}
+							<text v-if="showMaskType === 'service'">
+								{{ item.isPrimary ? '(主服务)' : '' }}
+							</text>
+						</view>
+					</view>
+					<view v-if="showMaskType === 'characteristics'">
+						<view class="uni-list_name">uuid:{{ item.uuid }}</view>
+						<view class="uni-list_item">是否支持 read 操作:{{ item.properties.read }}</view>
+						<view class="uni-list_item">
+							是否支持 write 操作:{{ item.properties.write }}
+						</view>
+						<view class="uni-list_item">
+							是否支持 notify 操作:{{ item.properties.notify }}
+						</view>
+						<view class="uni-list_item">
+							是否支持 indicate 操作:{{ item.properties.indicate }}
+						</view>
+					</view>
+				</view>
+			</scroll-view>
+		</view>
+	</view>
+</template>
+<script>
+export default {
+	data() {
+		return {
+			title: 'bluetooth',
+			disabled: [false, true, true, true, true, true, true, true, true, true, true],
+			newDeviceLoad: false,
+			searchLoad: false,
+			maskShow: false,
+			equipment: [],
+			adapterState: {
+				discovering: false,
+				available: false
+			},
+			connected: false,
+			showMaskType: 'device',
+			servicesData: [],
+			characteristicsData: [],
+			valueChangeData: {},
+			isStop:true ,
+			list: []
+		};
+	},
+	onLoad() {
+		this.onBLEConnectionStateChange();
+	},
+	methods: {
+		moveHandle() {},
+		/**
+		 * 关闭遮罩
+		 */
+		maskclose(){
+			this.maskShow = false;
+		},
+		/**
+		 * 选择设备
+		 */
+		queryDevices() {
+			// this.newDeviceLoad = true;
+			this.showMaskType = 'device';
+			this.maskShow = true;
+		},
+		tapQuery(item) {
+			if (this.showMaskType === 'device') {
+				this.$set(this.disabled, 4, false);
+				if (this.equipment.length > 0) {
+					this.equipment[0] = item;
+				} else {
+					this.equipment.push(item);
+				}
+				this.newDeviceLoad = false;
+			}
+			if (this.showMaskType === 'service') {
+				this.$set(this.disabled, 6, false);
+				if (this.servicesData.length > 0) {
+					this.servicesData[0] = item;
+				} else {
+					this.servicesData.push(item);
+				}
+			}
+			if (this.showMaskType === 'characteristics') {
+				this.$set(this.disabled, 7, false);
+				if (this.characteristicsData.length > 0) {
+					this.characteristicsData[0] = item;
+				} else {
+					this.characteristicsData.push(item);
+				}
+			}
+			this.maskShow = false;
+		},
+		/**
+		 * 初始化蓝牙设备
+		 */
+		openBluetoothAdapter() {
+			uni.openBluetoothAdapter({
+				success: e => {
+					console.log('初始化蓝牙成功:' + e.errMsg);
+					console.log(JSON.stringify(e));
+					this.isStop = false ;
+					this.$set(this.disabled, 0, true);
+					this.$set(this.disabled, 1, false);
+					this.$set(this.disabled, 10, false);
+					this.getBluetoothAdapterState();
+				},
+				fail: e => {
+					console.log(e)
+					console.log('初始化蓝牙失败,错误码:' + (e.errCode || e.errMsg));
+					if (e.errCode !== 0) {
+						initTypes(e.errCode,e.errMsg);
+					}
+				}
+			});
+		},
+		/**
+		 * 开始搜索蓝牙设备
+		 */
+		startBluetoothDevicesDiscovery() {
+			uni.startBluetoothDevicesDiscovery({
+				success: e => {
+					console.log('开始搜索蓝牙设备:' + e.errMsg);
+					this.searchLoad = true;
+					this.$set(this.disabled, 1, true);
+					this.$set(this.disabled, 2, false);
+					this.$set(this.disabled, 3, false);
+					this.onBluetoothDeviceFound();
+				},
+				fail: e => {
+					console.log('搜索蓝牙设备失败,错误码:' + e.errCode);
+					if (e.errCode !== 0) {
+						initTypes(e.errCode);
+					}
+				}
+			});
+		},
+		/**
+		 * 停止搜索蓝牙设备
+		 */
+		stopBluetoothDevicesDiscovery(types) {
+			uni.stopBluetoothDevicesDiscovery({
+				success: e => {
+					console.log('停止搜索蓝牙设备:' + e.errMsg);
+					if (types) {
+						this.$set(this.disabled, 1, true);
+					} else {
+						this.$set(this.disabled, 1, false);
+					}
+					this.$set(this.disabled, 2, true);
+					// this.$set(this.disabled, 3, true);
+					this.searchLoad = false;
+				},
+				fail: e => {
+					console.log('停止搜索蓝牙设备失败,错误码:' + e.errCode);
+					if (e.errCode !== 0) {
+						initTypes(e.errCode);
+					}
+				}
+			});
+		},
+		/**
+		 * 发现外围设备
+		 */
+		onBluetoothDeviceFound() {
+			uni.onBluetoothDeviceFound(devices => {
+				console.log('开始监听寻找到新设备的事件');
+				// this.$set(this.disabled, 3, false);
+				this.getBluetoothDevices();
+			});
+		},
+		/**
+		 * 获取在蓝牙模块生效期间所有已发现的蓝牙设备。包括已经和本机处于连接状态的设备。
+		 */
+		getBluetoothDevices() {
+			uni.getBluetoothDevices({
+				success: res => {
+					this.newDeviceLoad = false;
+					console.log('获取蓝牙设备成功:' + res.errMsg);
+					// console.log(JSON.stringify(res))
+					this.list = res.devices;
+				},
+				fail: e => {
+					console.log('获取蓝牙设备错误,错误码:' + e.errCode);
+					if (e.errCode !== 0) {
+						initTypes(e.errCode);
+					}
+				}
+			});
+		},
+		/**
+		 * 获取本机蓝牙适配器状态
+		 */
+		getBluetoothAdapterState() {
+			console.log('--->');
+			uni.getBluetoothAdapterState({
+				success: res => {
+					console.log(JSON.stringify(res));
+					this.adapterState = res;
+				},
+				fail: e => {
+					console.log('获取本机蓝牙适配器状态失败,错误码:' + e.errCode);
+					if (e.errCode !== 0) {
+						initTypes(e.errCode);
+					}
+				}
+			});
+		},
+		/**
+		 * 连接低功耗蓝牙
+		 */
+		createBLEConnection() {
+			let deviceId = this.equipment[0].deviceId;
+			uni.showToast({
+				title: '连接蓝牙...',
+				icon: 'loading',
+				duration: 99999
+			});
+			uni.createBLEConnection({
+				// 这里的 deviceId 需要已经通过 createBLEConnection 与对应设备建立链接
+				deviceId,
+				success: res => {
+					console.log(res);
+					console.log('连接蓝牙成功:' + res.errMsg);
+					// 连接设备后断开搜索 并且不能搜索设备
+					this.stopBluetoothDevicesDiscovery(true);
+					uni.hideToast();
+					uni.showToast({
+						title: '连接成功',
+						icon: 'success',
+						duration: 2000
+					});
+					this.$set(this.disabled, 3, true);
+					this.$set(this.disabled, 4, true);
+					this.$set(this.disabled, 5, false);
+					this.$set(this.disabled, 9, false);
+					this.connected = true;
+				},
+				fail: e => {
+					console.log('连接低功耗蓝牙失败,错误码:' + e.errCode);
+					if (e.errCode !== 0) {
+						initTypes(e.errCode);
+					}
+				}
+			});
+		},
+		/**
+		 * 断开与低功耗蓝牙设备的连接
+		 */
+		closeBLEConnection() {
+			let deviceId = this.equipment[0].deviceId;
+			uni.closeBLEConnection({
+				deviceId,
+				success: res => {
+					console.log(res);
+					console.log('断开低功耗蓝牙成功:' + res.errMsg);
+					this.$set(this.disabled, 1, false);
+					this.$set(this.disabled, 3, true);
+					this.$set(this.disabled, 4, true);
+					this.$set(this.disabled, 5, true);
+					this.$set(this.disabled, 6, true);
+					this.$set(this.disabled, 7, true);
+					this.$set(this.disabled, 8, true);
+					this.$set(this.disabled, 9, true);
+					this.equipment = [];
+					this.servicesData = [];
+					this.characteristicsData = [];
+				},
+				fail: e => {
+					console.log('断开低功耗蓝牙成功,错误码:' + e.errCode);
+					if (e.errCode !== 0) {
+						initTypes(e.errCode);
+					}
+				}
+			});
+		},
+		/**
+		 * 获取所有服务
+		 */
+		getBLEDeviceServices() {
+			let deviceId = this.equipment[0].deviceId;
+			console.log('获取所有服务的 uuid:' + deviceId);
+
+			uni.getBLEDeviceServices({
+				// 这里的 deviceId 需要已经通过 createBLEConnection 与对应设备建立链接
+				deviceId,
+				success: res => {
+					console.log(JSON.stringify(res.services));
+					console.log('获取设备服务成功:' + res.errMsg);
+					this.$set(this.disabled, 7, true);
+					this.$set(this.disabled, 8, true);
+					this.showMaskType = 'service';
+					this.list = res.services;
+
+					this.characteristicsData = [];
+					if (this.list.length <= 0) {
+						toast('获取服务失败,请重试!');
+						return;
+					}
+					this.maskShow = true;
+				},
+				fail: e => {
+					console.log('获取设备服务失败,错误码:' + e.errCode);
+					if (e.errCode !== 0) {
+						initTypes(e.errCode);
+					}
+				}
+			});
+		},
+		/**
+		 * 获取某个服务下的所有特征值
+		 */
+		getBLEDeviceCharacteristics() {
+			let deviceId = this.equipment[0].deviceId;
+			let serviceId = this.servicesData[0].uuid;
+			console.log(deviceId);
+			console.log(serviceId);
+			uni.getBLEDeviceCharacteristics({
+				// 这里的 deviceId 需要已经通过 createBLEConnection 与对应设备建立链接
+				deviceId,
+				// 这里的 serviceId 需要在 getBLEDeviceServices 接口中获取
+				serviceId,
+				success: res => {
+					console.log(JSON.stringify(res));
+					console.log('获取特征值成功:' + res.errMsg);
+					this.$set(this.disabled, 7, true);
+					this.valueChangeData = {};
+					this.showMaskType = 'characteristics';
+					this.list = res.characteristics;
+					if (this.list.length <= 0) {
+						toast('获取特征值失败,请重试!');
+						return;
+					}
+					this.maskShow = true;
+				},
+				fail: e => {
+					console.log('获取特征值失败,错误码:' + e.errCode);
+					if (e.errCode !== 0) {
+						initTypes(e.errCode);
+					}
+				}
+			});
+		},
+		/**
+		 * 监听低功耗蓝牙连接状态的改变事件。包括开发者主动连接或断开连接,设备丢失,连接异常断开等等
+		 */
+		onBLEConnectionStateChange() {
+			uni.onBLEConnectionStateChange(res => {
+				// 该方法回调中可以用于处理连接意外断开等异常情况
+				console.log(`蓝牙连接状态 -------------------------->`);
+				console.log(JSON.stringify(res));
+				if (!res.connected) {
+					if(this.isStop) return ;
+					console.log('断开低功耗蓝牙成功:');
+					this.$set(this.disabled, 1, false);
+					this.$set(this.disabled, 3, true);
+					this.$set(this.disabled, 4, true);
+					this.$set(this.disabled, 5, true);
+					this.$set(this.disabled, 6, true);
+					this.$set(this.disabled, 7, true);
+					this.$set(this.disabled, 8, true);
+					this.$set(this.disabled, 9, true);
+					this.searchLoad = false;
+					this.equipment = [];
+					this.servicesData = [];
+					this.characteristicsData = [];
+					this.valueChangeData = {};
+					toast('已经断开当前蓝牙连接');
+				}
+			});
+		},
+		/**
+		 * 读取低功耗蓝牙设备的特征值的二进制数据值。注意:必须设备的特征值支持 read 才可以成功调用
+		 */
+		readBLECharacteristicValue() {
+			let deviceId = this.equipment[0].deviceId;
+			let serviceId = this.servicesData[0].uuid;
+			let characteristicId = this.characteristicsData[0].uuid;
+			console.log(deviceId);
+			console.log(serviceId);
+			console.log(characteristicId);
+			uni.readBLECharacteristicValue({
+				// 这里的 deviceId 需要已经通过 createBLEConnection 与对应设备建立链接
+				deviceId,
+				// 这里的 serviceId 需要在 getBLEDeviceServices 接口中获取
+				serviceId,
+				// 这里的 characteristicId 需要在 getBLEDeviceCharacteristics 接口中获取
+				characteristicId,
+				success: res => {
+					console.log('读取设备数据值成功');
+					console.log(JSON.stringify(res));
+					this.notifyBLECharacteristicValueChange();
+				},
+				fail(e) {
+					console.log('读取设备数据值失败,错误码:' + e.errCode);
+					if (e.errCode !== 0) {
+						initTypes(e.errCode);
+					}
+				}
+			});
+			this.onBLECharacteristicValueChange();
+		},
+		/**
+		 * 监听低功耗蓝牙设备的特征值变化事件。必须先启用 notifyBLECharacteristicValueChange 接口才能接收到设备推送的 notification。
+		 */
+		onBLECharacteristicValueChange() {
+			// 必须在这里的回调才能获取
+			uni.onBLECharacteristicValueChange(characteristic => {
+				console.log('监听低功耗蓝牙设备的特征值变化事件成功');
+				console.log(JSON.stringify(characteristic));
+				this.valueChangeData = characteristic;
+			});
+		},
+		/**
+		 * 订阅操作成功后需要设备主动更新特征值的 value,才会触发 uni.onBLECharacteristicValueChange 回调。
+		 */
+		notifyBLECharacteristicValueChange() {
+			let deviceId = this.equipment[0].deviceId;
+			let serviceId = this.servicesData[0].uuid;
+			let characteristicId = this.characteristicsData[0].uuid;
+			let notify = this.characteristicsData[0].properties.notify;
+			console.log(deviceId);
+			console.log(serviceId);
+			console.log(characteristicId);
+			console.log(notify);
+			uni.notifyBLECharacteristicValueChange({
+				state: true, // 启用 notify 功能
+				// 这里的 deviceId 需要已经通过 createBLEConnection 与对应设备建立链接
+				deviceId,
+				// 这里的 serviceId 需要在 getBLEDeviceServices 接口中获取
+				serviceId,
+				// 这里的 characteristicId 需要在 getBLEDeviceCharacteristics 接口中获取
+				characteristicId,
+				success(res) {
+					console.log('notifyBLECharacteristicValueChange success:' + res.errMsg);
+					console.log(JSON.stringify(res));
+				}
+			});
+		},
+		/**
+		 * 	断开蓝牙模块
+		 */
+		closeBluetoothAdapter(OBJECT) {
+			uni.closeBluetoothAdapter({
+				success: res => {
+					console.log('断开蓝牙模块成功');
+					this.isStop = true ;
+					this.$set(this.disabled, 0, false);
+					this.$set(this.disabled, 1, true);
+					this.$set(this.disabled, 2, true);
+					this.$set(this.disabled, 3, true);
+					this.$set(this.disabled, 4, true);
+					this.$set(this.disabled, 5, true);
+					this.$set(this.disabled, 6, true);
+					this.$set(this.disabled, 7, true);
+					this.$set(this.disabled, 8, true);
+					this.$set(this.disabled, 9, true);
+					this.$set(this.disabled, 10, true);
+					this.equipment = [];
+					this.servicesData = [];
+					this.characteristicsData = [];
+					this.valueChangeData = {};
+					this.adapterState = [];
+					this.searchLoad =false;
+					toast('断开蓝牙模块');
+				}
+			});
+		}
+	}
+};
+
+/**
+ * 判断初始化蓝牙状态
+ */
+function initTypes(code, errMsg) {
+	switch (code) {
+		case 10000:
+			toast('未初始化蓝牙适配器');
+			break;
+		case 10001:
+			toast('未检测到蓝牙,请打开蓝牙重试!');
+			break;
+		case 10002:
+			toast('没有找到指定设备');
+			break;
+		case 10003:
+			toast('连接失败');
+			break;
+		case 10004:
+			toast('没有找到指定服务');
+			break;
+		case 10005:
+			toast('没有找到指定特征值');
+			break;
+		case 10006:
+			toast('当前连接已断开');
+			break;
+		case 10007:
+			toast('当前特征值不支持此操作');
+			break;
+		case 10008:
+			toast('其余所有系统上报的异常');
+			break;
+		case 10009:
+			toast('Android 系统特有,系统版本低于 4.3 不支持 BLE');
+			break;
+		default:
+			toast(errMsg);
+	}
+}
+
+/**
+ * 弹出框封装
+ */
+function toast(content, showCancel = false) {
+	uni.showModal({
+		title: '提示',
+		content,
+		showCancel
+	});
+}
+</script>
+
+<style>
+
+.uni-title {
+	/* width: 100%; */
+	/* height: 80rpx; */
+	text-align: center;
+}
+
+.uni-mask {
+	position: fixed;
+	top: 0;
+	left: 0;
+	bottom: 0;
+	display: flex;
+	align-items: center;
+	width: 100%;
+	background: rgba(0, 0, 0, 0.6);
+	padding: 0 30rpx;
+	box-sizing: border-box;
+}
+
+.uni-scroll_box {
+	height: 70%;
+	background: #fff;
+	border-radius: 20rpx;
+}
+.uni-list-box {
+	margin: 0 20rpx;
+	padding: 15rpx 0;
+	border-bottom: 1px #f5f5f5 solid;
+	box-sizing: border-box;
+}
+.uni-list:last-child {
+	border: none;
+}
+.uni-list_name {
+	font-size: 30rpx;
+	color: #333;
+}
+.uni-list_item {
+	font-size: 24rpx;
+	color: #555;
+	line-height: 1.5;
+}
+
+.uni-success_box {
+	position: absolute;
+	left: 0;
+	bottom: 0;
+	min-height: 100rpx;
+	width: 100%;
+	background: #fff;
+	box-sizing: border-box;
+	border-top: 1px #eee solid;
+}
+.uni-success_sub {
+	/* width: 100%%; */
+	height: 100rpx;
+	display: flex;
+	justify-content: space-between;
+	align-items: center;
+	padding: 0 30rpx;
+}
+.uni-close_button {
+	padding: 0 20rpx;
+	height: 60rpx;
+	line-height: 60rpx;
+	background: #ce3c39;
+	color: #ffffff;
+	border-radius: 10rpx;
+}
+.uni-success_content {
+	height: 600rpx;
+	margin: 30rpx;
+	margin-top: 0;
+	border: 1px #eee solid;
+	padding: 30rpx;
+}
+.uni-content_list {
+	padding-bottom: 10rpx;
+	border-bottom: 1px #f5f5f5 solid;
+}
+.uni-tips {
+	text-align: center;
+	font-size: 24rpx;
+	color: #666;
+}
+</style>

+ 86 - 0
pages/API/brightness/brightness.vue

@@ -0,0 +1,86 @@
+<template>
+	<view>
+		<page-head :title="title"></page-head>
+		<view class="uni-padding-wrap uni-common-mt">
+			<!-- #ifndef MP-TOUTIAO -->
+			<view class="text-box">亮度 : {{ screen }}</view>
+			<view class="uni-slider"><slider :value="screen" @changing="sliderChange" step="1" /></view>
+			<!-- #endif -->
+			<button type="primary" @click="keep">
+				{{ keepScreenOn ? '保持常亮状态' : '关闭常亮状态' }}
+			</button>
+			<view class="tips">
+				保持常亮时,屏幕不会熄灭。仅在当前应用生效,离开应用后设置失效。
+			</view>
+		</view>
+	</view>
+</template>
+<script>
+export default {
+	data() {
+		return {
+			title: 'brightness',
+			screen: 0,
+			keepScreenOn: true
+		};
+	},
+	onLoad() {
+		uni.getScreenBrightness({
+			success: res => {
+				this.screen = (res.value * 100).toFixed();
+			},
+			fail(e) {
+				console.log(e);
+			}
+		});
+	},
+	methods: {
+		sliderChange(e) {
+			let screen = e.detail.value;
+			// 判断是否重复
+			if (this.screen !== screen) {
+				console.log('当前数值:' + e.detail.value);
+				uni.setScreenBrightness({
+					value: screen / 100,
+					success: function() {
+					},
+					fail(e) {
+						console.log(e);
+					}
+				});
+				this.screen = screen;
+			}
+		},
+		keep() {
+			uni.setKeepScreenOn({
+				keepScreenOn: this.keepScreenOn
+			});
+			this.keepScreenOn = !this.keepScreenOn;
+		}
+	}
+};
+</script>
+
+<style>
+.text-box {
+	margin-bottom: 40rpx;
+	display: flex;
+	justify-content: center;
+	align-items: center;
+	height: 300rpx;
+	background-color: #ffffff;
+	font-size: 32rpx;
+	color: #353535;
+}
+
+.uni-slider {
+	margin: 100rpx 0;
+}
+
+.tips {
+	font-size: 26rpx;
+	text-align: center;
+	margin-top: 20rpx;
+	color: #999;
+}
+</style>

+ 366 - 0
pages/API/canvas/canvas.vue

@@ -0,0 +1,366 @@
+<template>
+	<view>
+		<page-head :title="title"></page-head>
+		<view class="uni-common-mt">
+			<canvas class="canvas-element" canvas-id="canvas" id="canvas"></canvas>
+			<scroll-view class="canvas-buttons" scroll-y="true">
+				<block v-for="(name, index) in names" :key="index">
+					<button class="canvas-button" @click="handleCanvasButton(name)">{{name}}</button>
+				</block>
+				<button class="canvas-button" @click="toTempFilePath" type="primary">toTempFilePath</button>
+			</scroll-view>
+		</view>
+	</view>
+</template>
+<script>
+	var context = null;
+	export default {
+		data() {
+			return {
+				title: 'createContext',
+				names: ["rotate", "scale", "reset", "translate", "save", "restore", "drawImage", "fillText", "fill",
+					"stroke", "clearRect", "beginPath", "closePath", "moveTo", "lineTo", "rect", "arc",
+					"quadraticCurveTo", "bezierCurveTo", "setFillStyle", "setStrokeStyle", "setGlobalAlpha",
+					"setShadow", "setFontSize", "setLineCap", "setLineJoin", "setLineWidth", "setMiterLimit"
+				]
+			}
+		},
+		onReady: function() {
+			context = uni.createCanvasContext('canvas',this)
+		},
+		methods: {
+			toTempFilePath: function() {
+				uni.canvasToTempFilePath({
+					canvasId: 'canvas',
+					success: function(res) {
+						console.log(res.tempFilePath)
+					},
+					fail: function(err) {
+						console.error(JSON.stringify(err))
+					}
+				})
+			},
+			handleCanvasButton: function(name) {
+				this[name] && this[name]();
+			},
+			rotate: function() {
+				context.beginPath()
+				context.rotate(10 * Math.PI / 180)
+				context.rect(225, 75, 20, 10)
+				context.fill()
+				context.draw()
+			},
+			scale: function() {
+				context.beginPath()
+				context.rect(25, 25, 50, 50)
+				context.stroke()
+
+				context.scale(2, 2)
+
+				context.beginPath()
+				context.rect(25, 25, 50, 50)
+				context.stroke()
+				context.draw()
+			},
+			reset: function() {
+				context.beginPath()
+
+				context.setFillStyle('#000000')
+				context.setStrokeStyle('#000000')
+				context.setFontSize(10)
+				context.setGlobalAlpha(1)
+				context.setShadow(0, 0, 0, 'rgba(0, 0, 0, 0)')
+
+				context.setLineCap('butt')
+				context.setLineJoin('miter')
+				context.setLineWidth(1)
+				context.setMiterLimit(10)
+				context.draw()
+			},
+			translate: function() {
+				context.beginPath()
+				context.rect(10, 10, 100, 50)
+				context.fill()
+
+				context.translate(70, 70)
+
+				context.beginPath()
+				context.fill()
+				context.draw()
+			},
+			save: function() {
+				context.beginPath()
+				context.setStrokeStyle('#00ff00')
+				context.save()
+
+				context.scale(2, 2)
+				context.setStrokeStyle('#ff0000')
+				context.rect(0, 0, 100, 100)
+				context.stroke()
+				context.restore()
+
+				context.rect(0, 0, 50, 50)
+				context.stroke()
+				context.draw()
+			},
+			restore: function() {
+				[3, 2, 1].forEach(function(item) {
+					context.beginPath()
+					context.save()
+					context.scale(item, item)
+					context.rect(10, 10, 100, 100)
+					context.stroke()
+					context.restore()
+				});
+				context.draw()
+			},
+			drawImage: function() {
+				// #ifdef APP-PLUS
+				context.drawImage('../../../static/app-plus/uni@2x.png', 0, 0)
+				// #endif
+				// #ifndef APP-PLUS
+				context.drawImage('../../../static/uni.png', 0, 0)
+				// #endif
+				context.draw()
+			},
+			fillText: function() {
+				context.setStrokeStyle('#ff0000')
+
+				context.beginPath()
+				context.moveTo(0, 10)
+				context.lineTo(300, 10)
+				context.stroke()
+				// context.save()
+				// context.scale(1.5, 1.5)
+				// context.translate(20, 20)
+				context.setFontSize(10)
+				context.fillText('Hello World', 0, 30)
+				context.setFontSize(20)
+				context.fillText('Hello World', 100, 30)
+
+				// context.restore()
+
+				context.beginPath()
+				context.moveTo(0, 30)
+				context.lineTo(300, 30)
+				context.stroke()
+				context.draw()
+			},
+			fill: function() {
+				context.beginPath()
+				context.rect(20, 20, 150, 100)
+				context.setStrokeStyle('#00ff00')
+				context.fill()
+				context.draw()
+			},
+			stroke: function() {
+				context.beginPath()
+				context.moveTo(20, 20)
+				context.lineTo(20, 100)
+				context.lineTo(70, 100)
+				context.setStrokeStyle('#00ff00')
+				context.stroke()
+				context.draw()
+			},
+			clearRect: function() {
+				context.setFillStyle('#ff0000')
+				context.beginPath()
+				context.rect(0, 0, 300, 150)
+				context.fill()
+				context.clearRect(20, 20, 100, 50)
+				context.draw()
+			},
+			beginPath: function() {
+				context.beginPath()
+				context.setLineWidth(5)
+				context.setStrokeStyle('#ff0000')
+				context.moveTo(0, 75)
+				context.lineTo(250, 75)
+				context.stroke()
+				context.beginPath()
+				context.setStrokeStyle('#0000ff')
+				context.moveTo(50, 0)
+				context.lineTo(150, 130)
+				context.stroke()
+				context.draw()
+			},
+			closePath: function() {
+				context.beginPath()
+				context.setLineWidth(1)
+				context.moveTo(20, 20)
+				context.lineTo(20, 100)
+				context.lineTo(70, 100)
+				context.closePath()
+				context.stroke()
+				context.draw()
+			},
+			moveTo: function() {
+				context.beginPath()
+				context.moveTo(0, 0)
+				context.lineTo(300, 150)
+				context.stroke()
+				context.draw()
+			},
+			lineTo: function() {
+				context.beginPath()
+				context.moveTo(20, 20)
+				context.lineTo(20, 100)
+				context.lineTo(70, 100)
+				context.stroke()
+				context.draw()
+			},
+			rect: function() {
+				context.beginPath()
+				context.rect(20, 20, 150, 100)
+				context.stroke()
+				context.draw()
+			},
+			arc: function() {
+				context.beginPath()
+				context.setLineWidth(2)
+				context.arc(75, 75, 50, 0, Math.PI * 2, true)
+				context.moveTo(110, 75)
+				context.arc(75, 75, 35, 0, Math.PI, false)
+				context.moveTo(65, 65)
+				context.arc(60, 65, 5, 0, Math.PI * 2, true)
+				context.moveTo(95, 65)
+				context.arc(90, 65, 5, 0, Math.PI * 2, true)
+				context.stroke()
+				context.draw()
+			},
+			quadraticCurveTo: function() {
+				context.beginPath()
+				context.moveTo(20, 20)
+				context.quadraticCurveTo(20, 100, 200, 20)
+				context.stroke()
+				context.draw()
+			},
+			bezierCurveTo: function() {
+				context.beginPath()
+				context.moveTo(20, 20)
+				context.bezierCurveTo(20, 100, 200, 100, 200, 20)
+				context.stroke()
+				context.draw()
+			},
+			setFillStyle: function() {
+				['#fef957', 'rgb(242,159,63)', 'rgb(242,117,63)', '#e87e51'].forEach(function(item, index) {
+					context.setFillStyle(item)
+					context.beginPath()
+					context.rect(0 + 75 * index, 0, 50, 50)
+					context.fill()
+				})
+				context.draw()
+			},
+			setStrokeStyle: function() {
+				['#fef957', 'rgb(242,159,63)', 'rgb(242,117,63)', '#e87e51'].forEach(function(item, index) {
+					context.setStrokeStyle(item)
+					context.beginPath()
+					context.rect(0 + 75 * index, 0, 50, 50)
+					context.stroke()
+				})
+				context.draw()
+			},
+			setGlobalAlpha: function() {
+				context.setFillStyle('#000000');
+				[1, 0.5, 0.1].forEach(function(item, index) {
+					context.setGlobalAlpha(item)
+					context.beginPath()
+					context.rect(0 + 75 * index, 0, 50, 50)
+					context.fill()
+				})
+				context.draw()
+				context.setGlobalAlpha(1)
+			},
+			setShadow: function() {
+				context.beginPath()
+				context.setShadow(10, 10, 10, 'rgba(0, 0, 0, 199)')
+				context.rect(10, 10, 100, 100)
+				context.fill()
+				context.draw()
+			},
+			setFontSize: function() {
+				[10, 20, 30, 40].forEach(function(item, index) {
+					context.setFontSize(item)
+					context.fillText('Hello, world', 20, 20 + 40 * index)
+				})
+				context.draw()
+			},
+			setLineCap: function() {
+				context.setLineWidth(10);
+				['butt', 'round', 'square'].forEach(function(item, index) {
+					context.beginPath()
+					context.setLineCap(item)
+					context.moveTo(20, 20 + 20 * index)
+					context.lineTo(100, 20 + 20 * index)
+					context.stroke()
+				})
+				context.draw()
+			},
+			setLineJoin: function() {
+				context.setLineWidth(10);
+				['bevel', 'round', 'miter'].forEach(function(item, index) {
+					context.beginPath()
+					context.setLineJoin(item)
+					context.moveTo(20 + 80 * index, 20)
+					context.lineTo(100 + 80 * index, 50)
+					context.lineTo(20 + 80 * index, 100)
+					context.stroke()
+				})
+				context.draw()
+			},
+			setLineWidth: function() {
+				[2, 4, 6, 8, 10].forEach(function(item, index) {
+					context.beginPath()
+					context.setLineWidth(item)
+					context.moveTo(20, 20 + 20 * index)
+					context.lineTo(100, 20 + 20 * index)
+					context.stroke()
+				})
+				context.draw()
+			},
+			setMiterLimit: function() {
+				context.setLineWidth(4);
+				[2, 4, 6, 8, 10].forEach(function(item, index) {
+					context.beginPath()
+					context.setMiterLimit(item)
+					context.moveTo(20 + 80 * index, 20)
+					context.lineTo(100 + 80 * index, 50)
+					context.lineTo(20 + 80 * index, 100)
+					context.stroke()
+				})
+				context.draw()
+			}
+		}
+	}
+</script>
+
+<style>
+	.canvas-element-wrapper {
+		display: block;
+		margin-bottom: 100rpx;
+	}
+
+	.canvas-element {
+		width: 100%;
+		height: 500rpx;
+		background-color: #ffffff;
+	}
+
+	.canvas-buttons {
+		padding: 30rpx 50rpx 10rpx;
+		width: 100%;
+		height: 360rpx;
+		box-sizing: border-box;
+	}
+
+	.canvas-button {
+		float: left;
+		display: inline-flex;
+		align-items: center;
+		justify-content: center;
+		height: 40px;
+		line-height: 1;
+		width: 300rpx;
+		margin: 15rpx 12rpx;
+	}
+</style>

+ 62 - 0
pages/API/choose-location/choose-location.vue

@@ -0,0 +1,62 @@
+<template>
+	<view>
+		<page-head :title="title"></page-head>
+		<view class="uni-padding-wrap">
+			<view style="background:#FFFFFF; padding:40rpx;">
+				<view class="uni-hello-text uni-center">当前位置信息</view>
+				<block v-if="hasLocation === false">
+					<view class="uni-h2 uni-center uni-common-mt">未选择位置</view>
+				</block>
+				<block v-if="hasLocation === true">
+					<view class="uni-hello-text uni-center" style="margin-top:10px;">
+						{{locationAddress}}
+					</view>
+					<view class="uni-h2 uni-center uni-common-mt">
+						<text>E: {{location.longitude[0]}}°{{location.longitude[1]}}′</text>
+						<text>\nN: {{location.latitude[0]}}°{{location.latitude[1]}}′</text>
+					</view>
+				</block>
+			</view>
+			<view class="uni-btn-v">
+				<button type="primary" @tap="chooseLocation">选择位置</button>
+				<button @tap="clear">清空</button>
+			</view>
+		</view>
+	</view>
+</template>
+<script>
+	import * as util from '../../../common/util.js'
+	var formatLocation = util.formatLocation;
+
+	export default {
+		data() {
+			return {
+				title: 'chooseLocation',
+				hasLocation: false,
+				location: {},
+				locationAddress: ''
+			}
+		},
+		methods: {
+			chooseLocation: function () {
+				uni.chooseLocation({
+					success: (res) => {
+						this.hasLocation = true,
+							this.location = formatLocation(res.longitude, res.latitude),
+							this.locationAddress = res.address
+					}
+				})
+			},
+			clear: function () {
+				this.hasLocation = false
+			}
+		}
+	}
+</script>
+
+<style>
+	.page-body-info {
+		padding-bottom: 0;
+		height: 440rpx;
+	}
+</style>

+ 91 - 0
pages/API/clipboard/clipboard.vue

@@ -0,0 +1,91 @@
+<template>
+	<view>
+		<page-head :title="title"></page-head>
+		<view class="uni-padding-wrap">
+			<view class="uni-title">请输入剪贴板内容</view>
+			<view class="uni-list">
+				<view class="uni-list-cell">
+					<input class="uni-input" type="text" placeholder="请输入剪贴板内容" :value="data" @input="dataChange"/>
+				</view>
+			</view>
+			<view class="uni-btn-v">
+				<button type="primary" @click="setClipboard">存储数据</button>
+				<button @tap="getClipboard">读取数据</button>
+			</view>
+		</view>
+	</view>
+</template>
+<script>
+	export default {
+		data() {
+			return {
+				title: 'get/setClipboardData',
+				data: ''
+			}
+		},
+		methods: {
+			dataChange: function (e) {
+				this.data = e.detail.value
+			},
+			getClipboard: function () {
+				uni.getClipboardData({
+					success: (res) => {
+						console.log(res.data);
+						const content = res.data ? '剪贴板内容为:' + res.data : '剪贴板暂无内容';
+						uni.showModal({
+							content,
+							title: '读取剪贴板',
+							showCancel: false
+						})
+					},
+					fail: () => {
+						uni.showModal({
+							content: '读取剪贴板失败!',
+							showCancel: false
+						})
+					}
+				});
+			},
+			setClipboard: function () {
+				var data = this.data;
+				if (data.length === 0) {
+					uni.showModal({
+						title: '设置剪贴板失败',
+						content: '内容不能为空',
+						showCancel: false
+					})
+				} else {
+					uni.setClipboardData({
+						data: data,
+						success: () => {
+							// 成功处理
+							// #ifdef MP-ALIPAY || MP-BAIDU || MP-TOUTIAO
+							uni.showToast({
+								title: '设置剪贴板成功',
+								icon: "success",
+								mask: !1
+							})
+							// #endif
+						},
+						fail: () => {
+							// 失败处理
+							// #ifdef MP-ALIPAY || MP-BAIDU || MP-TOUTIAO
+							uni.showToast({
+								title: '储存数据失败!',
+								icon: "none",
+								mask: !1
+							})
+							// #endif
+						}
+					});
+				}
+			}
+		}
+	}
+</script>
+
+<style>
+	
+</style>
+
+

+ 63 - 0
pages/API/download-file/download-file.vue

@@ -0,0 +1,63 @@
+<template>
+	<view>
+		<page-head :title="title"></page-head>
+		<view class="uni-padding-wrap uni-common-mt">
+			<view v-if="imageSrc" class="image-container">
+				<image class="img" :src="imageSrc" mode="center" />
+			</view>
+			<block v-else style="margin-top: 50px;">
+				<view class="uni-hello-text">
+					点击按钮下载服务端示例图片(下载网络文件到本地临时目录)
+				</view>
+				<view class="uni-btn-v">
+					<button type="primary" @tap="downloadImage">下载</button>
+				</view>
+			</block>
+		</view>
+	</view>
+</template>
+<script>
+	export default {
+		data() {
+			return {
+				title: 'downloadFile',
+				imageSrc: ''
+			}
+		},
+		onUnload() {
+			this.imageSrc = '';
+		},
+		methods: {
+			downloadImage: function () {
+				uni.showLoading({
+					title:'下载中'
+				})
+				var self = this
+				uni.downloadFile({
+					url: "https://img-cdn-qiniu.dcloud.net.cn/uniapp/images/uni@2x.png",
+					success: (res) => {
+						console.log('downloadFile success, res is', res)
+						self.imageSrc = res.tempFilePath;
+						uni.hideLoading();
+					},
+					fail: (err) => {
+						console.log('downloadFile fail, err is:', err)
+					}
+				})
+			}
+		}
+	}
+</script>
+
+<style>
+.img {
+	width: 500rpx;
+	height: 500rpx;
+	margin: 0 auto;
+}
+.image-container {
+	display: flex;
+	justify-content: center;
+	align-items: center;
+}
+</style>

+ 129 - 0
pages/API/file/file.vue

@@ -0,0 +1,129 @@
+<template>
+	<view>
+		<page-head :title="title"></page-head>
+		<view class="uni-padding-wrap uni-common-mt">
+			<block v-if="tempFilePath">
+				<image :src="tempFilePath" class="image" mode="aspectFit"></image>
+			</block>
+			<block v-if="!tempFilePath && savedFilePath">
+				<image :src="savedFilePath" class="image" mode="aspectFit"></image>
+			</block>
+			<block v-if="!tempFilePath && !savedFilePath">
+				<view class="uni-hello-addfile" @click="chooseImage">+ 请选择文件</view>
+			</block>
+			<view class="uni-btn-v">
+				<button class="btn-savefile" @click="saveFile">保存文件</button>
+				<button @click="clear">删除文件</button>
+			</view>
+			<!-- #ifndef MP-ALIPAY || MP-TOUTIAO -->
+			<view class="btn-area">
+				<button @click="openDocument">打开pdf文件</button>
+			</view>
+			<!-- #endif -->
+		</view>
+	</view>
+</template>
+<script>
+	export default {
+		data() {
+			return {
+				title: 'saveFile',
+				tempFilePath: '',
+				savedFilePath: ''
+			}
+		},
+		onLoad() {
+			this.savedFilePath = uni.getStorageSync('savedFilePath');
+		},
+		methods: {
+			chooseImage() {
+				uni.chooseImage({
+					count: 1,
+					success: (res) => {
+						this.tempFilePath = res.tempFilePaths[0];
+					},
+					fail: (err) => {
+						// #ifdef MP
+						uni.getSetting({
+							success: (res) => {
+								let authStatus = res.authSetting['scope.album'] && res.authSetting['scope.camera'];
+								if (!authStatus) {
+									uni.showModal({
+										title: '授权失败',
+										content: 'Hello uni-app需要从您的相机或相册获取图片,请在设置界面打开相关权限',
+										success: (res) => {
+											if (res.confirm) {
+												uni.openSetting()
+											}
+										}
+									})
+								}
+							}
+						})
+						// #endif
+					}
+				});
+			},
+			saveFile() {
+				if (this.tempFilePath.length > 0) {
+					uni.saveFile({
+						tempFilePath: this.tempFilePath,
+						success: (res) => {
+							this.savedFilePath = res.savedFilePath;
+							uni.setStorageSync('savedFilePath', res.savedFilePath);
+							uni.showModal({
+								title: '保存成功',
+								content: '下次进入页面时,此文件仍可用',
+								showCancel: false
+							});
+						},
+						fail: (res) => {
+							uni.showModal({
+								title: '保存失败',
+								content: '失败原因: ' + JSON.stringify(res),
+								showCancel: false
+							});
+						}
+					})
+				} else {
+					uni.showModal({
+						content: '请选择文件',
+						showCancel: false
+					});
+				}
+			},
+			clear() {
+				uni.setStorageSync('savedFilePath', '');
+				this.tempFilePath = '';
+				this.savedFilePath = '';
+			},
+			// #ifndef MP-ALIPAY || MP-TOUTIAO
+			openDocument() {
+				uni.downloadFile({
+					url: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/b3f1d0b0-5168-11eb-bd01-97bc1429a9ff.pdf',
+					success: (res) => {
+						uni.openDocument({
+							filePath: res.tempFilePath,
+							success: () => {
+								console.log('打开文档成功');
+							}
+						});
+					}
+				});
+			},
+			// #endif
+		}
+	}
+</script>
+
+<style>
+	.image {
+		width: 100%;
+		height: 360rpx;
+	}
+
+	.btn-savefile {
+		background-color: #007aff;
+		color: #ffffff;
+	}
+</style>

+ 84 - 0
pages/API/full-screen-video-ad/full-screen-video-ad.vue

@@ -0,0 +1,84 @@
+<template>
+	<view>
+		<page-head :title="title"></page-head>
+		<view class="uni-padding-wrap uni-common-mt">
+			<button :loading="loading" :disabled="loading" type="primary" class="btn" @click="showAd">显示广告</button>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				title: '全屏视频广告',
+				loading: false,
+				loadError: false
+			}
+		},
+		onReady() {
+			// #ifdef APP-PLUS
+			this.adOption = {
+				adpid: '1507000611'
+			};
+			// #endif
+			this.createAd();
+		},
+		methods: {
+			createAd() {
+				var _ad = this._ad = uni.createFullScreenVideoAd(this.adOption);
+				_ad.onLoad(() => {
+					this.loading = false;
+					this.loadError = false;
+					_ad.show();
+					console.log('onLoad event')
+				});
+				_ad.onClose((res) => {
+					// 用户点击了【关闭广告】按钮
+					if (res && res.isEnded) {
+						// 正常播放结束
+						console.log("onClose " + res.isEnded);
+					} else {
+						// 播放中途退出
+						console.log("onClose " + res.isEnded);
+					}
+
+					setTimeout(() => {
+						uni.showToast({
+							title: "全屏视频" + (res.isEnded ? "成功" : "未") + "播放完毕",
+							duration: 10000,
+							position: 'bottom'
+						})
+					}, 500)
+				});
+				_ad.onError((err) => {
+					this.loading = false;
+					if (err.code) {
+						this.loadError = true;
+					}
+					console.log('onError event', err)
+					uni.showToast({
+						title: err.errMsg,
+						position: 'bottom'
+					})
+				});
+			},
+			showAd() {
+				this.loading = true;
+				this._ad.load();
+			}
+		}
+	}
+</script>
+
+<style>
+	.btn {
+		margin-bottom: 20px;
+	}
+
+	.ad-tips {
+		color: #999;
+		padding: 30px 0;
+		text-align: center;
+	}
+</style>

+ 186 - 0
pages/API/get-location/get-location.vue

@@ -0,0 +1,186 @@
+<template>
+    <view>
+        <page-head :title="title"></page-head>
+        <view class="uni-padding-wrap">
+            <view style="background:#FFFFFF; padding:40rpx;">
+                <view class="uni-hello-text uni-center">当前位置经纬度</view>
+                <block v-if="hasLocation === false">
+                    <view class="uni-h2 uni-center uni-common-mt">未获取</view>
+                </block>
+                <block v-if="hasLocation === true">
+                    <view class="uni-h2 uni-center uni-common-mt">
+                        <text>E: {{location.longitude[0]}}°{{location.longitude[1]}}′</text>
+                        <text>\nN: {{location.latitude[0]}}°{{location.latitude[1]}}′</text>
+                    </view>
+                </block>
+            </view>
+            <view class="uni-btn-v">
+                <button type="primary" @tap="getLocation">获取位置</button>
+                <button @tap="clear">清空</button>
+            </view>
+        </view>
+        <uni-popup :show="type === 'showpopup'" mode="fixed" @hidePopup="togglePopup('')">
+            <view class="popup-view">
+                <text class="popup-title">需要用户授权位置权限</text>
+                <view class="uni-flex popup-buttons">
+                    <button class="uni-flex-item" type="primary" open-type="openSetting" @tap="openSetting">设置</button>
+                    <button class="uni-flex-item" @tap="togglePopup('')">取消</button>
+                </view>
+            </view>
+        </uni-popup>
+    </view>
+</template>
+<script>
+    import * as util from '../../../common/util.js'
+    var formatLocation = util.formatLocation;
+    // #ifdef APP-PLUS
+    import permision from "@/common/permission.js"
+    // #endif
+
+    export default {
+        data() {
+            return {
+                title: 'getLocation',
+                hasLocation: false,
+                location: {},
+                type: ''
+            }
+        },
+        methods: {
+            togglePopup(type) {
+                this.type = type;
+            },
+            showConfirm() {
+                this.type = 'showpopup';
+            },
+            hideConfirm() {
+                this.type = '';
+            },
+            async getLocation() {
+                // #ifdef APP-PLUS
+                let status = await this.checkPermission();
+                if (status !== 1) {
+                    return;
+                }
+                // #endif
+                // #ifdef MP-WEIXIN || MP-TOUTIAO || MP-QQ
+                let status = await this.getSetting();
+                if (status === 2) {
+                    this.showConfirm();
+                    return;
+                }
+                // #endif
+
+                this.doGetLocation();
+            },
+            doGetLocation() {
+                uni.getLocation({
+                    success: (res) => {
+                        this.hasLocation = true;
+                        this.location = formatLocation(res.longitude, res.latitude);
+                    },
+                    fail: (err) => {
+                        // #ifdef MP-BAIDU
+                        if (err.errCode === 202 || err.errCode === 10003) { // 202模拟器 10003真机 user deny
+                            this.showConfirm();
+                        }
+                        // #endif
+                        // #ifndef MP-BAIDU
+                        if (err.errMsg.indexOf("auth deny") >= 0) {
+                            uni.showToast({
+                                title: "访问位置被拒绝"
+                            })
+                        } else {
+                            uni.showToast({
+                                title: err.errMsg
+                            })
+                        }
+                        // #endif
+                    }
+                })
+            },
+            getSetting: function() {
+                return new Promise((resolve, reject) => {
+                    uni.getSetting({
+                        success: (res) => {
+                            if (res.authSetting['scope.userLocation'] === undefined) {
+                                resolve(0);
+                                return;
+                            }
+                            if (res.authSetting['scope.userLocation']) {
+                                resolve(1);
+                            } else {
+                                resolve(2);
+                            }
+                        }
+                    });
+                });
+            },
+            openSetting: function() {
+                this.hideConfirm();
+                uni.openSetting({
+                    success: (res) => {
+                        if (res.authSetting && res.authSetting['scope.userLocation']) {
+                            this.doGetLocation();
+                        }
+                    },
+                    fail: (err) => {}
+                })
+            },
+            async checkPermission() {
+                let status = permision.isIOS ? await permision.requestIOS('location') :
+                    await permision.requestAndroid('android.permission.ACCESS_FINE_LOCATION');
+
+                if (status === null || status === 1) {
+                    status = 1;
+                } else if (status === 2) {
+                    uni.showModal({
+                        content: "系统定位已关闭",
+                        confirmText: "确定",
+                        showCancel: false,
+                        success: function(res) {
+                        }
+                    })
+                } else if (status.code) {
+                    uni.showModal({
+                        content: status.message
+                    })
+                } else {
+                    uni.showModal({
+                        content: "需要定位权限",
+                        confirmText: "设置",
+                        success: function(res) {
+                            if (res.confirm) {
+                                permision.gotoAppSetting();
+                            }
+                        }
+                    })
+                }
+
+                return status;
+            },
+            clear: function() {
+                this.hasLocation = false
+            }
+        }
+    }
+</script>
+
+<style>
+    .popup-view {
+        width: 500rpx;
+    }
+
+    .popup-title {
+        display: block;
+        font-size: 16px;
+        line-height: 3;
+        margin-bottom: 10px;
+        text-align: center;
+    }
+
+    .popup-buttons button {
+        margin-left: 4px;
+        margin-right: 4px;
+    }
+</style>

+ 86 - 0
pages/API/get-network-type/get-network-type.vue

@@ -0,0 +1,86 @@
+<template>
+	<view>
+		<page-head :title="title"></page-head>
+		<view class="uni-padding-wrap uni-common-mt">
+			<view style="background:#FFFFFF; padding:40rpx;">
+				<view class="uni-hello-text uni-center">网络状态</view>
+				<block v-if="hasNetworkType === false">
+					<view class="uni-h2 uni-center uni-common-mt">未获取</view>
+					<view class="uni-hello-text uni-center uni-common-mt">请点击下面按钮获取网络状态</view>
+				</block>
+				<block v-if="hasNetworkType === true">
+					<view class="uni-h2 uni-center uni-common-mt">{{networkType}}</view>
+				</block>
+				<view v-if="hasNetworkType === true && networkType === 'wifi'" class="uni-textarea uni-common-mt">
+					<textarea :value="connectedWifi"></textarea>
+				</view>
+			</view>
+			<view class="uni-btn-v uni-common-mt">
+				<button type="primary"  @tap="getNetworkType">获取设备网络状态</button>
+				<!-- #ifdef MP-WEIXIN  || MP-JD-->
+				<button v-if="hasNetworkType === true && networkType === 'wifi'" class="uni-common-mt" type="primary" @tap="getConnectedWifi">获取 wifi 信息</button>
+				<!-- #endif -->
+				<button class="uni-common-mt" @tap="clear">清空</button>
+			</view>
+		</view>
+	</view>
+</template>
+<script>
+	export default {
+		data() {
+			return {
+				title: 'getNetworkType',
+				hasNetworkType: false,
+				networkType: '',
+				connectedWifi: ''
+			}
+		},
+		onUnload:function(){
+			this.networkType = '',this.hasNetworkType = false;
+		},
+		methods: {
+			getNetworkType: function () {
+				uni.getNetworkType({
+					success: (res) => {
+						console.log(res)
+						this.hasNetworkType = true, this.networkType = res.subtype || res.networkType
+					},
+                    fail: () => {
+                        uni.showModal({
+                        	content:'获取失败!',
+                            showCancel:false
+                        })
+                    }
+				})
+			},
+			clear: function () {
+				this.hasNetworkType = false,
+				this.networkType = '',
+				this.connectedWifi = ''
+			},
+			// #ifdef MP-WEIXIN || MP-JD
+			getConnectedWifi() {
+				const that = this
+				uni.startWifi({
+					success: function() {
+						uni.getConnectedWifi({
+							success: function(res) {
+								const { wifi } = res
+								that.connectedWifi = JSON.stringify(wifi)
+							},
+							fail: function(res) {
+							}
+						})
+					},
+					fail: function(res) {
+					}
+				})
+			}
+			// #endif
+		}
+	}
+</script>
+
+<style>
+
+</style>

+ 117 - 0
pages/API/get-node-info/get-node-info.vue

@@ -0,0 +1,117 @@
+<template>
+	<view>
+		<page-head :title="title"></page-head>
+		<view class="uni-padding-wrap uni-common-mt">
+			<view class="movable block">
+				<!-- #ifndef MP-TOUTIAO -->
+				<movable-area>
+					<movable-view class="target" direction="all" @change="getNodeInfo">Drag</movable-view>
+				</movable-area>
+				<!-- #endif -->
+				<!-- #ifdef MP-TOUTIAO -->
+				<view class="target" @click="setPosition" :style="{top:top,left:left}">Click</view>
+				<!-- #endif -->
+			</view>
+			<view class="movable">
+				<view class="info">
+					<view v-for="(item,index) in info" :key="index">
+						<text class="b">{{item.key}}</text>
+						<text class="span">{{item.val}}</text>
+					</view>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				title: 'createSelectorQuery',
+				top: 0,
+				left: '0px',
+				info: []
+			}
+		},
+		onReady() {
+			this.getNodeInfo()
+		},
+		methods: {
+			setPosition() {
+				this.left = Math.random() * uni.upx2px(320) + 'px'
+				this.top = Math.random() * uni.upx2px(320) + 'px'
+				this.getNodeInfo()
+			},
+			getNodeInfo() {
+				uni.createSelectorQuery().select('.target').boundingClientRect().exec((ret) => {
+					const rect = ret[0]
+					if (rect) {
+                        const sort = ['left','right','top','bottom','width','height']
+						const info = []
+						for (let i in sort) {
+                            let key = sort[i]
+                            info.push({
+                                'key': key,
+                                'val': rect[key]
+                            })
+						}
+						this.info = info
+					}
+				});
+			}
+		},
+	}
+</script>
+
+<style>
+	.movable {
+		display: flex;
+		justify-content: center;
+	}
+
+	.block {
+		height: 400rpx;
+		width: 400rpx;
+		background-color: #FFFFFF;
+		border: 1px solid #ccc;
+		position: relative;
+		margin: 0 auto;
+		margin-bottom: 30rpx;
+	}
+
+	movable-area {
+		height: 400rpx;
+		width: 400rpx;
+		position: relative;
+	}
+
+	.target {
+		height: 80rpx;
+		width: 80rpx;
+		line-height: 80rpx;
+		text-align: center;
+		color: #FFFFFF;
+		background-color: #4cd964;
+		font-size: 28rpx;
+		position: absolute;
+	}
+
+	.info {
+		display: flex;
+		flex-direction: column;
+		justify-content: center;
+		align-items: center;
+	}
+
+	.b {
+		font-weight: bold;
+		width: 150rpx;
+		display: inline-block;
+	}
+
+	.span {
+		width: 100rpx;
+		display: inline-block;
+	}
+</style>

+ 148 - 0
pages/API/get-system-info/get-system-info.vue

@@ -0,0 +1,148 @@
+<template>
+	<view>
+		<page-head :title="title"></page-head>
+		<view class="uni-common-mt">
+			<view class="uni-list">
+				<view class="uni-list-cell">
+					<view class="uni-pd">
+						<view class="uni-label" style="width:180px;">设备型号</view>
+					</view>
+					<view class="uni-list-cell-db">
+						<input class="uni-input" type="text" :disabled="true" placeholder="未获取" :value="systemInfo.model"/>
+					</view>
+				</view>
+				<view class="uni-list-cell">
+					<view class="uni-pd">
+						<view class="uni-label" style="width:180px;">客户端平台</view>
+					</view>
+					<view class="uni-list-cell-db">
+						<input class="uni-input" type="text" :disabled="true" placeholder="未获取" :value="systemInfo.platform"/>
+					</view>
+				</view>
+				<view class="uni-list-cell">
+					<view class="uni-pd">
+						<view class="uni-label" style="width:180px;">操作系统版本</view>
+					</view>
+					<view class="uni-list-cell-db">
+						<input class="uni-input" type="text" :disabled="true" placeholder="未获取" :value="systemInfo.system"/>
+					</view>
+				</view>
+				<view class="uni-list-cell">
+					<view class="uni-pd">
+						<view class="uni-label" style="width:180px;">语言</view>
+					</view>
+					<view class="uni-list-cell-db">
+						<input class="uni-input" type="text" :disabled="true" placeholder="未获取" :value="systemInfo.language"/>
+					</view>
+				</view>
+				<view class="uni-list-cell">
+					<view class="uni-pd">
+						<view class="uni-label" style="width:180px;">版本</view>
+					</view>
+					<view class="uni-list-cell-db">
+						<input class="uni-input" type="text" :disabled="true" placeholder="未获取" :value="systemInfo.version"/>
+					</view>
+				</view>
+				<view class="uni-list-cell">
+					<view class="uni-pd">
+						<view class="uni-label" style="width:180px;">屏幕宽度</view>
+					</view>
+					<view class="uni-list-cell-db">
+						<input class="uni-input" type="text" :disabled="true" placeholder="未获取" :value="systemInfo.screenWidth"/>
+					</view>
+				</view>
+				<view class="uni-list-cell">
+					<view class="uni-pd">
+						<view class="uni-label" style="width:180px;">屏幕高度</view>
+					</view>
+					<view class="uni-list-cell-db">
+						<input class="uni-input" type="text" :disabled="true" placeholder="未获取" :value="systemInfo.screenHeight"/>
+					</view>
+				</view>
+				<view class="uni-list-cell">
+					<view class="uni-pd">
+						<view class="uni-label" style="width:180px;">可使用窗口高度</view>
+					</view>
+					<view class="uni-list-cell-db">
+						<input class="uni-input" type="text" :disabled="true" placeholder="未获取" :value="systemInfo.windowHeight"/>
+					</view>
+				</view>
+				<view class="uni-list-cell">
+					<view class="uni-pd">
+						<view class="uni-label" style="width:180px;">可使用窗口的顶部位置</view>
+					</view>
+					<view class="uni-list-cell-db">
+						<input class="uni-input" type="text" :disabled="true" placeholder="未获取" :value="systemInfo.windowTop"/>
+					</view>
+				</view>
+				<view class="uni-list-cell">
+					<view class="uni-pd">
+						<view class="uni-label" style="width:180px;">可使用窗口的底部位置</view>
+					</view>
+					<view class="uni-list-cell-db">
+						<input class="uni-input" type="text" :disabled="true" placeholder="未获取" :value="systemInfo.windowBottom"/>
+					</view>
+				</view>
+				<view class="uni-list-cell">
+					<view class="uni-pd">
+						<view class="uni-label" style="width:180px;">状态栏的高度</view>
+					</view>
+					<view class="uni-list-cell-db">
+						<input class="uni-input" type="text" :disabled="true" placeholder="未获取" :value="systemInfo.statusBarHeight"/>
+					</view>
+				</view>
+				<view class="uni-list-cell">
+					<view class="uni-pd">
+						<view class="uni-label" style="width:180px;">DPI</view>
+					</view>
+					<view class="uni-list-cell-db">
+						<input class="uni-input" type="text" :disabled="true" placeholder="未获取" :value="systemInfo.pixelRatio"/>
+					</view>
+				</view>
+				<!-- #ifdef MP -->
+				<view class="uni-list-cell">
+					<view class="uni-pd">
+						<view class="uni-label" style="width:180px;">基础库版本</view>
+					</view>
+					<view class="uni-list-cell-db">
+						<input class="uni-input" type="text" :disabled="true" placeholder="未获取" :value="systemInfo.SDKVersion"/>
+					</view>
+				</view>
+				<!-- #endif -->
+			</view>
+			<view class="uni-padding-wrap">
+				<view class="uni-btn-v">
+					<button type="primary" @tap="getSystemInfo">获取设备系统信息</button>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+<script>
+	export default {
+		data() {
+			return {
+				title: 'getSystemInfo',
+				systemInfo: {}
+			}
+		},
+		onUnload:function(){
+			this.systemInfo = {};
+		},
+		methods: {
+			getSystemInfo: function () {
+				uni.getSystemInfo({
+					success: (res) => {
+						this.systemInfo = res
+					}
+				})
+			}
+		}
+	}
+</script>
+
+<style>
+	.uni-pd {
+		padding-left: 30rpx;
+	}
+</style>

+ 165 - 0
pages/API/get-user-info/get-user-info.vue

@@ -0,0 +1,165 @@
+<template>
+	<view>
+		<page-head :title="title"></page-head>
+		<view class="uni-padding-wrap">
+			<view style="background:#FFF; padding:40rpx;">
+				<block v-if="hasUserInfo === false">
+					<view class="uni-hello-text uni-center">
+						<text>请点击下方按钮获取用户头像及昵称或手机号</text>
+					</view>
+				</block>
+				<block v-if="hasUserInfo === true">
+					<view class="uni-h4 uni-center uni-common-mt">{{userInfo.nickName || userInfo.nickname || userInfo.gender || userInfo.email || userInfo.phoneNumber}}</view>
+					<view v-if="userInfo.avatarUrl || userInfo.avatar_url " style="padding:30rpx 0; text-align:center;">
+						<image class="userinfo-avatar" :src="userInfo.avatarUrl||userInfo.avatar_url"></image>
+					</view>
+				</block>
+			</view>
+			<view class="uni-btn-v">
+				<!-- #ifdef APP-PLUS || MP-ALIPAY || MP-TOUTIAO -->
+				<button type="primary" :loading="btnLoading" @click="getUserInfo">获取用户信息</button>
+				<!-- #endif -->
+				<!-- #ifdef MP-WEIXIN || MP-BAIDU || MP-QQ  || MP-JD -->
+				<button type="primary" open-type="getUserInfo" @getuserinfo="mpGetUserInfo">获取用户信息</button>
+				<!-- #endif -->
+				<button @click="clear">清空</button>
+			</view>
+		</view>
+	</view>
+</template>
+<script>
+	import {
+		mapState,
+		mapMutations,
+		mapActions
+	} from 'vuex'
+
+	export default {
+		data() {
+			return {
+				title: 'getUserInfo',
+				hasUserInfo: false,
+				userInfo: {},
+				btnLoading: false
+			}
+		},
+		computed: {
+			...mapState([
+				'loginProvider',
+				'isUniverifyLogin'
+			])
+		},
+		methods: {
+			...mapActions(['getPhoneNumber']),
+			// 获取用户信息 API 在小程序可直接使用,在 5+App 里面需要先登录才能调用
+			getUserInfo() {
+				this.btnLoading = true;
+				if (this.isUniverifyLogin) {
+					// 一键登录
+					this.getPhoneNumber(uni.getStorageSync('univerifyInfo')).then(phoneNumber => {
+						this.hasUserInfo = true;
+						this.userInfo = {
+							phoneNumber
+						};
+					}).catch(err => {
+						console.error('getUserInfo fail:', err);
+						this.hasUserInfo = false;
+					}).finally(() => {
+						this.btnLoading = false;
+					})
+					return;
+				}
+
+				if(this.loginProvider === 'apple'){
+					const nickname = uni.getStorageSync('apple_nickname')
+					if(nickname){
+						this.hasUserInfo = true;
+						this.userInfo = { nickName:nickname }
+						this.btnLoading = false;
+						return;
+					}
+				}
+
+				uni.getUserInfo({
+					provider: this.loginProvider,
+					success: (result) => {
+						this.hasUserInfo = true;
+						this.userInfo = result.userInfo;
+					},
+					fail: (error) => {
+						console.log('getUserInfo fail', error);
+						let content = error.errMsg;
+						if (~content.indexOf('uni.login')) {
+							content = '请在登录页面完成登录操作';
+						}
+						// #ifndef APP-PLUS
+						uni.getSetting({
+							success: (res) => {
+								let authStatus = res.authSetting['scope.userInfo'];
+								if (!authStatus) {
+									uni.showModal({
+										title: '授权失败',
+										content: 'Hello uni-app需要获取您的用户信息,请在设置界面打开相关权限',
+										success: (res) => {
+											if (res.confirm) {
+												uni.openSetting()
+											}
+										}
+									})
+								} else {
+									uni.showModal({
+										title: '获取用户信息失败',
+										content: '错误原因' + content,
+										showCancel: false
+									});
+								}
+							}
+						})
+						// #endif
+						// #ifdef APP-PLUS
+						uni.showModal({
+							title: '获取用户信息失败',
+							content: '错误原因' + content,
+							showCancel: false
+						});
+						// #endif
+					},
+					complete: () => {
+						this.btnLoading = false;
+					}
+				});
+			},
+			mpGetUserInfo(result) {
+				console.log('mpGetUserInfo', result);
+				if (result.detail.errMsg !== 'getUserInfo:ok') {
+					uni.showModal({
+						title: '获取用户信息失败',
+						content: '错误原因' + result.detail.errMsg,
+						showCancel: false
+					});
+					return;
+				}
+				this.hasUserInfo = true;
+				if(result.detail && result.detail.userInfo){
+					this.userInfo = result.detail.userInfo;
+				}else{
+					// #ifdef MP-JD
+					this.userInfo = result.detail.user_info;
+					// #endif
+				}
+			},
+			clear() {
+				this.hasUserInfo = false;
+				this.userInfo = {};
+			}
+		}
+	}
+</script>
+
+<style>
+	.userinfo-avatar {
+		border-radius: 128rpx;
+		width: 128rpx;
+		height: 128rpx;
+	}
+</style>

+ 300 - 0
pages/API/ibeacon/ibeacon.vue

@@ -0,0 +1,300 @@
+<template>
+	<view>
+		<page-head :title="title"></page-head>
+		<view class="uni-padding-wrap uni-common-mt">
+			<view class="uni-btn-v">
+				<button type="primary" :disabled="isOpen" @click="openBluetoothAdapter">打开蓝牙模块</button>
+				<button type="primary" :disabled="!isOpen" @click="closeBluetoothAdapter">关闭蓝牙模块</button>
+				<button type="primary" :disabled="!isOpen || isStarted" :loading="isStarted" @click="startBeaconDiscovery">开始搜索附近的iBeacon设备</button>
+				<button type="primary" :disabled="!isStarted" @click="stopBeaconDiscovery">停止搜索附近的iBeacon设备</button>
+			</view>
+		</view>
+		<scroll-view class="uni-scroll_box">
+			<view class="uni-title" v-if="isStarted || deviceList.length > 0">已经发现 {{ deviceList.length }} 台设备:</view>
+			<view class="uni-list-box" v-for="(item, index) in deviceList" :key="item.uuid">
+				<view>
+					<view class="uni-list_name">UUID: {{ item.uuid }}</view>
+					<view class="uni-list_item">major: {{ item.major }}</view>
+					<view class="uni-list_item">minor: {{ item.minor }}</view>
+					<view class="uni-list_item">rssi: {{ item.rssi }} dBm</view>
+					<view class="uni-list_item">accuracy: {{ item.accuracy }}</view>
+					<view class="uni-list_item">heading: {{ item.heading }}</view>
+				</view>
+			</view>
+		</scroll-view>
+	</view>
+</template>
+<script>
+	const DEVICE_UUID_WEICHAT = 'FDA50693-A4E2-4FB1-AFCF-C6EB07647825';
+	export default {
+		data() {
+			return {
+				title: 'iBeacon',
+				isOpen: false,
+				isStarted: false,
+				deviceList: [],
+				isPageOpened: false
+			};
+		},
+		onLoad() {
+			this.onBeaconUpdate();
+		},
+		onShow() {
+			this.isPageOpened = true;
+		},
+		methods: {
+			maskclose() {
+				this.maskShow = false;
+			},
+			openBluetoothAdapter() {
+				uni.openBluetoothAdapter({
+					success: (res) => {
+						console.log('初始化蓝牙成功:' + res.errMsg);
+						console.log(res);
+						this.isOpen = true;
+						this.deviceList = [];
+					},
+					fail: (err) => {
+						console.log('初始化蓝牙失败,错误码:' + (err.errCode || err.errMsg));
+						if (err.errCode !== 0) {
+							initTypes(err.errCode, err.errMsg);
+						}
+					}
+				});
+			},
+			closeBluetoothAdapter(OBJECT) {
+				this.stopBeaconDiscovery();
+				wx.closeBluetoothAdapter({
+					success: (res) => {
+						this.isOpen = false;
+						console.log('断开蓝牙模块成功');
+					}
+				});
+			},
+			onBeaconUpdate() {
+				uni.onBeaconUpdate(res => {
+					if (!this.isPageOpened || !this.isOpen || !this.isStarted) {
+						return;
+					}
+					console.log(res);
+					// if (res.code !== 0) {
+					// 	return;
+					// }
+					if (res.beacons && res.beacons.length > 0) {
+						this.getBeacons();
+					} else if (!res.connected) {
+						this.deviceList = [];
+					}
+				});
+			},
+			startBeaconDiscovery() {
+				uni.startBeaconDiscovery({
+					uuids: this.getUUIDList(),
+					success: (res) => {
+						this.isStarted = true;
+						console.log(res);
+					},
+					fail: (err) => {
+						console.error(err);
+					}
+				});
+			},
+			stopBeaconDiscovery(types) {
+				if(this.isStarted) {
+					uni.stopBeaconDiscovery({
+						success: (res) => {
+							this.isStarted = false;
+						},
+						fail: (err) => {
+							console.error(err);
+						}
+					});
+				}
+			},
+			getBeacons() {
+				uni.getBeacons({
+					success: (res) => {
+						if (res.beacons && res.beacons.length > 0) {
+							console.log(res.beacons);
+							this.deviceList = res.beacons;
+						}
+					},
+					fail: (err) => {
+						console.log('获取设备服务失败,错误码:' + err.errCode);
+						if (errCode.errCode !== 0) {
+							initTypes(errCode.errCode);
+						}
+					}
+				});
+			},
+			getUUIDList() {
+				// #ifdef APP-PLUS
+				const sysInfo = uni.getSystemInfoSync();
+				if (!sysInfo) {
+					return [];
+				}
+				let isIOS = sysInfo.platform ? (sysInfo.platform.toLowerCase() === "ios") : false;
+				if (isIOS) {
+					return [DEVICE_UUID_WEICHAT];
+				}
+				return [];
+				// #endif
+
+				// #ifndef APP-PLUS
+				return [DEVICE_UUID_WEICHAT];
+				// #endif
+			}
+		},
+		onUnload() {
+			this.isPageOpened = false;
+		}
+	};
+
+	/**
+	 * 判断初始化蓝牙状态
+	 */
+	function initTypes(code, errMsg) {
+		switch (code) {
+			case 10000:
+				toast('未初始化蓝牙适配器');
+				break;
+			case 10001:
+				toast('未检测到蓝牙,请打开蓝牙重试!');
+				break;
+			case 10002:
+				toast('没有找到指定设备');
+				break;
+			case 10003:
+				toast('连接失败');
+				break;
+			case 10004:
+				toast('没有找到指定服务');
+				break;
+			case 10005:
+				toast('没有找到指定特征值');
+				break;
+			case 10006:
+				toast('当前连接已断开');
+				break;
+			case 10007:
+				toast('当前特征值不支持此操作');
+				break;
+			case 10008:
+				toast('其余所有系统上报的异常');
+				break;
+			case 10009:
+				toast('Android 系统特有,系统版本低于 4.3 不支持 BLE');
+				break;
+			default:
+				toast(errMsg);
+		}
+	}
+
+	/**
+	 * 弹出框封装
+	 */
+	function toast(content, showCancel = false) {
+		uni.showModal({
+			title: '提示',
+			content,
+			showCancel
+		});
+	}
+</script>
+
+<style>
+	.uni-title {
+		/* width: 100%; */
+		/* height: 80rpx; */
+		text-align: center;
+	}
+
+	.uni-mask {
+		position: fixed;
+		top: 0;
+		left: 0;
+		bottom: 0;
+		display: flex;
+		align-items: center;
+		width: 100%;
+		background: rgba(0, 0, 0, 0.6);
+		padding: 0 30rpx;
+		box-sizing: border-box;
+	}
+
+	.uni-scroll_box {
+		height: 70%;
+		background: #fff;
+		border-radius: 20rpx;
+	}
+
+	.uni-list-box {
+		margin: 0 20rpx;
+		padding: 15rpx 0;
+		border-bottom: 1px #f5f5f5 solid;
+		box-sizing: border-box;
+	}
+
+	.uni-list:last-child {
+		border: none;
+	}
+
+	.uni-list_name {
+		font-size: 30rpx;
+		color: #333;
+	}
+
+	.uni-list_item {
+		font-size: 24rpx;
+		color: #555;
+		line-height: 1.5;
+	}
+
+	.uni-success_box {
+		position: absolute;
+		left: 0;
+		bottom: 0;
+		min-height: 100rpx;
+		width: 100%;
+		background: #fff;
+		box-sizing: border-box;
+		border-top: 1px #eee solid;
+	}
+
+	.uni-success_sub {
+		/* width: 100%%; */
+		height: 100rpx;
+		display: flex;
+		justify-content: space-between;
+		align-items: center;
+		padding: 0 30rpx;
+	}
+
+	.uni-close_button {
+		padding: 0 20rpx;
+		height: 60rpx;
+		line-height: 60rpx;
+		background: #ce3c39;
+		color: #ffffff;
+		border-radius: 10rpx;
+	}
+
+	.uni-success_content {
+		height: 600rpx;
+		margin: 30rpx;
+		margin-top: 0;
+		border: 1px #eee solid;
+		padding: 30rpx;
+	}
+
+	.uni-content_list {
+		padding-bottom: 10rpx;
+		border-bottom: 1px #f5f5f5 solid;
+	}
+
+	.uni-tips {
+		text-align: center;
+		font-size: 24rpx;
+		color: #666;
+	}
+</style>

+ 239 - 0
pages/API/image/image.vue

@@ -0,0 +1,239 @@
+<template>
+	<view>
+		<page-head :title="title"></page-head>
+		<view class="uni-common-mt">
+			<form>
+				<view class="uni-list">
+					<view class="uni-list-cell">
+						<view class="uni-list-cell-left">
+							<view class="uni-label">图片来源</view>
+						</view>
+						<view class="uni-list-cell-right">
+							<picker :range="sourceType" @change="sourceTypeChange" :value="sourceTypeIndex" mode="selector">
+								<view class="uni-input">{{sourceType[sourceTypeIndex]}}</view>
+							</picker>
+						</view>
+					</view>
+
+					<view class="uni-list-cell">
+						<view class="uni-list-cell-left">
+							<view class="uni-label">图片质量</view>
+						</view>
+						<view class="uni-list-cell-right">
+							<picker :range="sizeType" @change="sizeTypeChange" :value="sizeTypeIndex" mode="selector">
+								<view class="uni-input">{{sizeType[sizeTypeIndex]}}</view>
+							</picker>
+						</view>
+					</view>
+
+					<view class="uni-list-cell">
+						<view class="uni-list-cell-left">
+							<view class="uni-label">数量限制</view>
+						</view>
+						<view class="uni-list-cell-right">
+							<picker :range="count" @change="countChange" mode="selector">
+								<view class="uni-input">{{count[countIndex]}}</view>
+							</picker>
+						</view>
+					</view>
+				</view>
+
+
+				<view class="uni-list list-pd">
+					<view class="uni-list-cell cell-pd">
+						<view class="uni-uploader">
+							<view class="uni-uploader-head">
+								<view class="uni-uploader-title">点击可预览选好的图片</view>
+								<view class="uni-uploader-info">{{imageList.length}}/9</view>
+							</view>
+							<view class="uni-uploader-body">
+								<view class="uni-uploader__files">
+									<block v-for="(image,index) in imageList" :key="index">
+										<view class="uni-uploader__file">
+											<image class="uni-uploader__img" :src="image" :data-src="image" @tap="previewImage"></image>
+										</view>
+									</block>
+									<view class="uni-uploader__input-box">
+										<view class="uni-uploader__input" @tap="chooseImage"></view>
+									</view>
+								</view>
+							</view>
+						</view>
+					</view>
+				</view>
+			</form>
+		</view>
+	</view>
+</template>
+<script>
+	import permision from "@/common/permission.js"
+	var sourceType = [
+		['camera'],
+		['album'],
+		['camera', 'album']
+	]
+	var sizeType = [
+		['compressed'],
+		['original'],
+		['compressed', 'original']
+	]
+	export default {
+		data() {
+			return {
+				title: 'choose/previewImage',
+				imageList: [],
+				sourceTypeIndex: 2,
+				sourceType: ['拍照', '相册', '拍照或相册'],
+				sizeTypeIndex: 2,
+				sizeType: ['压缩', '原图', '压缩或原图'],
+				countIndex: 8,
+				count: [1, 2, 3, 4, 5, 6, 7, 8, 9]
+			}
+		},
+		onUnload() {
+			this.imageList = [],
+				this.sourceTypeIndex = 2,
+				this.sourceType = ['拍照', '相册', '拍照或相册'],
+				this.sizeTypeIndex = 2,
+				this.sizeType = ['压缩', '原图', '压缩或原图'],
+				this.countIndex = 8;
+		},
+		methods: {
+			sourceTypeChange: function(e) {
+				this.sourceTypeIndex = parseInt(e.detail.value)
+			},
+			sizeTypeChange: function(e) {
+				this.sizeTypeIndex = parseInt(e.detail.value)
+			},
+			countChange: function(e) {
+				this.countIndex = e.detail.value;
+			},
+			chooseImage: async function() {
+				// #ifdef APP-PLUS
+				// TODO 选择相机或相册时 需要弹出actionsheet,目前无法获得是相机还是相册,在失败回调中处理
+				if (this.sourceTypeIndex !== 2) {
+					let status = await this.checkPermission();
+					if (status !== 1) {
+						return;
+					}
+				}
+				// #endif
+
+				if (this.imageList.length === 9) {
+					let isContinue = await this.isFullImg();
+					console.log("是否继续?", isContinue);
+					if (!isContinue) {
+						return;
+					}
+				}
+				uni.chooseImage({
+					sourceType: sourceType[this.sourceTypeIndex],
+					sizeType: sizeType[this.sizeTypeIndex],
+					count: this.imageList.length + this.count[this.countIndex] > 9 ? 9 - this.imageList.length : this.count[this.countIndex],
+					success: (res) => {
+						this.imageList = this.imageList.concat(res.tempFilePaths);
+					},
+					fail: (err) => {
+						console.log("err: ",err);
+						// #ifdef APP-PLUS
+						if (err['code'] && err.code !== 0 && this.sourceTypeIndex === 2) {
+							this.checkPermission(err.code);
+						}
+						// #endif
+						// #ifdef MP
+						if(err.errMsg.indexOf('cancel') !== '-1'){
+							return;
+						}
+						uni.getSetting({
+							success: (res) => {
+								let authStatus = false;
+								switch (this.sourceTypeIndex) {
+									case 0:
+										authStatus = res.authSetting['scope.camera'];
+										break;
+									case 1:
+										authStatus = res.authSetting['scope.album'];
+										break;
+									case 2:
+										authStatus = res.authSetting['scope.album'] && res.authSetting['scope.camera'];
+										break;
+									default:
+										break;
+								}
+								if (!authStatus) {
+									uni.showModal({
+										title: '授权失败',
+										content: 'Hello uni-app需要从您的相机或相册获取图片,请在设置界面打开相关权限',
+										success: (res) => {
+											if (res.confirm) {
+												uni.openSetting()
+											}
+										}
+									})
+								}
+							}
+						})
+						// #endif
+					}
+				})
+			},
+			isFullImg: function() {
+				return new Promise((res) => {
+					uni.showModal({
+						content: "已经有9张图片了,是否清空现有图片?",
+						success: (e) => {
+							if (e.confirm) {
+								this.imageList = [];
+								res(true);
+							} else {
+								res(false)
+							}
+						},
+						fail: () => {
+							res(false)
+						}
+					})
+				})
+			},
+			previewImage: function(e) {
+				var current = e.target.dataset.src
+				uni.previewImage({
+					current: current,
+					urls: this.imageList
+				})
+			},
+			async checkPermission(code) {
+				let type = code ? code - 1 : this.sourceTypeIndex;
+				let status = permision.isIOS ? await permision.requestIOS(sourceType[type][0]) :
+					await permision.requestAndroid(type === 0 ? 'android.permission.CAMERA' :
+						'android.permission.READ_EXTERNAL_STORAGE');
+
+				if (status === null || status === 1) {
+					status = 1;
+				} else {
+					uni.showModal({
+						content: "没有开启权限",
+						confirmText: "设置",
+						success: function(res) {
+							if (res.confirm) {
+								permision.gotoAppSetting();
+							}
+						}
+					})
+				}
+
+				return status;
+			}
+		}
+	}
+</script>
+
+<style>
+	.cell-pd {
+		padding: 22rpx 30rpx;
+	}
+
+	.list-pd {
+		margin-top: 50rpx;
+	}
+</style>

+ 124 - 0
pages/API/inner-audio/inner-audio.vue

@@ -0,0 +1,124 @@
+<template>
+	<view class="uni-padding-wrap">
+		<page-head title="audio"></page-head>
+		<view class="uni-common-mt">
+			<slider :value="position" :min="0" :max="duration" @changing="onchanging" @change="onchange"></slider>
+		</view>
+		<!-- <view class="uni-common-mt play-time-area">
+			<text class="current-time">{{currentTime}}</text>
+			<text class="duration">{{duration}}</text>
+		</view> -->
+		<view class="play-button-area">
+			<image class="icon-play" :src="playImage" @click="play"></image>
+		</view>
+	</view>
+</template>
+<script>
+	const audioUrl = 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-hello-uniapp/2cc220e0-c27a-11ea-9dfb-6da8e309e0d8.mp3'
+	export default {
+		data() {
+			return {
+				title: "innerAudioContext",
+				isPlaying: false,
+				isPlayEnd: false,
+				currentTime: 0,
+				duration: 100
+			}
+		},
+		computed: {
+			position() {
+				return this.isPlayEnd ? 0 : this.currentTime;
+			},
+			playImage() {
+				return this.isPlaying ? "/static/pause.png" : "/static/play.png"
+			}
+		},
+		onLoad() {
+			this._isChanging = false;
+			this._audioContext = null;
+			this.createAudio();
+		},
+		onUnload() {
+			if (this._audioContext != null && this.isPlaying) {
+				this.stop();
+			}
+		},
+		methods: {
+			createAudio() {
+				var innerAudioContext = this._audioContext = uni.createInnerAudioContext();
+				innerAudioContext.autoplay = false;
+				innerAudioContext.src = audioUrl;
+				innerAudioContext.onPlay(() => {
+					console.log('开始播放');
+				});
+				innerAudioContext.onTimeUpdate((e) => {
+					if (this._isChanging === true) {
+						return;
+					}
+					this.currentTime = innerAudioContext.currentTime || 0;
+					this.duration = innerAudioContext.duration || 0;
+				});
+				innerAudioContext.onEnded(() => {
+					this.currentTime = 0;
+					this.isPlaying = false;
+					this.isPlayEnd = true;
+				});
+				innerAudioContext.onError((res) => {
+					this.isPlaying = false;
+					console.log(res.errMsg);
+					console.log(res.errCode);
+				});
+				return innerAudioContext;
+			},
+			onchanging() {
+				this._isChanging = true;
+			},
+			onchange(e) {
+				console.log(e.detail.value);
+				console.log(typeof e.detail.value);
+				this._audioContext.seek(e.detail.value);
+				this._isChanging = false;
+			},
+			play() {
+				if (this.isPlaying) {
+					this.pause();
+					return;
+				}
+				this.isPlaying = true;
+				this._audioContext.play();
+				this.isPlayEnd = false;
+			},
+			pause() {
+				this._audioContext.pause();
+				this.isPlaying = false;
+			},
+			stop() {
+				this._audioContext.stop();
+				this.isPlaying = false;
+			}
+		}
+	}
+</script>
+<style>
+	.play-time-area {
+		display: flex;
+		flex-direction: row;
+		margin-top: 20px;
+	}
+
+	.duration {
+		margin-left: auto;
+	}
+
+	.play-button-area {
+		display: flex;
+		flex-direction: row;
+		justify-content: center;
+		margin-top: 50px;
+	}
+
+	.icon-play {
+		width: 60px;
+		height: 60px;
+	}
+</style>

+ 69 - 0
pages/API/intersection-observer/intersection-observer.vue

@@ -0,0 +1,69 @@
+<template>
+	<view>
+		<page-head :title="title"></page-head>
+		<view class="uni-padding-wrap uni-common-mt">
+			<view class="uni-title uni-common-mt">
+				{{appear ? '小球出现' : '小球消失'}}
+			</view>
+			<scroll-view class="scroll-view" scroll-y>
+				<view class="scroll-area">
+					<text class="notice">向下滚动让小球出现</text>
+					<view class="ball"></view>
+				</view>
+			</scroll-view>
+		</view>
+	</view>
+</template>
+<script>
+	let observer = null;
+	export default {
+		data() {
+			return {
+				appear: false,
+				title:'intersectionObserver'
+			}
+		},
+		onReady() {
+			observer = uni.createIntersectionObserver(this);
+			observer.relativeTo('.scroll-view').observe('.ball', (res) => {
+				if (res.intersectionRatio > 0 && !this.appear) {
+					this.appear = true;
+				} else if (!res.intersectionRatio > 0 && this.appear) {
+					this.appear = false;
+				}
+			})
+		},
+		onUnload() {
+			if (observer) {
+				observer.disconnect()
+			}
+		}
+	}
+</script>
+<style>
+	.scroll-view {
+		height: 400rpx;
+		background: #fff;
+		border: 1px solid #ccc;
+		box-sizing: border-box;
+	}
+
+	.scroll-area {
+		height: 1300rpx;
+		display: flex;
+		flex-direction: column;
+		align-items: center;
+	}
+
+	.notice {
+		margin-top: 150rpx;
+		margin: 150rpx 0 400rpx 0;
+	}
+
+	.ball {
+		width: 200rpx;
+		height: 200rpx;
+		background: #4cd964;
+		border-radius: 50%;
+	}
+</style>

+ 322 - 0
pages/API/login/login.vue

@@ -0,0 +1,322 @@
+<template>
+	<view>
+		<page-head :title="title"></page-head>
+		<view class="uni-padding-wrap">
+			<view style="background:#FFF; padding:40rpx;">
+				<block v-if="hasLogin === true">
+					<view class="uni-h3 uni-center uni-common-mt">已登录
+						<text v-if="isUniverifyLogin" style="font-size: 0.8em;">
+							<i v-if="!phoneNumber.length" class="uni-icon_toast uni-loading"></i>
+							<i v-else>({{phoneNumber}})</i>
+						</text>
+					</view>
+					<view class="uni-hello-text uni-center">
+						<text>每个账号仅需登录 1 次,\n后续每次进入页面即可自动拉取用户信息。</text>
+					</view>
+				</block>
+				<block v-if="hasLogin === false">
+					<view class="uni-h3 uni-center uni-common-mt">未登录</view>
+					<view class="uni-hello-text uni-center">
+						请点击按钮登录
+					</view>
+				</block>
+			</view>
+			<view class="uni-btn-v uni- uni-common-mt">
+				<!-- #ifdef MP-TOUTIAO  -->
+				<button type="primary" class="page-body-button" v-for="(value,key) in providerList" @click="tologin(value)" :key="key">
+					登录
+				</button>
+				<!-- #endif -->
+				<!-- #ifndef MP-TOUTIAO -->
+				<button type="primary" class="page-body-button" v-for="(value,key) in providerList" @click="tologin(value)"
+				 :loading="value.id === 'univerify' ? univerifyBtnLoading : false" :key="key">{{value.name}}</button>
+				<!-- #endif -->
+			</view>
+		</view>
+	</view>
+</template>
+<script>
+	import {
+		mapState,
+		mapMutations,
+		mapActions
+	} from 'vuex'
+	const univerifyInfoKey = 'univerifyInfo';
+
+	export default {
+		data() {
+			return {
+				title: 'login',
+				providerList: [],
+				phoneNumber: '',
+				univerifyBtnLoading: false
+			}
+		},
+		computed: {
+			...mapState(['hasLogin', 'isUniverifyLogin', 'univerifyErrorMsg'])
+		},
+		onLoad() {
+			uni.getProvider({
+				service: 'oauth',
+				success: (result) => {
+					this.providerList = result.provider.map((value) => {
+						let providerName = '';
+						switch (value) {
+							case 'weixin':
+								providerName = '微信登录'
+								break;
+							case 'qq':
+								providerName = 'QQ登录'
+								break;
+							case 'sinaweibo':
+								providerName = '新浪微博登录'
+								break;
+							case 'xiaomi':
+								providerName = '小米登录'
+								break;
+							case 'alipay':
+								providerName = '支付宝登录'
+								break;
+							case 'baidu':
+								providerName = '百度登录'
+								break;
+							case 'jd':
+							  providerName = '京东登录'
+							  break;
+							case 'toutiao':
+								providerName = '头条登录'
+								break;
+							case 'apple':
+								providerName = '苹果登录'
+								break;
+							case 'univerify':
+								providerName = '一键登录'
+								break;
+						}
+						return {
+							name: providerName,
+							id: value
+						}
+					});
+
+				},
+				fail: (error) => {
+					console.log('获取登录通道失败', error);
+				}
+			});
+
+			if (this.hasLogin && this.isUniverifyLogin) {
+				this.getPhoneNumber(uni.getStorageSync(univerifyInfoKey)).then((phoneNumber) => {
+					this.phoneNumber = phoneNumber
+				})
+			}
+		},
+		methods: {
+			...mapMutations(['login', 'setUniverifyLogin']),
+			...mapActions(['getPhoneNumber']),
+			Toast(data, duration = 1000) {
+				uni.showToast(Object.assign({}, data, {
+					duration
+				}))
+			},
+			tologin(provider) {
+				if (provider.id === 'univerify') {
+					this.univerifyBtnLoading = true;
+				}
+
+				// 一键登录已在APP onLaunch的时候进行了预登陆,可以显著提高登录速度。登录成功后,预登陆状态会重置
+				uni.login({
+					provider: provider.id,
+					// #ifdef MP-ALIPAY
+					scopes: 'auth_user', //支付宝小程序需设置授权类型
+					// #endif
+					success: async (res) => {
+						console.log('login success:', res);
+						this.Toast({
+							title: '登录成功'
+						})
+						// 更新保存在 store 中的登录状态
+						this.login(provider.id);
+
+						// #ifdef APP-PLUS
+						this.setUniverifyLogin(provider.id === 'univerify')
+						switch (provider.id) {
+							case 'univerify':
+								this.loginByUniverify(provider.id, res)
+								break;
+							case 'apple':
+								this.loginByApple(provider.id, res)
+								break;
+						}
+						// #endif
+
+						// #ifdef MP-WEIXIN
+						console.warn('如需获取openid请参考uni-id: https://uniapp.dcloud.net.cn/uniCloud/uni-id')
+						uni.request({
+							url: 'https://97fca9f2-41f6-449f-a35e-3f135d4c3875.bspapp.com/http/user-center',
+							method: 'POST',
+							data: {
+								action: 'loginByWeixin',
+								params: {
+									code: res.code,
+									platform: 'mp-weixin'
+								}
+							},
+							success(res) {
+								console.log(res);
+								if (res.data.code !== 0) {
+									console.log('获取openid失败:', res.data.errMsg);
+									return
+								}
+								uni.setStorageSync('openid', res.data.openid)
+							},
+							fail(err) {
+								console.log('获取openid失败:', err);
+							}
+						})
+						// #endif
+					},
+					fail: (err) => {
+						console.log('login fail:', err);
+
+						// 一键登录点击其他登录方式
+						if (err.code == '30002') {
+							uni.closeAuthView();
+							this.Toast({
+								title: '其他登录方式'
+							})
+							return;
+						}
+
+						// 未开通
+						if (err.code == 1000) {
+							uni.showModal({
+								title: '登录失败',
+								content: `${err.errMsg}\n,错误码:${err.code}`,
+								confirmText: '开通指南',
+								cancelText: '确定',
+								success: (res) => {
+									if (res.confirm) {
+										setTimeout(() => {
+											plus.runtime.openWeb('https://ask.dcloud.net.cn/article/37965')
+										}, 500)
+									}
+								}
+							});
+							return;
+						}
+
+						// 一键登录预登陆失败
+						if (err.code == '30005') {
+							uni.showModal({
+								showCancel: false,
+								title: '预登录失败',
+								content: this.univerifyErrorMsg || err.errMsg
+							});
+							return;
+						}
+
+						// 一键登录用户关闭验证界面
+						if (err.code != '30003') {
+							uni.showModal({
+								showCancel: false,
+								title: '登录失败',
+								content: JSON.stringify(err)
+							});
+						}
+					},
+					complete: () => {
+						this.univerifyBtnLoading = false;
+					}
+				});
+			},
+			loginByUniverify(provider, res) {
+				this.setUniverifyLogin(true);
+				uni.closeAuthView();
+
+				const univerifyInfo = {
+					provider,
+					...res.authResult,
+				}
+
+				this.getPhoneNumber(univerifyInfo).then((phoneNumber) => {
+					this.phoneNumber = phoneNumber;
+					uni.setStorageSync(univerifyInfoKey, univerifyInfo)
+				}).catch(err => {
+					uni.showModal({
+						showCancel: false,
+						title: '手机号获取失败',
+						content: `${err.errMsg}\n,错误码:${err.code}`
+					})
+					console.error(res);
+				})
+			},
+			async loginByApple(provider, res) {
+				// 获取用户信息
+				let getUserInfoErr, result
+				// #ifndef VUE3
+				[getUserInfoErr, result] = await uni.getUserInfo({
+					provider
+				});
+				// #endif
+				
+				// #ifdef VUE3
+				try {
+					result = await uni.getUserInfo({
+						provider
+					});
+				} catch(e) {
+					getUserInfoErr = e
+				}
+				// #endif
+				
+				if (getUserInfoErr) {
+					let content = getUserInfoErr.errMsg;
+					if (~content.indexOf('uni.login')) {
+						content = '请在登录页面完成登录操作';
+					}
+					uni.showModal({
+						title: '获取用户信息失败',
+						content: '错误原因' + content,
+						showCancel: false
+					});
+				}
+				// uni-id 苹果登录
+				console.warn('此处使用uni-id处理苹果登录,详情参考: https://uniapp.dcloud.net.cn/uniCloud/uni-id')
+				uni.request({
+					url: 'https://97fca9f2-41f6-449f-a35e-3f135d4c3875.bspapp.com/http/user-center',
+					method: 'POST',
+					data: {
+						action: 'loginByApple',
+						params: result.userInfo
+					},
+					success: (res) => {
+						console.log('uniId login success', res);
+						if(res.data.code !== 0){
+							uni.showModal({
+								showCancel: false,
+								content: `苹果登录失败: ${JSON.stringify(res.data.msg)}`,
+							})
+						} else {
+							uni.setStorageSync('openid', res.data.openid)
+							uni.setStorageSync('apple_nickname', res.data.userInfo.nickname)
+						}
+					},
+					fail: (e) => {
+						uni.showModal({
+							content: `苹果登录失败: ${JSON.stringify(e)}`,
+							showCancel: false
+						})
+					}
+				})
+			}
+		}
+	}
+</script>
+
+<style>
+	button {
+		background-color: #007aff;
+		color: #ffffff;
+	}
+</style>

+ 50 - 0
pages/API/make-phone-call/make-phone-call.vue

@@ -0,0 +1,50 @@
+<template>
+	<view>
+		<page-head :title="title"></page-head>
+		<view class="uni-padding-wrap uni-common-mt">
+			<view class="uni-hello-text uni-center">请在下方输入电话号码</view>
+			<input class="input uni-common-mt" type="number" name="input" @input="bindInput" />
+			<view class="uni-btn-v uni-common-mt">
+				<button @tap="makePhoneCall" type="primary" :disabled="disabled">拨打</button>
+			</view>
+		</view>
+	</view>
+</template>
+<script>
+	export default {
+		data() {
+			return {
+				title: 'makePhoneCall',
+				disabled: true
+			}
+		},
+		methods: {
+			bindInput: function (e) {
+				this.inputValue = e.detail.value
+				if (this.inputValue.length > 0) {
+					this.disabled = false
+				} else {
+					this.disabled = true
+				}
+			},
+			makePhoneCall: function () {
+				uni.makePhoneCall({
+					phoneNumber: this.inputValue,
+					success: () => {
+						console.log("成功拨打电话")
+					}
+				})
+			}
+		}
+	}
+</script>
+
+<style>
+	.input {
+		height: 119rpx;
+		line-height: 119rpx;
+		font-size: 78rpx;
+		border-bottom: 1rpx solid #E2E2E2;
+		text-align:center;
+	}
+</style>

+ 102 - 0
pages/API/map-search/map-search.nvue

@@ -0,0 +1,102 @@
+<template>
+    <view class="content">
+        <map class="map" ref="dcmap" :markers="markers" @tap="selectPoint"></map>
+        <scroll-view class="scrollview" scroll-y="true">
+            <button class="button" @click="reverseGeocode">reverseGeocode</button>
+            <button class="button" @click="poiSearchNearBy">poiSearchNearBy</button>
+        </scroll-view>
+    </view>
+</template>
+
+<script>
+    // 116.397477,39.908692
+    let mapSearch = weex.requireModule('mapSearch')
+    export default {
+        data() {
+            return {
+                markers: [{
+                    id: '1',
+                    latitude: 39.9086920000,
+                    longitude: 116.3974770000,
+                    title: '天安门',
+                    zIndex: '1',
+                    iconPath: '/static/gps.png',
+                    width: 20,
+                    height: 20,
+                    anchor: {
+                        x: 0.5,
+                        y: 1
+                    },
+                    callout: {
+                        content: '首都北京\n天安门',
+                        color: '#00BFFF',
+                        fontSize: 12,
+                        borderRadius: 2,
+                        borderWidth: 0,
+                        borderColor: '#333300',
+                        bgColor: '#CCFF11',
+                        padding: '1',
+                        display: 'ALWAYS'
+                    }
+                }]
+            }
+        },
+        methods: {
+            selectPoint(e) {
+                console.log(e);
+            },
+            reverseGeocode() {
+                var point = this.markers[0]
+                mapSearch.reverseGeocode({
+                    point: {
+                        latitude: point.latitude,
+                        longitude: point.longitude
+                    }
+                }, ret => {
+                    console.log(JSON.stringify(ret));
+                    uni.showModal({
+                        content: JSON.stringify(ret)
+                    })
+                })
+            },
+            poiSearchNearBy() {
+                var point = this.markers[0]
+                mapSearch.poiSearchNearBy({
+                    point: {
+                        latitude: point.latitude,
+                        longitude: point.longitude
+                    },
+                    key: '停车场',
+                    radius: 1000
+                }, ret => {
+                    console.log(ret);
+                    uni.showModal({
+                        content: JSON.stringify(ret)
+                    })
+                })
+
+            }
+        }
+    }
+</script>
+
+<style>
+    .content {
+        flex: 1;
+    }
+
+    .map {
+        width: 750rpx;
+        height: 500rpx;
+        background-color: black;
+    }
+
+    .scrollview {
+        flex: 1;
+    }
+
+    .button {
+        margin-top: 30rpx;
+        margin-bottom: 20rpx;
+    }
+</style>

+ 448 - 0
pages/API/map/map.nvue

@@ -0,0 +1,448 @@
+<template>
+    <view class="content">
+        <map class="map" id="map1" ref="map1" :controls="controls" :scale="scale" :longitude="location.longitude"
+            :latitude="location.latitude" :show-location="showLocation" :enable-3D="enable3D" :rotate="rotate" :skew="skew"
+            :show-compass="showCompass" :enable-overlooking="enableOverlooking" :enable-zoom="enableZoom"
+            :enable-scroll="enableScroll" :enable-rotate="enableRotate" :enable-satellite="enableSatellite"
+            :enable-traffic="enableTraffic" :markers="markers" :polyline="polyline" :circles="circles" :polygons="polygons"
+            :include-points="includePoints" @tap="maptap" @controltap="oncontroltap" @markertap="onmarkertap"
+            @callouttap="oncallouttap" @poitap="onpoitap" @updated="onupdated" @regionchange="onregionchange"></map>
+        <scroll-view class="scrollview" scroll-y="true">
+            <!-- <view class="list-item">
+                <text class="list-text">显示3D楼块</text>
+                <switch :checked="enable3D" @change="enableThreeD" />
+            </view>
+            <view class="list-item">
+                <text class="list-text">显示指南针</text>
+                <switch :checked="showCompass" @change="changeShowCompass" />
+            </view>
+            <view class="list-item">
+                <text class="list-text">开启俯视</text>
+                <switch :checked="enableOverlooking" @change="changeEnableOverlooking" />
+            </view>
+            <view class="list-item">
+                <text class="list-text">是否支持缩放</text>
+                <switch :checked="enableZoom" @change="changeEnableZoom" />
+            </view>
+            <view class="list-item">
+                <text class="list-text">是否支持拖动</text>
+                <switch :checked="enableScroll" @change="changeEnableScroll" />
+            </view>
+            <view class="list-item">
+                <text class="list-text">是否支持旋转</text>
+                <switch :checked="enableRotate" @change="changeEnableRotate" />
+            </view>
+            <view class="list-item">
+                <text class="list-text">是否开启卫星图</text>
+                <switch :checked="enableSatellite" @change="changeEnableSatellite" />
+            </view>
+            <view class="list-item">
+                <text class="list-text">是否开启实时路况</text>
+                <switch :checked="enableTraffic" @change="changeEnableTraffic" />
+            </view> -->
+				<!-- #ifndef MP-JD -->
+            <button class="button" @click="changeScale">changeScale</button>
+            <button class="button" @click="changeRotate">changeRotate</button>
+				<button class="button" @click="changeSkew">skew</button>
+				<!-- #endif -->
+            <button class="button" @click="addMarkers">addMarkers</button>
+            <button class="button" @click="addPolyline">addPolyline</button>
+				<!-- #ifndef MP-JD -->
+            <button class="button" @click="addPolygons">addPolygons</button>
+				<!-- #endif -->
+            <button class="button" @click="addCircles">addCircles</button>
+            <button class="button" @click="includePoint">includePoints</button>
+            <button class="button" @click="handleGetCenterLocation">getCenterLocation</button>
+            <button class="button" @click="handleGetRegion">getRegion</button>
+				<!-- #ifndef MP-JD -->
+            <button class="button" @click="handleTranslateMarker">translateMarker</button>
+				<!-- #endif -->
+        </scroll-view>
+    </view>
+</template>
+
+<script>
+    const testMarkers = [{
+            id: 0,
+            latitude: 39.989631,
+            longitude: 116.481018,
+            title: '方恒国际 阜通东大街6号',
+            zIndex: '1',
+            rotate: 0,
+            width: 20,
+            height: 20,
+            anchor: {
+                x: 0.5,
+                y: 1
+            },
+            callout: {
+                content: '方恒国际 阜通东大街6号',
+                color: '#00BFFF',
+                fontSize: 10,
+                borderRadius: 4,
+                borderWidth: 1,
+                borderColor: '#333300',
+                bgColor: '#CCFF99',
+                padding: '5',
+                display: 'ALWAYS'
+            }
+        },
+        {
+            id: 1,
+            latitude: 39.9086920000,
+            longitude: 116.3974770000,
+            title: '天安门',
+            zIndex: '1',
+            iconPath: '/static/location.png',
+            width: 40,
+            height: 40,
+            anchor: {
+                x: 0.5,
+                y: 1
+            },
+            callout: {
+                content: '首都北京\n天安门',
+                color: '#00BFFF',
+                fontSize: 12,
+                borderRadius: 2,
+                borderWidth: 0,
+                borderColor: '#333300',
+                bgColor: '#CCFF11',
+                padding: '1',
+                display: 'ALWAYS'
+            }
+        }
+    ];
+    const testPolyline = [{
+            points: [{
+                    latitude: 39.925539,
+                    longitude: 116.279037
+                },
+                {
+                    latitude: 39.925539,
+                    longitude: 116.520285
+                }
+            ],
+            color: '#FFCCFF',
+            width: 7,
+            dottedLine: true,
+            arrowLine: true,
+            borderColor: '#000000',
+            borderWidth: 2
+        },
+        {
+            points: [{
+                    latitude: 39.938698,
+                    longitude: 116.275177
+                },
+                {
+                    latitude: 39.966069,
+                    longitude: 116.289253
+                },
+                {
+                    latitude: 39.944226,
+                    longitude: 116.306076
+                },
+                {
+                    latitude: 39.966069,
+                    longitude: 116.322899
+                },
+                {
+                    latitude: 39.938698,
+                    longitude: 116.336975
+                }
+            ],
+            color: '#CCFFFF',
+            width: 5,
+            dottedLine: true,
+            arrowLine: true,
+            borderColor: '#CC0000',
+            borderWidth: 3
+        }
+    ];
+    const testPolygons = [{
+            points: [{
+                    latitude: 39.781892,
+                    longitude: 116.293413
+                },
+                {
+                    latitude: 39.787600,
+                    longitude: 116.391842
+                },
+                {
+                    latitude: 39.733187,
+                    longitude: 116.417932
+                },
+                {
+                    latitude: 39.704653,
+                    longitude: 116.338255
+                }
+            ],
+            fillColor: '#FFCCFF',
+            strokeWidth: 3,
+            strokeColor: '#CC99CC',
+            zIndex: 11
+        },
+        {
+            points: [{
+                    latitude: 39.887600,
+                    longitude: 116.518932
+                },
+                {
+                    latitude: 39.781892,
+                    longitude: 116.518932
+                },
+                {
+                    latitude: 39.781892,
+                    longitude: 116.428932
+                },
+                {
+                    latitude: 39.887600,
+                    longitude: 116.428932
+                }
+            ],
+            fillColor: '#CCFFFF',
+            strokeWidth: 5,
+            strokeColor: '#CC0000',
+            zIndex: 3
+        }
+    ];
+    const testCircles = [{
+            latitude: 39.996441,
+            longitude: 116.411146,
+            radius: 15000,
+            strokeWidth: 5,
+            color: '#CCFFFF',
+            fillColor: '#CC0000'
+        },
+        {
+            latitude: 40.096441,
+            longitude: 116.511146,
+            radius: 12000,
+            strokeWidth: 3,
+            color: '#CCFFFF',
+            fillColor: '#FFCCFF'
+        },
+        {
+            latitude: 39.896441,
+            longitude: 116.311146,
+            radius: 9000,
+            strokeWidth: 1,
+            color: '#CCFFFF',
+            fillColor: '#CC0000'
+        }
+    ];
+    const testIncludePoints = [{
+            latitude: 39.989631,
+            longitude: 116.481018,
+        },
+        {
+            latitude: 39.9086920000,
+            longitude: 116.3974770000,
+        }
+    ];
+    export default {
+        data() {
+            return {
+                location: {
+                    longitude: 116.3974770000,
+                    latitude: 39.9086920000
+                },
+                controls: [{
+                    id: 1,
+                    position: {
+                        left: 5,
+                        top: 180,
+                        width: 30,
+                        height: 30
+                    },
+                    iconPath: '/static/logo.png',
+                    clickable: true
+                }],
+                showLocation: false,
+                scale: 13,
+                showCompass: true,
+                enable3D: true,
+                enableOverlooking: true,
+                enableOverlooking: true,
+                enableZoom: true,
+                enableScroll: true,
+                enableRotate: true,
+                enableSatellite: false,
+                enableTraffic: false,
+                polyline: [],
+                markers: [],
+                polygons: [],
+                circles: [],
+                includePoints: [],
+                rotate: 0,
+                skew: 0
+            }
+        },
+        onLoad() {},
+        onReady() {
+            this.map = uni.createMapContext("map1", this);
+        },
+        methods: {
+			  // #ifndef MP-JD
+            changeScale() {
+                this.scale = this.scale == 9 ? 15 : 9;
+            },
+            changeRotate() {
+                this.rotate = this.rotate == 90 ? 0 : 90;
+            },
+				changeSkew() {
+				    this.skew = this.skew == 30 ? 0 : 30;
+				},
+				// #endif
+            enableThreeD(e) {
+                this.enable3D = e.detail.value;
+            },
+            changeShowCompass(e) {
+                this.showCompass = e.detail.value;
+            },
+            changeEnableOverlooking(e) {
+                this.enableOverlooking = e.detail.value;
+            },
+            changeEnableZoom(e) {
+                this.enableZoom = e.detail.value;
+            },
+            changeEnableScroll(e) {
+                this.enableScroll = e.detail.value;
+            },
+            changeEnableRotate(e) {
+                this.enableRotate = e.detail.value;
+            },
+            changeEnableSatellite(e) {
+                this.enableSatellite = e.detail.value;
+            },
+            changeEnableTraffic(e) {
+                this.enableTraffic = e.detail.value;
+            },
+            addMarkers() {
+                this.markers = testMarkers;
+            },
+            addPolyline() {
+                this.polyline = testPolyline;
+            },
+				// #ifndef MP-JD
+            addPolygons() {
+                this.polygons = testPolygons;
+            },
+				// #endif
+            addCircles() {
+                this.circles = testCircles;
+            },
+            includePoint() {
+                this.includePoints = testIncludePoints;
+            },
+            handleGetCenterLocation() {
+                this.map.getCenterLocation({
+                    success: ret => {
+                        console.log(JSON.stringify(ret));
+                        uni.showModal({
+                            content: JSON.stringify(ret)
+                        })
+                    }
+                })
+            },
+            handleGetRegion() {
+                this.map.getRegion({
+                    success: ret => {
+                        console.log(JSON.stringify(ret));
+                        uni.showModal({
+                            content: JSON.stringify(ret)
+                        })
+                    }
+                })
+            },
+				// #ifndef MP-JD
+            handleTranslateMarker() {
+                this.map.translateMarker({
+                    markerId: 1,
+                    destination: {
+                        latitude: 39.989631,
+                        longitude: 116.481018
+                    },
+                    duration: 2000
+                }, ret => {
+                    console.log(JSON.stringify(ret));
+                    uni.showModal({
+                        content: JSON.stringify(ret)
+                    })
+                });
+            },
+				// #endif
+            maptap(e) {
+                uni.showModal({
+                    content: JSON.stringify(e)
+                })
+            },
+            onmarkertap(e) {
+                uni.showModal({
+                    content: JSON.stringify(e)
+                })
+            },
+            oncontroltap(e) {
+                uni.showModal({
+                    content: JSON.stringify(e)
+                })
+            },
+            oncallouttap(e) {
+                uni.showModal({
+                    content: JSON.stringify(e)
+                })
+            },
+            onupdated(e) {
+                console.log(JSON.stringify(e))
+            },
+            onregionchange(e) {
+                console.log(JSON.stringify(e));
+            },
+            onpoitap(e) {
+                uni.showModal({
+                    content: JSON.stringify(e)
+                })
+            }
+        }
+    }
+</script>
+
+<style>
+    .content {
+        flex: 1;
+    }
+
+    .map {
+        width: 750rpx;
+		/* #ifdef H5 */
+		width: 100%;
+		/* #endif */
+        height: 250px;
+        background-color: #f0f0f0;
+    }
+
+    .scrollview {
+		/* #ifdef H5 */
+		margin-top: 240px;
+		/* #endif */
+        flex: 1;
+        padding: 10px;
+    }
+
+    .list-item {
+        flex-direction: row;
+        flex-wrap: nowrap;
+        align-items: center;
+        padding: 5px 0px;
+    }
+
+    .list-text {
+        flex: 1;
+    }
+
+    .button {
+        margin-top: 5px;
+        margin-bottom: 5px;
+    }
+</style>

+ 40 - 0
pages/API/modal/modal.vue

@@ -0,0 +1,40 @@
+<template>
+	<view>
+		<page-head :title="title"></page-head>
+		<view class="uni-padding-wrap uni-common-mt">
+			<view class="uni-btn-v">
+				<button type="default" @tap="modalTap">有标题的modal</button>
+				<button type="default" @tap="noTitlemodalTap">无标题的modal</button>
+			</view>
+		</view>
+	</view>
+</template>
+<script>
+
+	export default {
+		data() {
+			return {
+				title: 'modal',
+				modalHidden: true,
+				modalHidden2: true
+			}
+		},
+		methods: {
+			modalTap: function (e) {
+				uni.showModal({
+					title: "弹窗标题",
+					content: "弹窗内容,告知当前状态、信息和解决方法,描述文字尽量控制在三行内",
+					showCancel: false,
+					confirmText: "确定"
+				})
+			},
+			noTitlemodalTap: function (e) {
+				uni.showModal({
+					content: "弹窗内容,告知当前状态、信息和解决方法,描述文字尽量控制在三行内",
+					confirmText: "确定",
+					cancelText: "取消"
+				})
+			}
+		}
+	}
+</script>

+ 105 - 0
pages/API/navigator/navigator.vue

@@ -0,0 +1,105 @@
+<template>
+	<view>
+		<page-head :title="title"></page-head>
+		<view class="uni-padding-wrap uni-common-mt">
+			<view class="uni-btn-v">
+				<button @tap="navigateTo">跳转新页面,并传递数据</button>
+				<button @tap="navigateBack">返回上一页</button>
+				<button @tap="redirectTo">在当前页面打开</button>
+				<button @tap="switchTab">切换到模板选项卡</button>
+				<button v-if="!hasLeftWin" @tap="reLaunch">关闭所有页面,打开首页</button>
+				<!-- #ifdef APP-PLUS -->
+				<button @tap="customAnimation">使用自定义动画打开页面</button>
+				<!-- #endif -->
+				<!-- #ifdef APP-PLUS || H5 -->
+				<button @tap="preloadPage">预载复杂页面</button>
+				<!-- #endif -->
+				<!-- #ifdef APP-PLUS -->
+				<button @tap="unPreloadPage">取消页面预载</button>
+				<!-- #endif -->
+				<!-- #ifdef APP-PLUS || H5 -->
+				<button @tap="navigateToPreloadPage">打开复杂页面</button>
+				<!-- #endif -->
+			</view>
+		</view>
+	</view>
+</template>
+<script>
+	const preloadPageUrl = '/pages/extUI/calendar/calendar'
+	import { mapState } from 'vuex'
+	export default {
+		data() {
+			return {
+				title: 'navigate'
+			}
+		},
+		computed: {
+			...mapState({
+				hasLeftWin: state => !state.noMatchLeftWindow
+			})
+		},
+		methods: {
+			navigateTo() {
+				uni.navigateTo({
+					url: 'new-page/new-vue-page-1?data=Hello'
+				})
+			},
+			navigateBack() {
+				uni.navigateBack();
+			},
+			redirectTo() {
+				uni.redirectTo({
+					url: 'new-page/new-vue-page-1'
+				});
+			},
+			switchTab() {
+				uni.switchTab({
+					url: '/pages/tabBar/template/template'
+				});
+			},
+			reLaunch() {
+				if (this.hasLeftWin) {
+					uni.reLaunch({
+						url: '/pages/component/view/view'
+					});
+					return;
+				}
+				uni.reLaunch({
+					url: '/pages/tabBar/component/component'
+				});
+			},
+			customAnimation(){
+				uni.navigateTo({
+					url: 'new-page/new-vue-page-1?data=使用自定义动画打开页面',
+					animationType: 'slide-in-bottom',
+					animationDuration: 200
+				})
+			},
+			preloadPage(){
+				uni.preloadPage({
+					url: preloadPageUrl,
+					success(){
+						uni.showToast({
+							title:'页面预载成功'
+						})
+					},
+					fail(){
+						uni.showToast({
+							title:'页面预载失败'
+						})
+					}
+				})
+			},
+			unPreloadPage(){
+				uni.unPreloadPage({
+					url: preloadPageUrl
+				})
+			},
+			navigateToPreloadPage(){
+				uni.navigateTo({
+					url: preloadPageUrl
+				})
+			}
+		}
+	}
+</script>

+ 83 - 0
pages/API/navigator/new-page/new-nvue-page-1.nvue

@@ -0,0 +1,83 @@
+<template>
+	<view class="root">
+		<view class="page-body">
+            <view class="new-page__color" @click="setColorIndex(colorIndex>1?0:colorIndex+1)" :style="{backgroundColor:currentColor}">
+                <text class="new-page__color-text">点击改变颜色</text>
+            </view>
+            <view class="new-page__text-box">
+                <text class="new-page__text">点击上方色块使用vuex在页面之间进行通讯</text>
+            </view>
+            <view class="new-page__button">
+                <button class="new-page__button-item" @click="navToNvue">跳转NVUE页面</button>
+                <button class="new-page__button-item" @click="navToVue">跳转VUE页面</button>
+            </view>
+		</view>
+	</view>
+</template>
+<script>
+    import {mapState,mapGetters,mapMutations} from 'vuex'
+	export default {
+		data() {
+			return {
+			}
+		},
+        computed:{
+            ...mapState(['colorIndex','colorList']),
+            ...mapGetters(['currentColor'])
+        },
+        methods:{
+            ...mapMutations(['setColorIndex']),
+            navToNvue(){
+                uni.navigateTo({
+                    url:'new-nvue-page-2'
+                })
+            },
+            navToVue(){
+                uni.navigateTo({
+                    url:'new-vue-page-2'
+                })
+            }
+        }
+	}
+</script>
+<style>
+    .new-page__text {
+        font-size: 14px;
+        color: #666666;
+    }
+
+	.root{
+		flex-direction: column;
+	}
+
+	.page-body{
+		flex: 1;
+        flex-direction: column;
+		justify-content: flex-start;
+		align-items: center;
+        padding-top: 50px;
+	}
+
+    .new-page__text-box{
+        padding: 20px;
+    }
+
+    .new-page__color{
+        width: 200px;
+        height: 100px;
+		justify-content: center;
+		align-items: center;
+    }
+
+    .new-page__color-text{
+        font-size: 14px;
+        color: #FFFFFF;
+        line-height: 30px;
+        text-align: center;
+    }
+
+    .new-page__button-item{
+        margin-top: 15px;
+        width: 300px;
+    }
+</style>

+ 69 - 0
pages/API/navigator/new-page/new-nvue-page-2.nvue

@@ -0,0 +1,69 @@
+<template>
+	<view class="root">
+		<view class="page-body">
+            <view class="new-page__color" @click="setColorIndex(colorIndex>1?0:colorIndex+1)" :style="{backgroundColor:currentColor}">
+                <text class="new-page__color-text">点击改变颜色</text>
+            </view>
+            <view class="new-page__text-box">
+                <text class="new-page__text">点击上方色块使用vuex在页面之间进行通讯</text>
+            </view>
+		</view>
+	</view>
+</template>
+<script>
+    import {mapState,mapGetters,mapMutations} from 'vuex'
+	export default {
+		data() {
+			return {
+			}
+		},
+        computed:{
+            ...mapState(['colorIndex','colorList']),
+            ...mapGetters(['currentColor'])
+        },
+        methods:{
+            ...mapMutations(['setColorIndex'])
+        }
+	}
+</script>
+<style>
+    .new-page__text {
+        font-size: 14px;
+        color: #666666;
+    }
+
+	.root{
+		flex-direction: column;
+	}
+
+	.page-body{
+		flex: 1;
+        flex-direction: column;
+		justify-content: flex-start;
+		align-items: center;
+        padding-top: 50px;
+	}
+
+    .new-page__text-box{
+        padding: 20px;
+    }
+
+    .new-page__color{
+        width: 200px;
+        height: 100px;
+		justify-content: center;
+		align-items: center;
+    }
+
+    .new-page__color-text{
+        font-size: 14px;
+        color: #FFFFFF;
+        line-height: 30px;
+        text-align: center;
+    }
+
+    .new-page__button-item{
+        margin-top: 15px;
+        width: 300px;
+    }
+</style>

+ 108 - 0
pages/API/navigator/new-page/new-vue-page-1.vue

@@ -0,0 +1,108 @@
+<template>
+	<view class="root">
+		<page-head :title="title"></page-head>
+		<view class="page-body">
+            <view class="new-page__text-box">
+                <text class="new-page__text">从上个页面接收到参数:{{data}}</text>
+            </view>
+            <view class="new-page__color" @click="setColorIndex(colorIndex>1?0:colorIndex+1)" :style="{backgroundColor:currentColor}">
+                <text class="new-page__color-text">点击改变颜色</text>
+            </view>
+            <view class="new-page__text-box">
+                <text class="new-page__text">点击上方色块使用vuex在页面之间进行通讯</text>
+            </view>
+            <view class="new-page__button">
+				<!-- #ifndef VUE3-->
+                <button class="new-page__button-item" @click="navToNvue">跳转NVUE页面</button>
+				<!-- #endif -->
+                <button class="new-page__button-item" @click="navToVue">跳转VUE页面</button>
+            </view>
+		</view>
+	</view>
+</template>
+<script>
+    import {mapState,mapGetters,mapMutations} from 'vuex'
+	export default {
+		data() {
+			return {
+				title: '新页面',
+				data:""
+			}
+		},
+        computed:{
+            ...mapState(['colorIndex','colorList']),
+            ...mapGetters(['currentColor'])
+        },
+		onLoad(e){
+			if(e.data){
+				this.data = e.data;
+			}
+            uni.$on('postMsg',(res)=>{
+                uni.showModal({
+                    content: `收到uni.$emit消息:${res.msg}`,
+                    showCancel: false
+                })
+            })
+		},
+        onUnload() {
+            uni.$off('postMsg')
+        },
+        methods:{
+            ...mapMutations(['setColorIndex']),
+            navToNvue(){
+                uni.navigateTo({
+                    url:'new-nvue-page-1'
+                })
+            },
+            navToVue(){
+                uni.navigateTo({
+                    url:'new-vue-page-2'
+                })
+            }
+        }
+	}
+</script>
+<style>
+    .new-page__text {
+        font-size: 14px;
+        color: #666666;
+    }
+
+	.root{
+		display: flex;
+		flex: 1;
+		flex-direction: column;
+	}
+
+	.page-body{
+		/* flex: 1; */
+		display: flex;
+        flex-direction: column;
+		justify-content: flex-start;
+		align-items: center;
+	}
+
+    .new-page__text-box{
+        padding: 20px;
+    }
+
+    .new-page__color{
+        display: flex;
+        width: 200px;
+        height: 100px;
+		justify-content: center;
+		align-items: center;
+    }
+
+    .new-page__color-text{
+        font-size: 14px;
+        color: #FFFFFF;
+        line-height: 30px;
+        text-align: center;
+    }
+
+    .new-page__button-item{
+        margin-top: 15px;
+        width: 300px;
+    }
+</style>

+ 84 - 0
pages/API/navigator/new-page/new-vue-page-2.vue

@@ -0,0 +1,84 @@
+<template>
+    <view class="root">
+        <view class="page-body">
+            <view class="new-page__color" @click="setColorIndex(colorIndex>1?0:colorIndex+1)" :style="{backgroundColor:currentColor}">
+                <text class="new-page__color-text">点击改变颜色</text>
+            </view>
+            <view class="new-page__text-box">
+                <text class="new-page__text">点击上方色块使用vuex在页面之间进行通讯</text>
+            </view>
+            <view class="new-page__button">
+                <button class="new-page__button-item" @click="emitMsg">向上一页面传递数据</button>
+            </view>
+        </view>
+    </view>
+</template>
+<script>
+    import {
+        mapState,
+        mapGetters,
+        mapMutations
+    } from 'vuex'
+    export default {
+        data() {
+            return {}
+        },
+        computed: {
+            ...mapState(['colorIndex', 'colorList']),
+            ...mapGetters(['currentColor'])
+        },
+        methods: {
+            ...mapMutations(['setColorIndex']),
+            emitMsg() {
+                uni.$emit('postMsg', {
+                    msg: 'From: Vue Page'
+                })
+            }
+        }
+    }
+</script>
+<style>
+    .new-page__text {
+        font-size: 14px;
+        color: #666666;
+    }
+
+    .root {
+        display: flex;
+        flex: 1;
+        flex-direction: column;
+    }
+
+    .page-body {
+        flex: 1;
+        display: flex;
+        flex-direction: column;
+        justify-content: flex-start;
+        align-items: center;
+        padding-top: 50px;
+    }
+
+    .new-page__text-box {
+        padding: 20px;
+    }
+
+    .new-page__color {
+        display: flex;
+        width: 200px;
+        height: 100px;
+        justify-content: center;
+        align-items: center;
+    }
+
+    .new-page__color-text {
+        font-size: 14px;
+        color: #FFFFFF;
+        line-height: 30px;
+        text-align: center;
+    }
+
+    .new-page__button-item {
+        margin-top: 15px;
+        width: 300px;
+    }
+</style>

+ 62 - 0
pages/API/on-accelerometer-change/on-accelerometer-change.vue

@@ -0,0 +1,62 @@
+<template>
+	<view>
+		<page-head :title="title"></page-head>
+		<view class="uni-padding-wrap uni-common-mt">
+			<!-- #ifdef APP-PLUS -->
+			<view class="uni-btn-v">
+				<button class="shake" @tap="shake">摇一摇</button>
+			</view>
+			<!-- #endif -->
+			<view class="uni-btn-v">
+				<button type="primary" @tap="watchAcce">监听设备的加速度变化</button>
+				<button type="primary" @tap="stopAcce">停止监听设备的加速度变化</button>
+			</view>
+			<view class="uni-textarea uni-common-mt">
+				<textarea class="acc-show" :value="value" />
+			</view>
+		</view>
+	</view>
+</template>
+<script>
+
+	export default {
+		data() {
+			return {
+				title: 'onAccelerometerChange',
+				value: ''
+			}
+		},
+		onUnload() {
+			uni.stopAccelerometer();
+		},
+		methods: {
+			//#ifdef APP-PLUS
+			shake() {
+				uni.navigateTo({
+					url: '/platforms/app-plus/shake/shake'
+				})
+			},
+			//#endif
+			watchAcce() {
+				uni.onAccelerometerChange((res) => {
+					this.value = "监听设备的加速度变化:\n" + "X轴:" + res.x.toFixed(2) + "\nY轴:" + res.y.toFixed(2) +
+						"\nZ轴:" + res.z.toFixed(2);
+				})
+			},
+			stopAcce() {
+				uni.stopAccelerometer()
+			}
+		}
+	}
+</script>
+
+<style>
+	.shake {
+		background-color: #FFCC33;
+		color: #ffffff;
+		margin-bottom: 50rpx;
+	}
+	.uni-textarea .acc-show{
+		height: 240rpx;
+	}
+</style>

+ 91 - 0
pages/API/on-compass-change/on-compass-change.vue

@@ -0,0 +1,91 @@
+<template>
+	<view>
+		<page-head :title="title"></page-head>
+		<view class="uni-padding-wrap">
+			<view class="uni-hello-text uni-center" style="padding-bottom:50rpx;">
+				旋转手机即可获取方位信息
+			</view>
+			<view class="direction">
+				<view class="bg-compass-line"></view>
+				<image class="bg-compass" src="../../../static/compass.png" :style="'transform: rotate('+direction+'deg)'"></image>
+				<view class="direction-value">
+					<text>{{direction}}</text>
+					<text class="direction-degree">o</text>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+<script>
+	export default {
+		data() {
+			return {
+				title: 'onCompassChange',
+				direction: 0
+			}
+		},
+		onReady: function () {
+			uni.onCompassChange((res) => {
+				this.direction = parseInt(res.direction)
+			})
+		},
+		onUnload() {
+			// #ifndef MP-ALIPAY
+			uni.stopCompass();
+			this.direction = 0;
+			// #endif
+
+			// #ifdef MP-ALIPAY
+			uni.offCompassChange();
+			// #endif
+		}
+	}
+</script>
+
+<style>
+	.direction {
+		position: relative;
+		margin-top: 70rpx;
+		display: flex;
+		width: 540rpx;
+		height: 540rpx;
+		align-items: center;
+		justify-content: center;
+		margin:0 auto;
+	}
+
+	.direction-value {
+		position: relative;
+		font-size: 200rpx;
+		color: #353535;
+		line-height: 1;
+		z-index: 1;
+	}
+
+	.direction-degree {
+		position: absolute;
+		top: 0;
+		right: -40rpx;
+		font-size: 60rpx;
+	}
+
+	.bg-compass {
+		position: absolute;
+		top: 0;
+		left: 0;
+		width: 540rpx;
+		height: 540rpx;
+		transition: .1s;
+	}
+
+	.bg-compass-line {
+		position: absolute;
+		left: 267rpx;
+		top: -10rpx;
+		width: 6rpx;
+		height: 56rpx;
+		background-color: #1AAD19;
+		border-radius: 999rpx;
+		z-index: 1;
+	}
+</style>

+ 75 - 0
pages/API/open-location/open-location.vue

@@ -0,0 +1,75 @@
+<template>
+	<view>
+		<page-head :title="title"></page-head>
+		<view class="uni-common-mt">
+			<form @submit="openLocation">
+				<view class="uni-list">
+					<view class="uni-list-cell">
+						<view class="uni-list-cell-left">
+							<view class="uni-label">经度</view>
+						</view>
+						<view class="uni-list-cell-db">
+							<input class="uni-input" type="text" :disabled="true" value="116.39747" name="longitude"/>
+						</view>
+					</view>
+					<view class="uni-list-cell">
+						<view class="uni-list-cell-left">
+							<view class="uni-label">纬度</view>
+						</view>
+						<view class="uni-list-cell-db">
+							<input class="uni-input" type="text" :disabled="true" value="39.9085" name="latitude"/>
+						</view>
+					</view>
+					<view class="uni-list-cell">
+						<view class="uni-list-cell-left">
+							<view class="uni-label">位置名称</view>
+						</view>
+						<view class="uni-list-cell-db">
+							<input class="uni-input" type="text" :disabled="true" value="天安门" name="name"/>
+						</view>
+					</view>
+					<view class="uni-list-cell">
+						<view class="uni-list-cell-left">
+							<view class="uni-label">详细位置</view>
+						</view>
+						<view class="uni-list-cell-db">
+							<input class="uni-input" type="text" :disabled="true" value="北京市东城区东长安街" name="address"/>
+						</view>
+					</view>
+				</view>
+				<view class="uni-padding-wrap">
+					<view class="uni-btn-v uni-common-mt">
+						<button type="primary" formType="submit">查看位置</button>
+					</view>
+				</view>
+			</form>
+		</view>
+	</view>
+</template>
+<script>
+	export default {
+		data() {
+			return {
+				title: 'openLocation'
+			}
+		},
+		methods: {
+			openLocation: function (e) {
+				console.log(e)
+				var value = e.detail.value
+				uni.openLocation({
+					longitude: Number(value.longitude),
+					latitude: Number(value.latitude),
+					name: value.name,
+					address: value.address
+				})
+			}
+		}
+	}
+</script>
+
+<style>
+	.uni-list-cell-left {
+		padding: 0 30rpx;
+	}
+</style>

+ 83 - 0
pages/API/pull-down-refresh/pull-down-refresh.vue

@@ -0,0 +1,83 @@
+<template>
+	<view>
+		<page-head :title="title"></page-head>
+		<view class="uni-padding-wrap uni-common-mt">
+			<view style="font-size: 12px; color: #666;">注:PC 不支持下拉刷新</view>
+			<view class="text" v-for="(num,index) in data" :key="index">list - {{num}}</view>
+			<view class="uni-loadmore" v-if="showLoadMore">{{loadMoreText}}</view>
+		</view>
+	</view>
+</template>
+<script>
+	export default {
+		data() {
+			return {
+				title: '下拉刷新 + 加载更多',
+				data: [],
+				loadMoreText: "加载中...",
+				showLoadMore: false,
+				max: 0
+			}
+		},
+		onLoad() {
+			this.initData();
+		},
+		onUnload() {
+			this.max = 0,
+			this.data = [],
+			this.loadMoreText = "加载更多",
+			this.showLoadMore = false;
+		},
+		onReachBottom() {
+			console.log("onReachBottom");
+			if (this.max > 40) {
+				this.loadMoreText = "没有更多数据了!"
+				return;
+			}
+			this.showLoadMore = true;
+			setTimeout(() => {
+				this.setListData();
+			}, 300);
+		},
+		onPullDownRefresh() {
+			console.log('onPullDownRefresh');
+			this.initData();
+		},
+		methods: {
+			initData(){
+				setTimeout(() => {
+					this.max = 0;
+					this.data = [];
+					let data = [];
+					this.max += 20;
+					for (var i = this.max - 19; i < this.max + 1; i++) {
+						data.push(i)
+					}
+					this.data = this.data.concat(data);
+					uni.stopPullDownRefresh();
+				}, 300);
+			},
+			setListData() {
+				let data = [];
+				this.max += 10;
+				for (var i = this.max - 9; i < this.max + 1; i++) {
+					data.push(i)
+				}
+				this.data = this.data.concat(data);
+			}
+		}
+	}
+</script>
+
+<style>
+	.text {
+		margin: 16rpx 0;
+		width:100%;
+		background-color: #fff;
+		height: 120rpx;
+		line-height: 120rpx;
+		text-align: center;
+		color: #555;
+		border-radius: 8rpx;
+	}
+</style>

+ 256 - 0
pages/API/request-payment/request-payment.vue

@@ -0,0 +1,256 @@
+<template>
+	<view>
+		<page-head :title="title"></page-head>
+		<view class="uni-padding-wrap">
+			<view style="background:#FFF; padding:50rpx 0;">
+				<view class="uni-hello-text uni-center"><text>支付金额</text></view>
+				<view class="uni-h1 uni-center uni-common-mt">
+					<text class="rmbLogo">¥</text>
+					<input class="price" type="digit" :value="price" maxlength="4" @input="priceChange" />
+				</view>
+			</view>
+			<view class="uni-btn-v uni-common-mt">
+				<!-- #ifdef MP-WEIXIN -->
+				<button type="primary" @click="weixinPay" :loading="loading">微信支付</button>
+				<!-- #endif -->
+				<!-- #ifdef APP-PLUS -->
+				<template v-if="providerList.length > 0">
+					<button v-for="(item,index) in providerList" :key="index" @click="requestPayment(item,index)" :loading="item.loading">{{item.name}}支付</button>
+				</template>
+				<!-- #endif -->
+			</view>
+		</view>
+	</view>
+</template>
+<script>
+	export default {
+		data() {
+			return {
+				title: 'request-payment',
+				loading: false,
+				price: 1,
+				providerList: []
+			}
+		},
+		onLoad: function() {
+			// #ifdef APP-PLUS
+			uni.getProvider({
+				service: "payment",
+				success: (e) => {
+					console.log("payment success:" + JSON.stringify(e));
+					let providerList = [];
+					e.provider.map((value) => {
+						switch (value) {
+							case 'alipay':
+								providerList.push({
+									name: '支付宝',
+									id: value,
+									loading: false
+								});
+								break;
+							case 'wxpay':
+								providerList.push({
+									name: '微信',
+									id: value,
+									loading: false
+								});
+								break;
+							default:
+								break;
+						}
+					})
+					this.providerList = providerList;
+				},
+				fail: (e) => {
+					console.log("获取支付通道失败:", e);
+				}
+			});
+			// #endif
+		},
+		methods: {
+			loginMpWeixin() {
+				return new Promise((resolve, reject) => {
+					uni.login({
+						provider: 'weixin',
+						success(res) {
+							console.warn('此处使用uni-id处理微信小程序登录,详情参考: https://uniapp.dcloud.net.cn/uniCloud/uni-id')
+							uni.request({
+								url: 'https://97fca9f2-41f6-449f-a35e-3f135d4c3875.bspapp.com/http/user-center',
+								method: 'POST',
+								data: {
+									action: 'loginByWeixin',
+									params: {
+										code: res.code,
+										platform: 'mp-weixin'
+									}
+								},
+								success(res) {
+									if (res.data.code !== 0) {
+										reject(new Error('获取openid失败:', res.result.msg))
+										return
+									}
+									uni.setStorageSync('openid', res.data.openid)
+									resolve(res.data.openid)
+								},
+								fail(err) {
+									reject(new Error('获取openid失败:' + err))
+								}
+							})
+						},
+						fail(err) {
+							reject(err)
+						}
+					})
+				})
+			},
+			async weixinPay() {
+				let openid = uni.getStorageSync('openid')
+				console.log("发起支付");
+				this.loading = true;
+				if (!openid) {
+					try {
+						openid = await this.loginMpWeixin()
+					} catch (e) {
+						console.error(e)
+					}
+
+					if (!openid) {
+						uni.showModal({
+							content: '获取openid失败',
+							showCancel: false
+						})
+						this.loading = false
+						return
+					}
+				}
+				this.openid = openid
+				let orderInfo = await this.getOrderInfo('wxpay')
+				if (!orderInfo) {
+					uni.showModal({
+						content: '获取支付信息失败',
+						showCancel: false
+					})
+					return
+				}
+				uni.requestPayment({
+					...orderInfo,
+					success: (res) => {
+						uni.showToast({
+							title: "感谢您的赞助!"
+						})
+					},
+					fail: (res) => {
+						uni.showModal({
+							content: "支付失败,原因为: " + res
+								.errMsg,
+							showCancel: false
+						})
+					},
+					complete: () => {
+						this.loading = false;
+					}
+				})
+			},
+			async requestPayment(e, index) {
+				this.providerList[index].loading = true;
+				const provider = e.id
+				let orderInfo = await this.getOrderInfo(provider);
+				if (!orderInfo) {
+					uni.showModal({
+						content: '获取支付信息失败',
+						showCancel: false
+					})
+					return
+				}
+				console.log('--------orderInfo--------')
+				console.log(orderInfo);
+				uni.requestPayment({
+					provider,
+					orderInfo: orderInfo,
+					success: (e) => {
+						console.log("success", e);
+						uni.showToast({
+							title: "感谢您的赞助!"
+						})
+					},
+					fail: (e) => {
+						console.log("fail", e);
+						uni.showModal({
+							content: "支付失败,原因为: " + e.errMsg,
+							showCancel: false
+						})
+					},
+					complete: () => {
+						this.providerList[index].loading = false;
+					}
+				})
+			},
+			getOrderInfo(provider) {
+				return new Promise((resolve, reject) => {
+					if (!this.price) {
+						reject(new Error('请输入金额'))
+					}
+					console.warn('此处使用uni-pay处理支付,详情参考: https://uniapp.dcloud.io/uniCloud/unipay')
+					uni.request({
+						method: 'POST',
+						url: 'https://97fca9f2-41f6-449f-a35e-3f135d4c3875.bspapp.com/http/pay',
+						data: {
+							provider,
+							openid: this.openid,
+							totalFee: Number(this.price) * 100, // 转为以分为单位
+							// #ifdef APP-PLUS
+							platform: 'app-plus',
+							// #endif
+							// #ifdef MP-WEIXIN
+							platform: 'mp-weixin',
+							// #endif
+						},
+						success(res) {
+							if (res.data.code === 0) {
+								resolve(res.data.orderInfo)
+							} else {
+								reject(new Error('获取支付信息失败' + res.data.msg))
+							}
+						},
+						fail(err) {
+							reject(new Error('请求支付接口失败' + err))
+						}
+					})
+				})
+			},
+			priceChange(e) {
+				console.log(e.detail.value)
+				this.price = e.detail.value;
+			}
+		}
+	}
+</script>
+
+<style>
+	.rmbLogo {
+		font-size: 40rpx;
+	}
+
+	button {
+		background-color: #007aff;
+		color: #ffffff;
+	}
+
+	.uni-h1.uni-center {
+		display: flex;
+		flex-direction: row;
+		justify-content: center;
+		align-items: flex-end;
+	}
+
+	.price {
+		border-bottom: 1px solid #eee;
+		width: 200rpx;
+		height: 80rpx;
+		padding-bottom: 4rpx;
+	}
+
+	.ipaPayBtn {
+		margin-top: 30rpx;
+	}
+</style>

+ 176 - 0
pages/API/request/request.vue

@@ -0,0 +1,176 @@
+<template>
+	<view>
+		<page-head :title="title"></page-head>
+		<view class="uni-padding-wrap uni-common-mt">
+			<view class="uni-hello-text">
+				请点击按钮向服务器发起请求
+			</view>
+			<view class="uni-textarea uni-common-mt">
+				<textarea :value="res"></textarea>
+			</view>
+			<view class="uni-btn-v uni-common-mt">
+				<button type="primary" @click="sendRequest" :loading="loading">发起请求(Callback)</button>
+				<button type="primary" @click="sendRequest('promise')" :loading="loading">发起请求(Promise)</button>
+				<button type="primary" @click="sendRequest('await')" :loading="loading">发起请求(Async/Await)</button>
+			</view>
+		</view>
+	</view>
+</template>
+<script>
+	const requestUrl = 'https://unidemo.dcloud.net.cn/ajax/echo/text?name=uni-app'
+	const duration = 2000
+	export default {
+		data() {
+			return {
+				title: 'request',
+				loading: false,
+				res: ''
+			}
+		},
+		methods: {
+			sendRequest(mode) {
+				this.loading = true;
+				switch (mode) {
+					case 'promise':
+						this._requestPromise();
+						break;
+					case 'await':
+						this._requestAwait();
+						break;
+					default:
+						this._request();
+						break;
+				}
+			},
+			_request() {
+				uni.request({
+					url: requestUrl,
+					dataType: 'text',
+					data: {
+						noncestr: Date.now()
+					},
+					success: (res) => {
+						console.log('request success', res)
+						uni.showToast({
+							title: '请求成功',
+							icon: 'success',
+							mask: true,
+							duration: duration
+						});
+						this.res = '请求结果 : ' + JSON.stringify(res);
+					},
+					fail: (err) => {
+						console.log('request fail', err);
+						uni.showModal({
+							content: err.errMsg,
+							showCancel: false
+						});
+					},
+					complete: () => {
+						this.loading = false;
+					}
+				});
+			},
+			_requestPromise() {
+				// #ifndef VUE3
+				uni.request({
+					url: requestUrl,
+					dataType: 'text',
+					data: {
+						noncestr: Date.now()
+					}
+				}).then(res => {
+					console.log('request success', res[1]);
+					uni.showToast({
+						title: '请求成功',
+						icon: 'success',
+						mask: true,
+						duration: duration
+					});
+					this.res = '请求结果 : ' + JSON.stringify(res[1]);
+					this.loading = false;
+				}).catch(err => {
+					console.log('request fail', err);
+					uni.showModal({
+						content: err.errMsg,
+						showCancel: false
+					});
+
+					this.loading = false;
+				});
+				// #endif
+
+				// #ifdef VUE3
+				uni.request({
+					url: requestUrl,
+					dataType: 'text',
+					data: {
+						noncestr: Date.now()
+					}
+				}).then(res => {
+					console.log('request success', res);
+					uni.showToast({
+						title: '请求成功',
+						icon: 'success',
+						mask: true,
+						duration: duration
+					});
+					this.res = '请求结果 : ' + JSON.stringify(res);
+
+					this.loading = false;
+				}).catch(err => {
+					console.log('request fail', err);
+					uni.showModal({
+						content: err.errMsg,
+						showCancel: false
+					});
+
+					this.loading = false;
+				});
+				// #endif
+			},
+			async _requestAwait() {
+				let res, err
+				// #ifndef VUE3
+				[err, res] = await uni.request({
+					url: requestUrl,
+					dataType: 'text',
+					data: {
+						noncestr: Date.now()
+					}
+				});
+				// #endif
+				// #ifdef VUE3
+				try {
+				res = await uni.request({
+					url: requestUrl,
+					dataType: 'text',
+					data: {
+						noncestr: Date.now()
+					}
+				});
+				} catch(e){
+					err=e
+				}
+				// #endif
+				if (err) {
+					console.log('request fail', err);
+					uni.showModal({
+						content: err.errMsg,
+						showCancel: false
+					});
+				} else {
+					console.log('request success', res)
+					uni.showToast({
+						title: '请求成功',
+						icon: 'success',
+						mask: true,
+						duration: duration
+					});
+					this.res = '请求结果 : ' + JSON.stringify(res);
+				}
+				this.loading = false;
+			}
+		}
+	}
+</script>

+ 109 - 0
pages/API/rewarded-video-ad/rewarded-video-ad.vue

@@ -0,0 +1,109 @@
+<template>
+    <view>
+        <page-head :title="title"></page-head>
+        <view class="uni-padding-wrap uni-common-mt">
+            <button v-if="!loadError" :loading="loading" :disabled="loading" type="primary" class="btn" @click="show">显示广告</button>
+            <button v-if="loadError" :loading="loading" :disabled="loading" type="primary" class="btn" @click="reloadAd">重新加载广告</button>
+        </view>
+        <!-- #ifndef APP-PLUS -->
+        <view class="ad-tips">
+            <text>小程序端的广告ID由小程序平台提供</text>
+        </view>
+        <!-- #endif -->
+    </view>
+</template>
+
+<script>
+    const ERROR_CODE = [-5001, -5002, -5003, -5004, -5005, -5006];
+
+    export default {
+        data() {
+            return {
+                title: '激励视频广告',
+                loading: false,
+                loadError: false
+            }
+        },
+        onReady() {
+            // #ifdef APP-PLUS
+            this.adOption = {
+                adpid: '1507000689'
+            };
+            // #endif
+            // #ifdef MP-WEIXIN
+            this.adOption = {
+                adUnitId: ''
+            };
+            // #endif
+            this.createAd();
+        },
+        methods: {
+            createAd() {
+                var rewardedVideoAd = this.rewardedVideoAd = uni.createRewardedVideoAd(this.adOption);
+                rewardedVideoAd.onLoad(() => {
+                    this.loading = false;
+                    this.loadError = false;
+                    console.log('onLoad event')
+                });
+                rewardedVideoAd.onClose((res) => {
+                    this.loading = true;
+                    // 用户点击了【关闭广告】按钮
+                    if (res && res.isEnded) {
+                        // 正常播放结束
+                        console.log("onClose " + res.isEnded);
+                    } else {
+                        // 播放中途退出
+                        console.log("onClose " + res.isEnded);
+                    }
+
+                    setTimeout(() => {
+                        uni.showToast({
+                            title: "激励视频" + (res.isEnded ? "成功" : "未") + "播放完毕",
+                            duration: 10000,
+                            position: 'bottom'
+                        })
+                    }, 500)
+                });
+                rewardedVideoAd.onError((err) => {
+                    this.loading = false;
+                    if (err.code && ERROR_CODE.indexOf(err.code) !== -1) {
+                        this.loadError = true;
+                    }
+                    console.log('onError event', err)
+                });
+                this.loading = true;
+            },
+            show() {
+                const rewardedVideoAd = this.rewardedVideoAd;
+                rewardedVideoAd.show().catch(() => {
+                    rewardedVideoAd.load()
+                        .then(() => rewardedVideoAd.show())
+                        .catch(err => {
+                            console.log('激励视频 广告显示失败', err)
+                            uni.showToast({
+                                title: err.errMsg || err.message,
+                                duration: 5000,
+                                position: 'bottom'
+                            })
+                        })
+                })
+            },
+            reloadAd() {
+                this.loading = true;
+                this.rewardedVideoAd.load();
+            }
+        }
+    }
+</script>
+
+<style>
+    .btn {
+        margin-bottom: 20px;
+    }
+
+    .ad-tips {
+        color: #999;
+        padding: 30px 0;
+        text-align: center;
+    }
+</style>

+ 158 - 0
pages/API/save-media/save-media.vue

@@ -0,0 +1,158 @@
+<template>
+	<view>
+		<page-head :title="title"></page-head>
+		<view class="uni-padding-wrap">
+			<view v-if="imagePath !== ''" class="media-box image">
+				<image class="image" mode="widthFix" :src="imagePath" />
+			</view>
+			<button type="primary"  class="uni-button" @click="saveImage">拍摄图片并保存到本地</button>
+			<view v-if="videoPath !== ''" class="media-box">
+				<video
+					id="myVideo"
+					:src="videoPath"
+					@error="videoErrorCallback"
+					enable-danmu
+					danmu-btn
+					controls
+				></video>
+			</view>
+			<button type="primary" class="uni-button" @click="saveVideo">录制视频并保存到本地</button>
+		</view>
+	</view>
+</template>
+<script>
+export default {
+	data() {
+		return {
+			title: 'saveImage/saveVideo',
+			imagePath: '',
+			videoPath: ''
+		};
+	},
+	onLoad() {},
+	methods: {
+		videoErrorCallback: function() {
+			uni.showModal({
+				content: '视频加载失败',
+				showCancel: false
+			});
+		},
+		saveImage() {
+			uni.chooseImage({
+				count: 1,
+				sourceType: ['camera'],
+				success: res => {
+					this.imagePath = res.tempFilePaths[0];
+					this.getTempFilePath(res.tempFilePaths[0], 'imageTempPath');
+				},
+				fail: (err) => {
+					// #ifdef MP
+					uni.getSetting({
+						success: (res) => {
+							let authStatus = res.authSetting['scope.camera'];
+							if (!authStatus) {
+								uni.showModal({
+									title: '授权失败',
+									content: 'Hello uni-app需要从您的相机获取图片,请在设置界面打开相关权限',
+									success: (res) => {
+										if (res.confirm) {
+											uni.openSetting()
+										}
+									}
+								})
+							}
+						}
+					})
+					// #endif
+				}
+			});
+		},
+		saveVideo() {
+			let _this = this;
+			uni.chooseVideo({
+				count: 1,
+				sourceType: ['camera'],
+				success: res => {
+					console.log(res.tempFilePath)
+					this.videoPath = res.tempFilePath;
+					this.getTempFilePath(res.tempFilePath, 'videoTempPath');
+				},
+				fail: (err) => {
+					// #ifdef MP
+					uni.getSetting({
+						success: (res) => {
+							let authStatus = res.authSetting['scope.camera'];
+							if (!authStatus) {
+								uni.showModal({
+									title: '授权失败',
+									content: 'Hello uni-app需要从您的相机获取视频,请在设置界面打开相关权限',
+									success: (res) => {
+										if (res.confirm) {
+											uni.openSetting()
+										}
+									}
+								})
+							}
+						}
+					})
+					// #endif
+				}
+			});
+		},
+
+		getTempFilePath(url, types) {
+			// 如果已经下载本地路径,那么直接储存
+			let obj = {
+				filePath: url,
+				success: () => {
+					console.log('save success');
+					uni.showModal({
+						content: '保存成功',
+						showCancel: false
+					});
+					uni.hideLoading();
+				},
+				fail: e => {
+					uni.hideLoading();
+					uni.showModal({
+						content: '保存失败',
+						showCancel: false
+					});
+				}
+			};
+			uni.showLoading({
+				title: '保存中...'
+			});
+			if (types === 'videoTempPath') {
+				uni.saveVideoToPhotosAlbum(obj);
+			} else {
+				uni.saveImageToPhotosAlbum(obj);
+			}
+		}
+	}
+};
+</script>
+
+<style>
+.media-box {
+	display: flex;
+	justify-content: center;
+	align-items: center;
+	margin: 30rpx 0;
+	width: 100%;
+}
+.image {
+	height: 400rpx;
+	overflow: hidden;
+}
+.image image {
+	width: 100%;
+	height: 100%;
+}
+video {
+	width: 100%;
+}
+.uni-button {
+	margin: 30rpx 0;
+}
+</style>

+ 76 - 0
pages/API/scan-code/scan-code.vue

@@ -0,0 +1,76 @@
+<template>
+	<view>
+		<page-head :title="title"></page-head>
+		<view class="uni-padding-wrap uni-common-mt">
+			<view class="uni-title">扫码结果:</view>
+			<view class="uni-list" v-if="result">
+				<view class="uni-cell">
+					<view class="scan-result">
+						{{result}}
+					</view>
+				</view>
+			</view>
+			<view class="uni-btn-v">
+				<button type="primary" @click="scan">扫一扫</button>
+			</view>
+		</view>
+	</view>
+</template>
+<script>
+	import permision from "@/common/permission.js"
+	export default {
+		data() {
+			return {
+				title: 'scanCode',
+				result: ''
+			}
+		},
+		methods: {
+			async scan() {
+				// #ifdef APP-PLUS
+				let status = await this.checkPermission();
+				if (status !== 1) {
+				    return;
+				}
+				// #endif
+				uni.scanCode({
+					success: (res) => {
+						this.result = res.result
+					},
+					fail: (err) => {
+						// 需要注意的是小程序扫码不需要申请相机权限
+					}
+				});
+			}
+			// #ifdef APP-PLUS
+			,
+			async checkPermission(code) {
+				let status = permision.isIOS ? await permision.requestIOS('camera') :
+					await permision.requestAndroid('android.permission.CAMERA');
+
+				if (status === null || status === 1) {
+					status = 1;
+				} else {
+					uni.showModal({
+						content: "需要相机权限",
+						confirmText: "设置",
+						success: function(res) {
+							if (res.confirm) {
+								permision.gotoAppSetting();
+							}
+						}
+					})
+				}
+				return status;
+			}
+			// #endif
+		}
+	}
+</script>
+
+<style>
+	.scan-result {
+		min-height: 50rpx;
+		line-height: 50rpx;
+	}
+</style>

+ 22 - 0
pages/API/set-navigation-bar-title/set-navigation-bar-title.test.js

@@ -0,0 +1,22 @@
+
+describe('pages/API/set-navigation-bar-title/set-navigation-bar-title.vue', () => {
+    let page
+    beforeAll(async () => {
+        // 重新reLaunch至首页,并获取首页page对象(其中 program 是uni-automator自动注入的全局对象)
+       page = await program.reLaunch('/pages/API/set-navigation-bar-title/set-navigation-bar-title')
+       
+       if (process.env.UNI_PLATFORM === "mp-weixin") {
+       	await page.waitFor(10000)
+       } else {
+       	await page.waitFor(5000)
+       }
+       
+       page = await program.currentPage()
+
+    })
+
+    it('set-navigation-bar-title 组件标题', async () => {
+    	let view = await page.$('.common-page-head-title')
+    	expect(await view.text()).toBe('nav-default')
+    })
+})

+ 0 - 0
pages/API/set-navigation-bar-title/set-navigation-bar-title.vue


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