Browse Source

JeecgUniapp移动框架 2.0版本发布,一份代码多终端适配

zhangdaiscott 3 years ago
parent
commit
e3e356136a
93 changed files with 9282 additions and 288 deletions
  1. 8 1
      App.vue
  2. 34 28
      README.md
  3. 55 0
      common/router/modules/routes.js
  4. 1 1
      common/service/config.service.js
  5. 0 1
      common/service/service.js
  6. 37 0
      common/util/vue-py.js
  7. 66 60
      common/util/work.js
  8. 83 0
      components/mescroll-uni/app-tabs.vue
  9. 55 0
      components/mescroll-uni/components/mescroll-down.css
  10. 47 0
      components/mescroll-uni/components/mescroll-down.vue
  11. 91 0
      components/mescroll-uni/components/mescroll-empty.vue
  12. 81 0
      components/mescroll-uni/components/mescroll-top.vue
  13. 46 0
      components/mescroll-uni/components/mescroll-up.css
  14. 39 0
      components/mescroll-uni/components/mescroll-up.vue
  15. 10 0
      components/mescroll-uni/mescroll-body.css
  16. 292 0
      components/mescroll-uni/mescroll-body.vue
  17. 60 0
      components/mescroll-uni/mescroll-mixins.js
  18. 34 0
      components/mescroll-uni/mescroll-uni-option.js
  19. 29 0
      components/mescroll-uni/mescroll-uni.css
  20. 862 0
      components/mescroll-uni/mescroll-uni.js
  21. 364 0
      components/mescroll-uni/mescroll-uni.vue
  22. 23 0
      components/mescroll-uni/mixins/mescroll-comp.js
  23. 48 0
      components/mescroll-uni/mixins/mescroll-more-item.js
  24. 56 0
      components/mescroll-uni/mixins/mescroll-more.js
  25. 158 0
      components/my-componets/appSelect.vue
  26. 93 0
      components/my-componets/my-date.vue
  27. 326 0
      components/my-componets/my-map.vue
  28. 79 0
      components/my-componets/my-nav.vue
  29. 546 0
      components/uni-calendar/calendar.js
  30. 179 0
      components/uni-calendar/uni-calendar-item.vue
  31. 512 0
      components/uni-calendar/uni-calendar.vue
  32. 352 0
      components/uni-calendar/util.js
  33. 54 0
      components/uni-collapse/uni-collapse.vue
  34. 0 0
      components/w-picker/areadata/areadata.js
  35. 742 0
      components/w-picker/date-picker.vue
  36. 346 0
      components/w-picker/half-picker.vue
  37. 274 0
      components/w-picker/linkage-picker.vue
  38. 344 0
      components/w-picker/range-picker.vue
  39. 183 0
      components/w-picker/region-picker.vue
  40. 129 0
      components/w-picker/selector-picker.vue
  41. 250 0
      components/w-picker/shortterm-picker.vue
  42. 218 0
      components/w-picker/time-picker.vue
  43. 26 0
      components/w-picker/w-picker.css
  44. 340 0
      components/w-picker/w-picker.vue
  45. 35 1
      pages.json
  46. 251 0
      pages/addressbook/address-book.vue
  47. 122 0
      pages/addressbook/address-detail.vue
  48. 207 0
      pages/addressbook/level-address-book.vue
  49. 128 0
      pages/addressbook/member.vue
  50. 93 0
      pages/annotation/annotationDetail.vue
  51. 308 0
      pages/annotation/annotationList.vue
  52. 59 0
      pages/common/helloWorld.vue
  53. 39 33
      pages/home/home.vue
  54. 195 58
      pages/login/login.vue
  55. 117 0
      pages/user/location.vue
  56. 70 34
      pages/user/people.vue
  57. 2 52
      pages/user/userdetail.vue
  58. 1 16
      pages/user/useredit.vue
  59. BIN
      static/blue.png
  60. BIN
      static/folder.png
  61. BIN
      static/home/128/chuchai.png
  62. BIN
      static/home/128/duanxin.png
  63. BIN
      static/home/128/gongwen.png
  64. BIN
      static/home/128/hetong.png
  65. BIN
      static/home/128/huiyi.png
  66. BIN
      static/home/128/kaoqin.png
  67. BIN
      static/home/128/kehu.png
  68. BIN
      static/home/128/liucheng.png
  69. BIN
      static/home/128/more.png
  70. BIN
      static/home/128/qingjia.png
  71. BIN
      static/home/128/qingjia1.png
  72. BIN
      static/home/128/renwu.png
  73. BIN
      static/home/128/richang.png
  74. BIN
      static/home/128/richeng.png
  75. BIN
      static/home/128/tongxun.png
  76. BIN
      static/home/128/tongzhi.png
  77. BIN
      static/home/128/toupiao.png
  78. BIN
      static/home/128/wendang.png
  79. BIN
      static/home/128/xinwen.png
  80. BIN
      static/home/128/youjian.png
  81. BIN
      static/home/128/zhoubao.png
  82. BIN
      static/home/fresher_ticket_back.png
  83. BIN
      static/home/icon_message_back.png
  84. BIN
      static/home/line2_icon1.png
  85. BIN
      static/home/line2_icon2.png
  86. BIN
      static/home/line2_icon3.png
  87. BIN
      static/home/line2_icon4.png
  88. BIN
      static/home/line4_icon1.png
  89. BIN
      static/home/line4_icon2.png
  90. BIN
      static/home/line4_icon3.png
  91. BIN
      static/home/line4_icon4.png
  92. 4 3
      store/index.js
  93. 79 0
      uni.scss

+ 8 - 1
App.vue

@@ -54,7 +54,14 @@
 					
 				}
 			})
-
+            Vue.prototype.NavBarColor='bg-gradual-blue'
+            Vue.prototype.Radio_Check_Size='scale(0.7)'
+            Vue.prototype.bannerList=[
+           		  {id:1,type: 'image',url: 'https://static.jeecg.com/upload/test/banner0_1595850438042.jpeg', link: ''},
+           		  {id:2,type: 'image',url: 'https://static.jeecg.com/upload/test/banner2_1595818081327.jpg', link: ''},
+           		  {id:3,type: 'image',url: 'https://static.jeecg.com/upload/test/oabanner-2_1595648520760.png', link: ''},
+           		  {id:4,type: 'image',url: 'https://static.jeecg.com/upload/test/banner5_1595818089013.jpeg', link: ''},
+           	]	
 			Vue.prototype.ColorList = [{
 					title: '嫣红',
 					name: 'red',

+ 34 - 28
README.md

@@ -3,28 +3,36 @@
 
 
 
-# Jeecg-Boot-Uniapp(APP开发框架)
-JEECG BOOT APP 移动解决方案,采用uniapp框架,一份代码解决多终端适配(APP、小程序、H5)
+# JeecgUniapp项目介绍
+JEECG BOOT APP 移动解决方案,采用uniapp框架,一份代码多终端适配,同时支持APP、小程序、H5!实现了与JeecgBoot平台完美对接的移动解决方案!目前已经实现登录、用户信息、通讯录、公告、移动首页、九宫格等基础功能
 
-当前最新版本: 1.0.0-beta(发布日期:2020-06-08
+当前最新版本: 2.0(发布日期:2020-07-05
 
 
 [![AUR](https://img.shields.io/badge/license-Apache%20License%202.0-blue.svg)](https://github.com/zhangdaiscott/jeecg-boot/blob/master/LICENSE)
 [![](https://img.shields.io/badge/Author-JEECG团队-orange.svg)](http://www.jeecg.com)
-[![](https://img.shields.io/badge/version-1.0.0-brightgreen.svg)](https://github.com/zhangdaiscott/jeecg-boot-uniapp)
+[![](https://img.shields.io/badge/version-2.0-brightgreen.svg)](https://github.com/zhangdaiscott/jeecg-boot-uniapp)
 [![GitHub stars](https://img.shields.io/github/stars/zhangdaiscott/jeecg-boot-uniapp.svg?style=social&label=Stars)](https://github.com/zhangdaiscott/jeecg-boot-uniapp)
 [![GitHub forks](https://img.shields.io/github/forks/zhangdaiscott/jeecg-boot-uniapp.svg?style=social&label=Fork)](https://github.com/zhangdaiscott/jeecg-boot-uniapp)
 
 
 
-项目介绍:
------------------------------------
 
-<h3 align="center">JEECG BOOT UNIAPP</h3>
+### 源码下载
+- https://github.com/jeecgboot/jeecg-uniapp
+- https://gitee.com/jeecg/jeecg-uniapp
 
-实现了与JeecgBoot平台完美对接的移动解决方案!采用技术架构:Uniapp,ColorUI,Vue。
+### 技术文档
+- 技术官网: [http://www.jeecg.com/appIndex]("http://www.jeecg.com/appIndex")
+- 技术文档: [http://doc.jeecg.com/2044258](http://doc.jeecg.com/2044258)
+- QQ交流群 : ④774126647、③816531124(满)、②769925425(满)、①284271917(满)
+- 视频教程: [零基础入门视频](https://www.bilibili.com/video/BV1sQ4y1R7Rz)
+- H5体验:  [http://uniapp.jeecg.com](http://uniapp.jeecg.com)
+- APP体验: [下载APP](https://jeecgos.oss-cn-beijing.aliyuncs.com/app/jeecgbootapp_20210518.apk)
+- 小程序体验
+
+![输入图片说明](https://oscimg.oschina.net/oscnet/up-213908261262c30a7d83a4bc9936587ed2a.png "在这里输入图片标题")
 
-项目目标: 为企业提供一套成熟的APP开发框架,轻松与Jeecg Boot对接,一份代码解决多终端适配(APP、小程序、H5),目前框架已经实现登录机制、用户信息设置、移动首页、菜单等基础功能。
 
 
 ### 功能模块
@@ -39,39 +47,37 @@ JEECG BOOT APP 移动解决方案,采用uniapp框架,一份代码解决多
 │  ├─登录页面
 │  ├─移动首页
 │  ├─个人信息设置
-├─消息中心(开发中)
+├─消息中心
 │  ├─通讯录
 │  ├─系统公告
 │  ├─消息推送
-├─示例代码(开发中)
-│  ├─调用摄像头扫码
-│  ├─获取地理位置
+├─示例代码
+│  ├─调用摄像头扫码(扫码)
+│  ├─获取地理位置(定位)
+├─新增组件
+│  ├─页面滚动
+│  ├─日历
+│  ├─时间选择
+│  ├─下拉选择
+│  ├─图片上传
 ├─。。。
 ```
 
 
-技术文档
------------------------------------
-
-- 入门视频教程: https://www.bilibili.com/video/BV1JZ4y1W7yC?p=5
-
-- 开发环境搭建:  [http://doc.jeecg.com/1751706](http://doc.jeecg.com/1751706)
-
-- 技术官网:  [http://www.jeecg.com](http://www.jeecg.com)
-
 
 
+效果预览
+----
 
-交流互动
------------------------------------
+![](https://oscimg.oschina.net/oscnet/up-9fb74025440e6066651599d78b4bc78f2cd.png)
 
-- QQ交流群 :  ②769925425、①284271917(满)
+![](https://oscimg.oschina.net/oscnet/up-7605e213638a559bba64279b6db93af3ed0.png)
 
-- 反馈问题:  [反馈问题,请按格式发Issues](https://github.com/zhangdaiscott/jeecg-boot-uniapp/issues/new)
+![](https://oscimg.oschina.net/oscnet/up-43ddd52486509ab06a920c3f99f42b8b432.png)
 
+![](https://oscimg.oschina.net/oscnet/up-02d83a8fe3fab4c0153862a9084f8a94cbb.png)
 
-效果预览
-----
+![](https://oscimg.oschina.net/oscnet/up-937a63d5e13869c40e6f1437452171d8235.png)
 
 ![输入图片说明](https://oscimg.oschina.net/oscnet/up-49e27699eb278c7c6b6748bfeaeb6c13b72.gif "在这里输入图片标题")
 

+ 55 - 0
common/router/modules/routes.js

@@ -52,6 +52,13 @@ const routes = [
 	    },
 	},
 	{
+	    path: '/pages/user/location',
+	    name: 'location',
+	    meta: {
+	        title: '定位',
+	    },
+	},
+	{
 	    path: '/pages/common/exit',
 	    name: 'exit',
 	    meta: {
@@ -64,6 +71,54 @@ const routes = [
 	    meta: {
 	        title: 'success',
 	    },
+	},{
+	  path: '/pages/addressbook/address-book',
+	  name: 'addressBook',
+	    meta: {
+	        title: 'addressBook',
+	    },
+	},
+	{
+	  path: '/pages/addressbook/level-address-book',
+	  name: 'levelAddressBook',
+	    meta: {
+	        title: 'levelAddressBook',
+	    },
+	},
+	{
+	  path: '/pages/addressbook/member',
+	  name: 'member',
+	    meta: {
+	        title: 'member',
+	    },
+	},
+	{
+	  path: '/pages/addressbook/address-detail',
+	  name: 'addressDetail',
+	    meta: {
+	        title: 'addressDetail',
+	    },
+	},
+	{
+	    path: '/pages/annotation/annotationList',
+	    name: 'annotationList',
+	    meta: {
+	        title: '通知公告',
+	    },
+	},
+	{
+	    path: '/pages/annotation/annotationDetail',
+	    name: 'annotationDetail',
+	    meta: {
+	        title: '通知详情',
+	    },
+	},
+	{
+	    path: '/pages/common/helloWorld',
+	    name: 'helloWorld',
+	    meta: {
+	        title: 'helloWorld',
+	    },
 	},
 ]
 export default routes

+ 1 - 1
common/service/config.service.js

@@ -13,4 +13,4 @@ const configService = {
 	staticDomainURL: staticDomainURL
 };
 
-export default configService
+export default configService

+ 0 - 1
common/service/service.js

@@ -39,7 +39,6 @@ http.validateStatus = (statusCode) => {
 }
 
 http.interceptor.request((config, cancel) => { /* 请求之前拦截器 */
-  tip.alert(config.baseUrl)
   config.header = {
     ...config.header,
      'X-Access-Token':getTokenStorage()

+ 37 - 0
common/util/vue-py.js

@@ -0,0 +1,37 @@
+import { pinyin } from './constants.js';
+export default {
+    chineseToPinYin: function (l1) {
+        var l2 = l1.length;
+        var I1 = '';
+        var reg = new RegExp('[a-zA-Z0-9]');
+        for (var i = 0; i < l2; i++) {
+            var val = l1.substr(i, 1);
+            var name = this.arraySearch(val, pinyin);
+            if (reg.test(val)) {
+                I1 += val;
+            } else if (name !== false) {
+                I1 += name;
+            }
+        }
+        I1 = I1.replace(/ /g, '-');
+        while (I1.indexOf('--') > 0) {
+            I1 = I1.replace('--', '-');
+        }
+        return I1;
+    },
+    arraySearch: function (l1, l2) {
+        for (var name in pinyin) {
+            if (pinyin[name].indexOf(l1) !== -1) {
+                return this.ucfirst(name);
+            }
+        }
+        return false;
+    },
+    ucfirst: function (l1) {
+        if (l1.length > 0) {
+            var first = l1.substr(0, 1).toUpperCase();
+            var spare = l1.substr(1, l1.length);
+            return first + spare;
+        }
+    }
+};

+ 66 - 60
common/util/work.js

@@ -2,71 +2,72 @@
  * 常用服务
  * useful server
  */
+const  icon_prefix="/static/home/128/"
+
 export const us = {
   data:[
     {
       title:"日报",
-      icon:"/static/icon/daycall.png",
+      icon:icon_prefix+"richang.png",
       description:"记录每天的工作经验和心得",
-      useCount:1000
+      useCount:1000,
+	  page:'helloWorld'
     },{
       title:"周报",
-      icon:"/static/icon/week.png",
+      icon:icon_prefix+"zhoubao.png",
       description:"总结每周的工作情况和下周计划",
-      useCount:10000
+      useCount:10000,
+	  page:'helloWorld'
     },{
       title:"考勤",
-      icon:"/static/icon/kaoqin.png",
+      icon:icon_prefix+"kaoqin.png",
       description:"工作考勤",
-      page:'clockHome',
-      useCount:10000
+      useCount:10000,
+	  page:'helloWorld'
     },{
       title:"日程",
-      icon:"/static/icon/richeng.png",
+      icon:icon_prefix+"richeng.png",
       description:"建立和查看个人工作安排",
       useCount:10000,
-      page:'planList'
+	  page:'helloWorld'
     },{
       title:"请假申请",
-      icon:"/static/icon/liucheng.png",
+      icon:icon_prefix+"qingjia1.png",
       description:"请假申请",
       useCount:10000,
-      page:'leave'
-    },
-      {
-          title:"出差申请",
-          icon:"/static/icon/liucheng.png",
-          description:"出差申请",
-          useCount:10000,
-          page:'businesStrip'
-      },
-      {
-          title:"公文发文",
-          icon:"/static/icon/liucheng.png",
-          description:"公文发文",
-          useCount:10000,
-          page:'docSend'
-      },
-      {
-          title:"通知公告",
-          icon:"/static/icon/tongzhi.png",
-          description:"查看企业对员工下发的通知公告",
-          useCount:10000,
-          page:'annotationList'
+	  page:'helloWorld'
+    },{
+	  title:"出差申请",
+	  icon:icon_prefix+"chuchai.png",
+	  description:"出差申请",
+	  useCount:10000,
+	  page:'helloWorld'
+    },{
+	  title:"公文发文",
+	  icon:icon_prefix+"gongwen.png",
+	  description:"公文发文",
+	  useCount:10000,
+	  page:'helloWorld'
+    },{
+	  title:"通知公告",
+	  icon:icon_prefix+"tongzhi.png",
+	  description:"查看企业对员工下发的通知公告",
+	  useCount:10000,
+	  page:'annotationList'
+    },{
+	  title:"内部邮件",
+	  icon:icon_prefix+"youjian.png",
+	  description:"查看内部消息",
+	  useCount:10000,
+	  dot:false,
+	  page:'helloWorld'
     },{
-          title:"内部邮件",
-          icon:"/static/icon/duanxin.png",
-          description:"查看内部消息",
-          useCount:10000,
-          page:'mailHome',
-          dot:false
-      },{
-          title:"通讯录",
-          icon:"/static/icon/duanxin.png",
-          description:"查看部门,组员",
-          useCount:10000,
-          page:'communication',
-      }
+	  title:"通讯录",
+	  icon:icon_prefix+"tongxun.png",
+	  description:"查看部门,组员",
+	  useCount:10000,
+	  page:'levelAddressBook'
+    }
   ]
 }
 
@@ -78,41 +79,46 @@ export const os = {
   data:[
     {
       title:"新闻中心",
-      icon:"/static/icon/daycall.png",
+      icon:icon_prefix+"xinwen.png",
       description:"新闻中心",
-      page:'columnList',
-      useCount:10000
+      useCount:10000,
+	  page:'helloWorld'
     },{
       title:"投票中心",
-      icon:"/static/icon/week.png",
+      icon:icon_prefix+"toupiao.png",
       description:"投票中心",
-      useCount:10000
+      useCount:10000,
+	  page:'helloWorld'
     },{
       title:"任务中心",
-      icon:"/static/icon/kaoqin.png",
+      icon:icon_prefix+"renwu.png",
       description:"任务中心",
-      useCount:10000
+      useCount:10000,
+	  page:'helloWorld'
     },{
       title:"文档中心",
-      icon:"/static/icon/richeng.png",
+      icon:icon_prefix+"wendang.png",
       description:"文档中心",
       useCount:10000,
-      page:'fileHome'
+	  page:'helloWorld'
     },{
       title:"合同",
-      icon:"/static/icon/liucheng.png",
+      icon:icon_prefix+"hetong.png",
       description:"合同",
-      useCount:10000
+      useCount:10000,
+	  page:'helloWorld'
     },{
       title:"会议",
-      icon:"/static/icon/tongzhi.png",
+      icon:icon_prefix+"huiyi.png",
       description:"会议",
-      useCount:10000
+      useCount:10000,
+	  page:'helloWorld'
     },{
       title:"客户关系",
-      icon:"/static/icon/duanxin.png",
+      icon:icon_prefix+"tongzhi.png",
       description:"客户关系",
-      useCount:10000
+      useCount:10000,
+	  page:'helloWorld'
     }
   ]
 }

+ 83 - 0
components/mescroll-uni/app-tabs.vue

@@ -0,0 +1,83 @@
+<template>
+	<view v-if="tabs && tabs.length" class="app-tabs" :class="{'tabs-fixed': fixed}">
+		<view class="tabs-item">
+			<view class="tab-item" v-for="(tab, i) in tabs" :class="{'active': value===i}" :key="i" @click="tabClick(i)">
+				{{getTabName(tab)}}
+			</view>
+		</view>
+		<view class="tabs-line" :style="{left:lineLift}"></view>
+	</view>
+</template>
+
+<script>
+	export default {
+		props:{
+			tabs: Array,
+			value: { // 当前显示的下标 (使用v-model语法糖: 1.props需为value; 2.需回调input事件)
+				type: [String, Number],
+				default(){
+					return 0
+				}
+			},
+			fixed: Boolean // 是否悬浮,默认false
+		},
+		computed: {
+			lineLift() {
+				return 100/this.tabs.length*(this.value + 1) - 100/(this.tabs.length*2) + '%'
+			}
+		},
+		methods: {
+			getTabName(tab){
+				return typeof tab === "object" ? tab.name : tab
+			},
+			tabClick(i){
+				if(this.value!=i){
+					this.$emit("input",i);
+					this.$emit("change",i);
+				}
+			}
+		}
+	}
+</script>
+
+<style>
+	.app-tabs{
+		position: relative;
+		height: 80rpx;
+		line-height: 80rpx;
+		font-size: 24rpx;
+		background-color: #fff;
+		border-bottom: 1rpx solid #eee;
+	}
+	.app-tabs .tabs-item{
+		display: flex;
+		text-align: center;
+		font-size: 28rpx;
+	}
+	.app-tabs .tabs-item .tab-item{
+		flex: 1;
+	}
+	.app-tabs .tabs-item .active{
+		color: red;
+	}
+	.app-tabs .tabs-line{
+		position: absolute;
+		bottom: 0;
+		width: 150rpx;
+		height: 4rpx;
+		transform: translateX(-50%);
+		border-radius: 4rpx;
+		transition: left .3s;
+		background: red;
+	}
+	
+	/*悬浮*/
+	.app-tabs.tabs-fixed{
+		z-index: 9999;
+		position: fixed;
+		top: var(--window-top);
+		left: 0;
+		width: 100%;
+	}
+</style>
+

+ 55 - 0
components/mescroll-uni/components/mescroll-down.css

@@ -0,0 +1,55 @@
+/* 下拉刷新区域 */
+.mescroll-downwarp {
+	position: absolute;
+	top: -100%;
+	left: 0;
+	width: 100%;
+	height: 100%;
+	text-align: center;
+}
+
+/* 下拉刷新--内容区,定位于区域底部 */
+.mescroll-downwarp .downwarp-content {
+	position: absolute;
+	left: 0;
+	bottom: 0;
+	width: 100%;
+	min-height: 60rpx;
+	padding: 20rpx 0;
+	text-align: center;
+}
+
+/* 下拉刷新--提示文本 */
+.mescroll-downwarp .downwarp-tip {
+	display: inline-block;
+	font-size: 28rpx;
+	vertical-align: middle;
+	margin-left: 16rpx;
+	/* color: gray; 已在style设置color,此处删去*/
+}
+
+/* 下拉刷新--旋转进度条 */
+.mescroll-downwarp .downwarp-progress {
+	display: inline-block;
+	width: 32rpx;
+	height: 32rpx;
+	border-radius: 50%;
+	border: 2rpx solid gray;
+	border-bottom-color: transparent !important; /*已在style设置border-color,此处需加 !important*/
+	vertical-align: middle;
+}
+
+/* 旋转动画 */
+.mescroll-downwarp .mescroll-rotate {
+	animation: mescrollDownRotate 0.6s linear infinite;
+}
+
+@keyframes mescrollDownRotate {
+	0% {
+		transform: rotate(0deg);
+	}
+
+	100% {
+		transform: rotate(360deg);
+	}
+}

+ 47 - 0
components/mescroll-uni/components/mescroll-down.vue

@@ -0,0 +1,47 @@
+<!-- 下拉刷新区域 -->
+<template>
+	<view v-if="mOption.use" class="mescroll-downwarp" :style="{'background-color':mescroll.optDown.bgColor,'color':mescroll.optDown.textColor}">
+		<view class="downwarp-content">
+			<view class="downwarp-progress" :class="{'mescroll-rotate': isDownLoading}" :style="{'border-color':mescroll.optDown.textColor, 'transform':downRotate}"></view>
+			<view class="downwarp-tip">{{downText}}</view>
+		</view>
+	</view>
+</template>
+
+<script>
+export default {
+	props: {
+		option: Object , // down的配置项
+		type: Number, // 下拉状态(inOffset:1, outOffset:2, showLoading:3, endDownScroll:4)
+		rate: Number // 下拉比率 (inOffset: rate<1; outOffset: rate>=1)
+	},
+	computed: {
+		// 支付宝小程序需写成计算属性,prop定义default仍报错
+		mOption(){
+			return this.option || {}
+		},
+		// 是否在加载中
+		isDownLoading(){
+			return this.type === 3
+		},
+		// 旋转的角度
+		downRotate(){
+			return 'rotate(' + 360 * this.rate + 'deg)'
+		},
+		// 文本提示
+		downText(){
+			switch (this.type){
+				case 1: return this.mOption.textInOffset;
+				case 2: return this.mOption.textOutOffset;
+				case 3: return this.mOption.textLoading;
+				case 4: return this.mOption.textLoading;
+				default: return this.mOption.textInOffset;
+			}
+		}
+	}
+};
+</script>
+
+<style>
+@import "./mescroll-down.css";
+</style>

+ 91 - 0
components/mescroll-uni/components/mescroll-empty.vue

@@ -0,0 +1,91 @@
+<!--空布局
+
+可作为独立的组件, 不使用mescroll的页面也能单独引入, 以便APP全局统一管理:
+import MescrollEmpty from '@/components/mescroll-uni/components/mescroll-empty.vue';
+<mescroll-empty v-if="isShowEmpty" :option="optEmpty" @emptyclick="emptyClick"></mescroll-empty>
+
+-->
+<template>
+	<view class="mescroll-empty" :class="{ 'empty-fixed': option.fixed }" :style="{ 'z-index': option.zIndex, top: option.top }">
+		<image v-if="icon" class="empty-icon" src="/static/nocontent-1.png" mode="widthFix" />
+		<view v-if="tip" class="empty-tip">{{ tip }}</view>
+		<view v-if="option.btnText" class="empty-btn" @click="emptyClick">{{ option.btnText }}</view>
+	</view>
+</template>
+
+<script>
+// 引入全局配置
+import GlobalOption from './../mescroll-uni-option.js';
+export default {
+	props: {
+		// empty的配置项: 默认为GlobalOption.up.empty
+		option: {
+			type: Object,
+			default() {
+				return {
+				};
+			}
+		}
+	},
+	// 使用computed获取配置,用于支持option的动态配置
+	computed: {
+		// 图标
+		icon() {
+			return this.option.icon == null ? GlobalOption.up.empty.icon : this.option.icon; // 此处不使用短路求值, 用于支持传空串不显示图标
+		},
+		// 文本提示
+		tip() {
+			return this.option.tip == null ? GlobalOption.up.empty.tip : this.option.tip; // 此处不使用短路求值, 用于支持传空串不显示文本提示
+		}
+	},
+	methods: {
+		// 点击按钮
+		emptyClick() {
+			this.$emit('emptyclick');
+		}
+	}
+};
+</script>
+
+<style>
+/* 无任何数据的空布局 */
+.mescroll-empty {
+	box-sizing: border-box;
+	width: 100%;
+	padding: 60% 50rpx;
+	text-align: center;
+}
+
+.mescroll-empty.empty-fixed {
+	z-index: 99;
+	position: absolute; /*transform会使fixed失效,最终会降级为absolute */
+	top: 100rpx;
+	left: 0;
+}
+
+.mescroll-empty .empty-icon {
+	width: 280rpx;
+	height: 280rpx;
+}
+
+.mescroll-empty .empty-tip {
+	font-size: 24rpx;
+	margin-top: -80rpx;
+	color: gray;
+}
+
+.mescroll-empty .empty-btn {
+	display: inline-block;
+	margin-top: 40rpx;
+	min-width: 200rpx;
+	padding: 18rpx;
+	font-size: 28rpx;
+	border: 1rpx solid #e04b28;
+	border-radius: 60rpx;
+	color: #e04b28;
+}
+
+.mescroll-empty .empty-btn:active {
+	opacity: 0.75;
+}
+</style>

+ 81 - 0
components/mescroll-uni/components/mescroll-top.vue

@@ -0,0 +1,81 @@
+<!-- 回到顶部的按钮 -->
+<template>
+	<image
+		v-if="mOption.src"
+		class="mescroll-totop"
+		:class="[value ? 'mescroll-totop-in' : 'mescroll-totop-out', {'mescroll-safe-bottom': mOption.safearea}]"
+		:style="{'z-index':mOption.zIndex, 'left': left, 'right': right, 'bottom':addUnit(mOption.bottom), 'width':addUnit(mOption.width), 'border-radius':addUnit(mOption.radius)}"
+		:src="mOption.src"
+		mode="widthFix"
+		@click="toTopClick"
+	/>
+</template>
+
+<script>
+export default {
+	props: {
+		// up.toTop的配置项
+		option: Object,
+		// 是否显示
+		value: false
+	},
+	computed: {
+		// 支付宝小程序需写成计算属性,prop定义default仍报错
+		mOption(){
+			return this.option || {}
+		},
+		// 优先显示左边
+		left(){
+			return this.mOption.left ? this.addUnit(this.mOption.left) : 'auto';
+		},
+		// 右边距离 (优先显示左边)
+		right() {
+			return this.mOption.left ? 'auto' : this.addUnit(this.mOption.right);
+		}
+	},
+	methods: {
+		addUnit(num){
+			if(!num) return 0;
+			if(typeof num === 'number') return num + 'rpx';
+			return num
+		},
+		toTopClick() {
+			this.$emit('input', false); // 使v-model生效
+			this.$emit('click'); // 派发点击事件
+		}
+	}
+};
+</script>
+
+<style>
+/* 回到顶部的按钮 */
+.mescroll-totop {
+	z-index: 9990;
+	position: fixed !important; /* 加上important避免编译到H5,在多mescroll中定位失效 */
+	right: 20rpx;
+	bottom: 120rpx;
+	width: 72rpx;
+	height: auto;
+	border-radius: 50%;
+	opacity: 0;
+	transition: opacity 0.5s; /* 过渡 */
+	margin-bottom: var(--window-bottom); /* css变量 */
+}
+
+ /* 适配 iPhoneX */
+.mescroll-safe-bottom{
+	margin-bottom: calc(var(--window-bottom) + constant(safe-area-inset-bottom)); /* window-bottom + 适配 iPhoneX */
+	margin-bottom: calc(var(--window-bottom) + env(safe-area-inset-bottom));
+}
+
+/* 显示 -- 淡入 */
+.mescroll-totop-in {
+	opacity: 1;
+}
+
+/* 隐藏 -- 淡出且不接收事件*/
+.mescroll-totop-out {
+	opacity: 0;
+	pointer-events: none;
+}
+</style>

+ 46 - 0
components/mescroll-uni/components/mescroll-up.css

@@ -0,0 +1,46 @@
+/* 上拉加载区域 */
+.mescroll-upwarp {
+	min-height: 60rpx;
+	padding: 30rpx 0;
+	text-align: center;
+	clear: both;
+}
+
+/*提示文本 */
+.mescroll-upwarp .upwarp-tip,
+.mescroll-upwarp .upwarp-nodata {
+	display: inline-block;
+	font-size: 28rpx;
+	vertical-align: middle;
+	/* color: gray; 已在style设置color,此处删去*/
+}
+
+.mescroll-upwarp .upwarp-tip {
+	margin-left: 16rpx;
+}
+
+/*旋转进度条 */
+.mescroll-upwarp .upwarp-progress {
+	display: inline-block;
+	width: 32rpx;
+	height: 32rpx;
+	border-radius: 50%;
+	border: 2rpx solid gray;
+	border-bottom-color: transparent !important; /*已在style设置border-color,此处需加 !important*/
+	vertical-align: middle;
+}
+
+/* 旋转动画 */
+.mescroll-upwarp .mescroll-rotate {
+	animation: mescrollUpRotate 0.6s linear infinite;
+}
+
+@keyframes mescrollUpRotate {
+	0% {
+		transform: rotate(0deg);
+	}
+
+	100% {
+		transform: rotate(360deg);
+	}
+}

+ 39 - 0
components/mescroll-uni/components/mescroll-up.vue

@@ -0,0 +1,39 @@
+<!-- 上拉加载区域 -->
+<template>
+	<view class="mescroll-upwarp" :style="{'background-color':mescroll.optUp.bgColor,'color':mescroll.optUp.textColor}">
+		<!-- 加载中 (此处不能用v-if,否则android小程序快速上拉可能会不断触发上拉回调) -->
+		<view v-show="isUpLoading">
+			<view class="upwarp-progress mescroll-rotate" :style="{'border-color':mescroll.optUp.textColor}"></view>
+			<view class="upwarp-tip">{{ mOption.textLoading }}</view>
+		</view>
+		<!-- 无数据 -->
+		<view v-if="isUpNoMore" class="upwarp-nodata">{{ mOption.textNoMore }}</view>
+	</view>
+</template>
+
+<script>
+export default {
+	props: {
+		option: Object, // up的配置项
+		type: Number // 上拉加载的状态:0(loading前),1(loading中),2(没有更多了)
+	},
+	computed: {
+		// 支付宝小程序需写成计算属性,prop定义default仍报错
+		mOption() {
+			return this.option || {};
+		},
+		// 加载中
+		isUpLoading() {
+			return this.type === 1;
+		},
+		// 没有更多了
+		isUpNoMore() {
+			return this.type === 2;
+		}
+	}
+};
+</script>
+
+<style>
+@import './mescroll-up.css';
+</style>

+ 10 - 0
components/mescroll-uni/mescroll-body.css

@@ -0,0 +1,10 @@
+page {
+	-webkit-overflow-scrolling: touch; /* 使iOS滚动流畅 */
+}
+
+.mescroll-body {
+	position: relative; /* 下拉刷新区域相对自身定位 */
+	height: auto; /* 不可固定高度,否则overflow: hidden, 可通过设置最小高度使列表不满屏仍可下拉*/
+	overflow: hidden; /* 遮住顶部下拉刷新区域 */
+	box-sizing: border-box; /* 避免设置padding出现双滚动条的问题 */
+}

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

@@ -0,0 +1,292 @@
+<template>
+	<view class="mescroll-body" :style="{'minHeight':minHeight, 'padding-top': padTop, 'padding-bottom': padBottom, 'padding-bottom': padBottomConstant, 'padding-bottom': padBottomEnv }" @touchstart="touchstartEvent" @touchmove="touchmoveEvent" @touchend="touchendEvent" @touchcancel="touchendEvent" >
+		<view class="mescroll-body-content" :style="{ transform: translateY, transition: transition }">
+			<!-- 下拉加载区域 (支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-down组件实现)-->
+			<!-- <mescroll-down :option="mescroll.optDown" :type="downLoadType" :rate="downRate"></mescroll-down> -->
+			<view v-if="mescroll.optDown.use" class="mescroll-downwarp" :style="{'background-color':mescroll.optDown.bgColor,'color':mescroll.optDown.textColor}">
+				<view class="downwarp-content">
+					<view class="downwarp-progress" :class="{'mescroll-rotate': isDownLoading}" :style="{'border-color':mescroll.optDown.textColor, 'transform': downRotate}"></view>
+					<view class="downwarp-tip">{{downText}}</view>
+				</view>
+			</view>
+	
+			<!-- 列表内容 -->
+			<slot></slot>
+
+			<!-- 空布局 -->
+			<mescroll-empty v-if="isShowEmpty" :option="mescroll.optUp.empty" @emptyclick="emptyClick"></mescroll-empty>
+
+			<!-- 上拉加载区域 (下拉刷新时不显示, 支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-up组件实现)-->
+			<!-- <mescroll-up v-if="mescroll.optUp.use && !isDownLoading" :option="mescroll.optUp" :type="upLoadType"></mescroll-up> -->
+			<view v-if="mescroll.optUp.use && !isDownLoading" class="mescroll-upwarp" :style="{'background-color':mescroll.optUp.bgColor,'color':mescroll.optUp.textColor}">
+				<!-- 加载中 (此处不能用v-if,否则android小程序快速上拉可能会不断触发上拉回调) -->
+				<view v-show="upLoadType===1">
+					<view class="upwarp-progress mescroll-rotate" :style="{'border-color':mescroll.optUp.textColor}"></view>
+					<view class="upwarp-tip">{{ mescroll.optUp.textLoading }}</view>
+				</view>
+				<!-- 无数据 -->
+				<view v-if="upLoadType===2" class="upwarp-nodata">{{ mescroll.optUp.textNoMore }}</view>
+			</view>
+		</view>
+
+		<!-- 回到顶部按钮 (fixed元素需写在transform外面,防止降级为absolute)-->
+		<mescroll-top v-model="isShowToTop" :option="mescroll.optUp.toTop" @click="toTopClick"></mescroll-top>
+	</view>
+</template>
+
+<script>
+	// 引入mescroll-uni.js,处理核心逻辑
+	import MeScroll from './mescroll-uni.js';
+	// 引入全局配置
+	import GlobalOption from './mescroll-uni-option.js';
+	// 引入空布局组件
+	import MescrollEmpty from './components/mescroll-empty.vue';
+	// 引入回到顶部组件
+	import MescrollTop from './components/mescroll-top.vue';
+	
+	export default {
+		components: {
+			MescrollEmpty,
+			MescrollTop
+		},
+		data() {
+			return {
+				mescroll: {optDown:{},optUp:{}}, // mescroll实例
+				downHight: 0, //下拉刷新: 容器高度
+				downRate: 0, // 下拉比率(inOffset: rate<1; outOffset: rate>=1)
+				downLoadType: 4, // 下拉刷新状态 (inOffset:1, outOffset:2, showLoading:3, endDownScroll:4)
+				upLoadType: 0, // 上拉加载状态:0(loading前),1(loading中),2(没有更多了)
+				isShowEmpty: false, // 是否显示空布局
+				isShowToTop: false, // 是否显示回到顶部按钮
+				windowHeight: 0, // 可使用窗口的高度
+				statusBarHeight: 0, // 状态栏高度
+				isSafearea: false // 支持安全区
+			};
+		},
+		props: {
+			down: Object, // 下拉刷新的参数配置
+			up: Object, // 上拉加载的参数配置
+			top: [String, Number], // 下拉布局往下的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
+			topbar: Boolean, // top的偏移量是否加上状态栏高度, 默认false (使用场景:取消原生导航栏时,配置此项可自动加上状态栏高度的偏移量)
+			bottom: [String, Number], // 上拉布局往上的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
+			safearea: Boolean, // bottom的偏移量是否加上底部安全区的距离, 默认false (需要适配iPhoneX时使用)
+			height: [String, Number] // 指定mescroll最小高度,默认windowHeight,使列表不满屏仍可下拉
+		},
+		computed: {
+			// mescroll最小高度,默认windowHeight,使列表不满屏仍可下拉
+			minHeight(){
+				return this.toPx(this.height || '100%') + 'px'
+			},
+			// 下拉布局往下偏移的距离 (px)
+			numTop() {
+				return this.toPx(this.top) + (this.topbar ? this.statusBarHeight : 0);
+			},
+			padTop() {
+				return this.numTop + 'px';
+			},
+			// 上拉布局往上偏移 (px)
+			numBottom() {
+				return this.toPx(this.bottom);
+			},
+			padBottom() {
+				return this.numBottom + 'px';
+			},
+			padBottomConstant() {
+				return this.isSafearea ? 'calc(' + this.padBottom + ' + constant(safe-area-inset-bottom))' : this.padBottom;
+			},
+			padBottomEnv() {
+				return this.isSafearea ? 'calc(' + this.padBottom + ' + env(safe-area-inset-bottom))' : this.padBottom;
+			},
+			// 是否为重置下拉的状态
+			isDownReset() {
+				return this.downLoadType === 3 || this.downLoadType === 4;
+			},
+			// 过渡
+			transition() {
+				return this.isDownReset ? 'transform 300ms' : this.downTransition;
+			},
+			translateY() {
+				return this.downHight > 0 ? 'translateY(' + this.downHight + 'px)' : ''; // transform会使fixed失效,需注意把fixed元素写在mescroll之外
+			},
+			// 是否在加载中
+			isDownLoading(){
+				return this.downLoadType === 3
+			},
+			// 旋转的角度
+			downRotate(){
+				return 'rotate(' + 360 * this.downRate + 'deg)'
+			},
+			// 文本提示
+			downText(){
+				switch (this.downLoadType){
+					case 1: return this.mescroll.optDown.textInOffset;
+					case 2: return this.mescroll.optDown.textOutOffset;
+					case 3: return this.mescroll.optDown.textLoading;
+					case 4: return this.mescroll.optDown.textLoading;
+					default: return this.mescroll.optDown.textInOffset;
+				}
+			}
+		},
+		methods: {
+			//number,rpx,upx,px,% --> px的数值
+			toPx(num) {
+				if (typeof num === 'string') {
+					if (num.indexOf('px') !== -1) {
+						if (num.indexOf('rpx') !== -1) {
+							// "10rpx"
+							num = num.replace('rpx', '');
+						} else if (num.indexOf('upx') !== -1) {
+							// "10upx"
+							num = num.replace('upx', '');
+						} else {
+							// "10px"
+							return Number(num.replace('px', ''));
+						}
+					} else if (num.indexOf('%') !== -1) {
+						// 传百分比,则相对于windowHeight,传"10%"则等于windowHeight的10%
+						let rate = Number(num.replace('%', '')) / 100;
+						return this.windowHeight * rate;
+					}
+				}
+				return num ? uni.upx2px(Number(num)) : 0;
+			},
+			//注册列表touchstart事件,用于下拉刷新
+			touchstartEvent(e) {
+				this.mescroll.touchstartEvent(e);
+			},
+			//注册列表touchmove事件,用于下拉刷新
+			touchmoveEvent(e) {
+				this.mescroll.touchmoveEvent(e);
+			},
+			//注册列表touchend事件,用于下拉刷新
+			touchendEvent(e) {
+				this.mescroll.touchendEvent(e);
+			},
+			// 点击空布局的按钮回调
+			emptyClick() {
+				this.$emit('emptyclick', this.mescroll);
+			},
+			// 点击回到顶部的按钮回调
+			toTopClick() {
+				this.mescroll.scrollTo(0, this.mescroll.optUp.toTop.duration); // 执行回到顶部
+				this.$emit('topclick', this.mescroll); // 派发点击回到顶部按钮的回调
+			}
+		},
+		// 使用created初始化mescroll对象; 如果用mounted部分css样式编译到H5会失效
+		created() {
+			let vm = this;
+
+			let diyOption = {
+				// 下拉刷新的配置
+				down: {
+					inOffset(mescroll) {
+						vm.downLoadType = 1; // 下拉的距离进入offset范围内那一刻的回调 (自定义mescroll组件时,此行不可删)
+					},
+					outOffset(mescroll) {
+						vm.downLoadType = 2; // 下拉的距离大于offset那一刻的回调 (自定义mescroll组件时,此行不可删)
+					},
+					onMoving(mescroll, rate, downHight) {
+						// 下拉过程中的回调,滑动过程一直在执行;
+						vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
+						vm.downRate = rate; //下拉比率 (inOffset: rate<1; outOffset: rate>=1)
+					},
+					showLoading(mescroll, downHight) {
+						vm.downLoadType = 3; // 显示下拉刷新进度的回调 (自定义mescroll组件时,此行不可删)
+						vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
+					},
+					endDownScroll(mescroll) {
+						vm.downLoadType = 4; // 结束下拉 (自定义mescroll组件时,此行不可删)
+						vm.downHight = 0; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
+					},
+					// 派发下拉刷新的回调
+					callback: function(mescroll) {
+						vm.$emit('down', mescroll);
+					}
+				},
+				// 上拉加载的配置
+				up: {
+					// 显示加载中的回调
+					showLoading() {
+						vm.upLoadType = 1;
+					},
+					// 显示无更多数据的回调
+					showNoMore() {
+						vm.upLoadType = 2;
+					},
+					// 隐藏上拉加载的回调
+					hideUpScroll() {
+						vm.upLoadType = 0;
+					},
+					// 空布局
+					empty: {
+						onShow(isShow) {
+							// 显示隐藏的回调
+							vm.isShowEmpty = isShow;
+						}
+					},
+					// 回到顶部
+					toTop: {
+						onShow(isShow) {
+							// 显示隐藏的回调
+							vm.isShowToTop = isShow;
+						}
+					},
+					// 派发上拉加载的回调
+					callback: function(mescroll) {
+						vm.$emit('up', mescroll);
+					}
+				}
+			};
+
+			MeScroll.extend(diyOption, GlobalOption); // 混入全局的配置
+			let myOption = JSON.parse(
+				JSON.stringify({
+					down: vm.down,
+					up: vm.up
+				})
+			); // 深拷贝,避免对props的影响
+			MeScroll.extend(myOption, diyOption); // 混入具体界面的配置
+
+			// 初始化MeScroll对象
+			vm.mescroll = new MeScroll(myOption, true); // 传入true,标记body为滚动区域
+			// init回调mescroll对象
+			vm.$emit('init', vm.mescroll);
+
+			// 设置高度
+			const sys = uni.getSystemInfoSync();
+			if (sys.windowHeight) vm.windowHeight = sys.windowHeight;
+			if (sys.statusBarHeight) vm.statusBarHeight = sys.statusBarHeight;
+			// 使down的bottomOffset生效
+			vm.mescroll.setBodyHeight(sys.windowHeight);
+			// mescroll-body在Android小程序下拉会卡顿,无法像mescroll-uni那样通过设置"disableScroll":true解决,只能用动画过渡缓解
+			// #ifdef MP
+			if(sys.platform == "android") vm.downTransition = 'transform 200ms'
+			// #endif
+
+			// 因为使用的是page的scroll,这里需自定义scrollTo
+			vm.mescroll.resetScrollTo((y, t) => {
+				uni.pageScrollTo({
+					scrollTop: y,
+					duration: t
+				})
+			});
+
+			// 具体的界面如果不配置up.toTop.safearea,则取本vue的safearea值
+			if(sys.platform == "ios"){
+				vm.isSafearea = vm.safearea;
+				if (vm.up && vm.up.toTop && vm.up.toTop.safearea != null) {} else {
+					vm.mescroll.optUp.toTop.safearea = vm.safearea;
+				}
+			}else{
+				vm.isSafearea = false
+				vm.mescroll.optUp.toTop.safearea = false
+			}
+		}
+	};
+</script>
+
+<style>
+	@import "./mescroll-body.css";
+	@import "./components/mescroll-down.css";
+	@import './components/mescroll-up.css';
+</style>

+ 60 - 0
components/mescroll-uni/mescroll-mixins.js

@@ -0,0 +1,60 @@
+// mescroll-body 和 mescroll-uni 通用
+
+// import MescrollUni from "./mescroll-uni.vue";
+// import MescrollBody from "./mescroll-body.vue";
+
+const MescrollMixin = {
+	// components: { // 非H5端无法通过mixin注册组件, 只能在main.js中注册全局组件或具体界面中注册
+	// 	MescrollUni,
+	// 	MescrollBody
+	// },
+	data() {
+		return {
+			mescroll: null //mescroll实例对象
+		}
+	},
+	// 注册系统自带的下拉刷新 (配置down.native为true时生效, 还需在pages配置enablePullDownRefresh:true;详请参考mescroll-native的案例)
+	onPullDownRefresh(){
+		this.mescroll && this.mescroll.onPullDownRefresh();
+	},
+	// 注册列表滚动事件,用于判定在顶部可下拉刷新,在指定位置可显示隐藏回到顶部按钮 (此方法为页面生命周期,无法在子组件中触发, 仅在mescroll-body生效)
+	onPageScroll(e) {
+		this.mescroll && this.mescroll.onPageScroll(e);
+	},
+	// 注册滚动到底部的事件,用于上拉加载 (此方法为页面生命周期,无法在子组件中触发, 仅在mescroll-body生效)
+	onReachBottom() {
+		this.mescroll && this.mescroll.onReachBottom();
+	},
+	methods: {
+		// mescroll组件初始化的回调,可获取到mescroll对象
+		mescrollInit(mescroll) {
+			this.mescroll = mescroll;
+			this.mescrollInitByRef(); // 兼容字节跳动小程序
+		},
+		// 以ref的方式初始化mescroll对象 (兼容字节跳动小程序: http://www.mescroll.com/qa.html?v=20200107#q26)
+		mescrollInitByRef() {
+			if(!this.mescroll || !this.mescroll.resetUpScroll){
+				let mescrollRef = this.$refs.mescrollRef;
+				if(mescrollRef) this.mescroll = mescrollRef.mescroll
+			}
+		},
+		// 下拉刷新的回调
+		downCallback() {
+			// mixin默认resetUpScroll
+			this.mescroll.resetUpScroll()
+		},
+		// 上拉加载的回调
+		upCallback() {
+			// mixin默认延时500自动结束加载
+			setTimeout(()=>{
+				this.mescroll.endErr();
+			}, 500)
+		}
+	},
+	mounted() {
+		this.mescrollInitByRef(); // 兼容字节跳动小程序, 避免未设置@init或@init此时未能取到ref的情况
+	}
+	
+}
+
+export default MescrollMixin;

+ 34 - 0
components/mescroll-uni/mescroll-uni-option.js

@@ -0,0 +1,34 @@
+// 全局配置
+// mescroll-body 和 mescroll-uni 通用
+const GlobalOption = {
+	down: {
+		// 其他down的配置参数也可以写,这里只展示了常用的配置:
+		textInOffset: '下拉刷新', // 下拉的距离在offset范围内的提示文本
+		textOutOffset: '释放更新', // 下拉的距离大于offset范围的提示文本
+		textLoading: '加载中 ...', // 加载中的提示文本
+		offset: 80, // 在列表顶部,下拉大于80px,松手即可触发下拉刷新的回调
+		native: false // 是否使用系统自带的下拉刷新; 默认false; 仅在mescroll-body生效 (值为true时,还需在pages配置enablePullDownRefresh:true;详请参考mescroll-native的案例)
+	},
+	up: {
+		// 其他up的配置参数也可以写,这里只展示了常用的配置:
+		textLoading: '加载中 ...', // 加载中的提示文本
+		textNoMore: '-- END --', // 没有更多数据的提示文本
+		offset: 80, // 距底部多远时,触发upCallback
+		isBounce: false, // 默认禁止橡皮筋的回弹效果, 必读事项: http://www.mescroll.com/qa.html?v=190725#q25
+		toTop: {
+			// 回到顶部按钮,需配置src才显示
+			src: "http://www.mescroll.com/img/mescroll-totop.png?v=1", // 图片路径 (建议放入static目录, 如 /static/img/mescroll-totop.png )
+			offset: 1000, // 列表滚动多少距离才显示回到顶部按钮,默认1000px
+			right: 20, // 到右边的距离, 默认20 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx)
+			bottom: 120, // 到底部的距离, 默认120 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx)
+			width: 72 // 回到顶部图标的宽度, 默认72 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx)
+		},
+		empty: {
+			use: true, // 是否显示空布局
+			icon: "http://www.mescroll.com/img/mescroll-empty.png?v=1", // 图标路径 (建议放入static目录, 如 /static/img/mescroll-empty.png )
+			tip: '~ 暂无相关数据 ~' // 提示
+		}
+	}
+}
+
+export default GlobalOption

+ 29 - 0
components/mescroll-uni/mescroll-uni.css

@@ -0,0 +1,29 @@
+page {
+	height: 100%;
+	box-sizing: border-box; /* 避免设置padding出现双滚动条的问题 */
+}
+
+.mescroll-uni-warp{
+	height: 100%;
+}
+
+.mescroll-uni {
+	position: relative;
+	width: 100%;
+	height: 100%;
+	min-height: 200rpx;
+	overflow-y: auto;
+	box-sizing: border-box; /* 避免设置padding出现双滚动条的问题 */
+}
+
+/* 定位的方式固定高度 */
+.mescroll-uni-fixed{
+	z-index: 1;
+	position: fixed;
+	top: 0;
+	left: 0;
+	right: 0;
+	bottom: 0;
+	width: auto; /* 使right生效 */
+	height: auto; /* 使bottom生效 */
+}

+ 862 - 0
components/mescroll-uni/mescroll-uni.js

@@ -0,0 +1,862 @@
+/* mescroll
+ * version 1.2.5
+ * 2020-03-15 wenju
+ * http://www.mescroll.com
+ */
+
+export default function MeScroll(options, isScrollBody) {
+	let me = this;
+	me.version = '1.2.5'; // mescroll版本号
+	me.options = options || {}; // 配置
+	me.isScrollBody = isScrollBody || false; // 滚动区域是否为原生页面滚动; 默认为scroll-view
+
+	me.isDownScrolling = false; // 是否在执行下拉刷新的回调
+	me.isUpScrolling = false; // 是否在执行上拉加载的回调
+	let hasDownCallback = me.options.down && me.options.down.callback; // 是否配置了down的callback
+
+	// 初始化下拉刷新
+	me.initDownScroll();
+	// 初始化上拉加载,则初始化
+	me.initUpScroll();
+
+	// 自动加载
+	setTimeout(function() { // 待主线程执行完毕再执行,避免new MeScroll未初始化,在回调获取不到mescroll的实例
+		// 自动触发下拉刷新 (只有配置了down的callback才自动触发下拉刷新)
+		if (me.optDown.use && me.optDown.auto && hasDownCallback) {
+			if (me.optDown.autoShowLoading) {
+				me.triggerDownScroll(); // 显示下拉进度,执行下拉回调
+			} else {
+				me.optDown.callback && me.optDown.callback(me); // 不显示下拉进度,直接执行下拉回调
+			}
+		}
+		// 自动触发上拉加载
+		setTimeout(function(){ // 延时确保先执行down的callback,再执行up的callback,因为部分小程序emit是异步,会导致isUpAutoLoad判断有误
+			me.optUp.use && me.optUp.auto && !me.isUpAutoLoad && me.triggerUpScroll();
+		},100)
+	}, 30); // 需让me.optDown.inited和me.optUp.inited先执行
+}
+
+/* 配置参数:下拉刷新 */
+MeScroll.prototype.extendDownScroll = function(optDown) {
+	// 下拉刷新的配置
+	MeScroll.extend(optDown, {
+		use: true, // 是否启用下拉刷新; 默认true
+		auto: true, // 是否在初始化完毕之后自动执行下拉刷新的回调; 默认true
+		native: false, // 是否使用系统自带的下拉刷新; 默认false; 仅mescroll-body生效 (值为true时,还需在pages配置enablePullDownRefresh:true;详请参考mescroll-native的案例)
+		autoShowLoading: false, // 如果设置auto=true(在初始化完毕之后自动执行下拉刷新的回调),那么是否显示下拉刷新的进度; 默认false
+		isLock: false, // 是否锁定下拉刷新,默认false;
+		offset: 80, // 在列表顶部,下拉大于80px,松手即可触发下拉刷新的回调
+		startTop: 100, // scroll-view滚动到顶部时,此时的scroll-top不一定为0, 此值用于控制最大的误差
+		fps: 80, // 下拉节流 (值越大每秒刷新频率越高)
+		inOffsetRate: 1, // 在列表顶部,下拉的距离小于offset时,改变下拉区域高度比例;值小于1且越接近0,高度变化越小,表现为越往下越难拉
+		outOffsetRate: 0.2, // 在列表顶部,下拉的距离大于offset时,改变下拉区域高度比例;值小于1且越接近0,高度变化越小,表现为越往下越难拉
+		bottomOffset: 20, // 当手指touchmove位置在距离body底部20px范围内的时候结束上拉刷新,避免Webview嵌套导致touchend事件不执行
+		minAngle: 45, // 向下滑动最少偏移的角度,取值区间  [0,90];默认45度,即向下滑动的角度大于45度则触发下拉;而小于45度,将不触发下拉,避免与左右滑动的轮播等组件冲突;
+		textInOffset: '下拉刷新', // 下拉的距离在offset范围内的提示文本
+		textOutOffset: '释放更新', // 下拉的距离大于offset范围的提示文本
+		textLoading: '加载中 ...', // 加载中的提示文本
+		bgColor: "transparent", // 背景颜色 (建议在pages.json中再设置一下backgroundColorTop)
+		textColor: "gray", // 文本颜色 (当bgColor配置了颜色,而textColor未配置时,则textColor会默认为白色)
+		inited: null, // 下拉刷新初始化完毕的回调
+		inOffset: null, // 下拉的距离进入offset范围内那一刻的回调
+		outOffset: null, // 下拉的距离大于offset那一刻的回调
+		onMoving: null, // 下拉过程中的回调,滑动过程一直在执行; rate下拉区域当前高度与指定距离的比值(inOffset: rate<1; outOffset: rate>=1); downHight当前下拉区域的高度
+		beforeLoading: null, // 准备触发下拉刷新的回调: 如果return true,将不触发showLoading和callback回调; 常用来完全自定义下拉刷新, 参考案例【淘宝 v6.8.0】
+		showLoading: null, // 显示下拉刷新进度的回调
+		afterLoading: null, // 准备结束下拉的回调. 返回结束下拉的延时执行时间,默认0ms; 常用于结束下拉之前再显示另外一小段动画,才去隐藏下拉刷新的场景, 参考案例【dotJump】
+		endDownScroll: null, // 结束下拉刷新的回调
+		callback: function(mescroll) {
+			// 下拉刷新的回调;默认重置上拉加载列表为第一页
+			mescroll.resetUpScroll();
+		}
+	})
+}
+
+/* 配置参数:上拉加载 */
+MeScroll.prototype.extendUpScroll = function(optUp) {
+	// 上拉加载的配置
+	MeScroll.extend(optUp, {
+		use: true, // 是否启用上拉加载; 默认true
+		auto: true, // 是否在初始化完毕之后自动执行上拉加载的回调; 默认true
+		isLock: false, // 是否锁定上拉加载,默认false;
+		isBoth: true, // 上拉加载时,如果滑动到列表顶部是否可以同时触发下拉刷新;默认true,两者可同时触发;
+		isBounce: false, // 默认禁止橡皮筋的回弹效果, 必读事项: http://www.mescroll.com/qa.html?v=190725#q25
+		callback: null, // 上拉加载的回调;function(page,mescroll){ }
+		page: {
+			num: 0, // 当前页码,默认0,回调之前会加1,即callback(page)会从1开始
+			size: 10, // 每页数据的数量
+			time: null // 加载第一页数据服务器返回的时间; 防止用户翻页时,后台新增了数据从而导致下一页数据重复;
+		},
+		noMoreSize: 5, // 如果列表已无数据,可设置列表的总数量要大于等于5条才显示无更多数据;避免列表数据过少(比如只有一条数据),显示无更多数据会不好看
+		offset: 80, // 距底部多远时,触发upCallback
+		textLoading: '加载中 ...', // 加载中的提示文本
+		textNoMore: '-- END --', // 没有更多数据的提示文本
+		bgColor: "transparent", // 背景颜色 (建议在pages.json中再设置一下backgroundColorBottom)
+		textColor: "gray", // 文本颜色 (当bgColor配置了颜色,而textColor未配置时,则textColor会默认为白色)
+		inited: null, // 初始化完毕的回调
+		showLoading: null, // 显示加载中的回调
+		showNoMore: null, // 显示无更多数据的回调
+		hideUpScroll: null, // 隐藏上拉加载的回调
+		errDistance: 60, // endErr的时候需往上滑动一段距离,使其往下滑动时再次触发onReachBottom,仅mescroll-body生效
+		toTop: {
+			// 回到顶部按钮,需配置src才显示
+			src: null, // 图片路径,默认null (绝对路径或网络图)
+			offset: 1000, // 列表滚动多少距离才显示回到顶部按钮,默认1000
+			duration: 300, // 回到顶部的动画时长,默认300ms (当值为0或300则使用系统自带回到顶部,更流畅; 其他值则通过step模拟,部分机型可能不够流畅,所以非特殊情况不建议修改此项)
+			btnClick: null, // 点击按钮的回调
+			onShow: null, // 是否显示的回调
+			zIndex: 9990, // fixed定位z-index值
+			left: null, // 到左边的距离, 默认null. 此项有值时,right不生效. (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx)
+			right: 20, // 到右边的距离, 默认20 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx)
+			bottom: 120, // 到底部的距离, 默认120 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx)
+			safearea: false, // bottom的偏移量是否加上底部安全区的距离, 默认false, 需要适配iPhoneX时使用 (具体的界面如果不配置此项,则取本vue的safearea值)
+			width: 72, // 回到顶部图标的宽度, 默认72 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx)
+			radius: "50%" // 圆角, 默认"50%" (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx)
+		},
+		empty: {
+			use: true, // 是否显示空布局
+			icon: null, // 图标路径
+			tip: '~ 暂无相关数据 ~', // 提示
+			btnText: '', // 按钮
+			btnClick: null, // 点击按钮的回调
+			onShow: null, // 是否显示的回调
+			fixed: false, // 是否使用fixed定位,默认false; 配置fixed为true,以下的top和zIndex才生效 (transform会使fixed失效,最终会降级为absolute)
+			top: "100rpx", // fixed定位的top值 (完整的单位值,如 "10%"; "100rpx")
+			zIndex: 99 // fixed定位z-index值
+		},
+		onScroll: false // 是否监听滚动事件
+	})
+}
+
+/* 配置参数 */
+MeScroll.extend = function(userOption, defaultOption) {
+	if (!userOption) return defaultOption;
+	for (let key in defaultOption) {
+		if (userOption[key] == null) {
+			let def = defaultOption[key];
+			if (def != null && typeof def === 'object') {
+				userOption[key] = MeScroll.extend({}, def); // 深度匹配
+			} else {
+				userOption[key] = def;
+			}
+		} else if (typeof userOption[key] === 'object') {
+			MeScroll.extend(userOption[key], defaultOption[key]); // 深度匹配
+		}
+	}
+	return userOption;
+}
+
+/* 简单判断是否配置了颜色 (非透明,非白色) */
+MeScroll.prototype.hasColor = function(color) {
+	if(!color) return false;
+	let c = color.toLowerCase();
+	return c != "#fff" && c != "#ffffff" && c != "transparent" && c != "white"
+}
+
+/* -------初始化下拉刷新------- */
+MeScroll.prototype.initDownScroll = function() {
+	let me = this;
+	// 配置参数
+	me.optDown = me.options.down || {};
+	if(!me.optDown.textColor && me.hasColor(me.optDown.bgColor)) me.optDown.textColor = "#fff"; // 当bgColor有值且textColor未设置,则textColor默认白色
+	me.extendDownScroll(me.optDown);
+	
+	// 如果是mescroll-body且配置了native,则禁止自定义的下拉刷新
+	if(me.isScrollBody && me.optDown.native){
+		me.optDown.use = false
+	}else{
+		me.optDown.native = false // 仅mescroll-body支持,mescroll-uni不支持
+	}
+	
+	me.downHight = 0; // 下拉区域的高度
+
+	// 在页面中加入下拉布局
+	if (me.optDown.use && me.optDown.inited) {
+		// 初始化完毕的回调
+		setTimeout(function() { // 待主线程执行完毕再执行,避免new MeScroll未初始化,在回调获取不到mescroll的实例
+			me.optDown.inited(me);
+		}, 0)
+	}
+}
+
+/* 列表touchstart事件 */
+MeScroll.prototype.touchstartEvent = function(e) {
+	if (!this.optDown.use) return;
+
+	this.startPoint = this.getPoint(e); // 记录起点
+	this.startTop = this.getScrollTop(); // 记录此时的滚动条位置
+	this.lastPoint = this.startPoint; // 重置上次move的点
+	this.maxTouchmoveY = this.getBodyHeight() - this.optDown.bottomOffset; // 手指触摸的最大范围(写在touchstart避免body获取高度为0的情况)
+	this.inTouchend = false; // 标记不是touchend
+}
+
+/* 列表touchmove事件 */
+MeScroll.prototype.touchmoveEvent = function(e) {
+	// #ifdef H5
+	window.isPreventDefault = false // 标记不需要阻止window事件
+	// #endif
+	
+	if (!this.optDown.use) return;
+	if (!this.startPoint) return;
+	let me = this;
+
+	// 节流
+	let t = new Date().getTime();
+	if (me.moveTime && t - me.moveTime < me.moveTimeDiff) { // 小于节流时间,则不处理
+		return;
+	} else {
+		me.moveTime = t
+		if(!me.moveTimeDiff) me.moveTimeDiff = 1000 / me.optDown.fps
+	}
+
+	let scrollTop = me.getScrollTop(); // 当前滚动条的距离
+	let curPoint = me.getPoint(e); // 当前点
+
+	let moveY = curPoint.y - me.startPoint.y; // 和起点比,移动的距离,大于0向下拉,小于0向上拉
+
+	// 向下拉 && 在顶部
+	// mescroll-body,直接判定在顶部即可
+	// scroll-view在滚动时不会触发touchmove,当触顶/底/左/右时,才会触发touchmove
+	// scroll-view滚动到顶部时,scrollTop不一定为0; 在iOS的APP中scrollTop可能为负数,不一定和startTop相等
+	if (moveY > 0 && (
+			(me.isScrollBody && scrollTop <= 0)
+			||
+			(!me.isScrollBody && (scrollTop <= 0 || (scrollTop <= me.optDown.startTop && scrollTop === me.startTop)) )
+		)) {
+		// 可下拉的条件
+		if (!me.inTouchend && !me.isDownScrolling && !me.optDown.isLock && (!me.isUpScrolling || (me.isUpScrolling &&
+				me.optUp.isBoth))) {
+
+			// 下拉的角度是否在配置的范围内
+			let angle = me.getAngle(me.lastPoint, curPoint); // 两点之间的角度,区间 [0,90]
+			if (angle < me.optDown.minAngle) return; // 如果小于配置的角度,则不往下执行下拉刷新
+
+			// 如果手指的位置超过配置的距离,则提前结束下拉,避免Webview嵌套导致touchend无法触发
+			if (me.maxTouchmoveY > 0 && curPoint.y >= me.maxTouchmoveY) {
+				me.inTouchend = true; // 标记执行touchend
+				me.touchendEvent(); // 提前触发touchend
+				return;
+			}
+			
+			// #ifdef H5
+			window.isPreventDefault = true // 标记阻止window事件
+			// #endif
+			me.preventDefault(e); // 阻止默认事件
+
+			let diff = curPoint.y - me.lastPoint.y; // 和上次比,移动的距离 (大于0向下,小于0向上)
+
+			// 下拉距离  < 指定距离
+			if (me.downHight < me.optDown.offset) {
+				if (me.movetype !== 1) {
+					me.movetype = 1; // 加入标记,保证只执行一次
+					me.optDown.inOffset && me.optDown.inOffset(me); // 进入指定距离范围内那一刻的回调,只执行一次
+					me.isMoveDown = true; // 标记下拉区域高度改变,在touchend重置回来
+				}
+				me.downHight += diff * me.optDown.inOffsetRate; // 越往下,高度变化越小
+
+				// 指定距离  <= 下拉距离
+			} else {
+				if (me.movetype !== 2) {
+					me.movetype = 2; // 加入标记,保证只执行一次
+					me.optDown.outOffset && me.optDown.outOffset(me); // 下拉超过指定距离那一刻的回调,只执行一次
+					me.isMoveDown = true; // 标记下拉区域高度改变,在touchend重置回来
+				}
+				if (diff > 0) { // 向下拉
+					me.downHight += Math.round(diff * me.optDown.outOffsetRate); // 越往下,高度变化越小
+				} else { // 向上收
+					me.downHight += diff; // 向上收回高度,则向上滑多少收多少高度
+				}
+			}
+
+			let rate = me.downHight / me.optDown.offset; // 下拉区域当前高度与指定距离的比值
+			me.optDown.onMoving && me.optDown.onMoving(me, rate, me.downHight); // 下拉过程中的回调,一直在执行
+		}
+	}
+
+	me.lastPoint = curPoint; // 记录本次移动的点
+}
+
+/* 列表touchend事件 */
+MeScroll.prototype.touchendEvent = function(e) {
+	if (!this.optDown.use) return;
+	// 如果下拉区域高度已改变,则需重置回来
+	if (this.isMoveDown) {
+		if (this.downHight >= this.optDown.offset) {
+			// 符合触发刷新的条件
+			this.triggerDownScroll();
+		} else {
+			// 不符合的话 则重置
+			this.downHight = 0;
+			this.optDown.endDownScroll && this.optDown.endDownScroll(this);
+		}
+		this.movetype = 0;
+		this.isMoveDown = false;
+	} else if (!this.isScrollBody && this.getScrollTop() === this.startTop) { // scroll-view到顶/左/右/底的滑动事件
+		let isScrollUp = this.getPoint(e).y - this.startPoint.y < 0; // 和起点比,移动的距离,大于0向下拉,小于0向上拉
+		// 上滑
+		if (isScrollUp) {
+			// 需检查滑动的角度
+			let angle = this.getAngle(this.getPoint(e), this.startPoint); // 两点之间的角度,区间 [0,90]
+			if (angle > 80) {
+				// 检查并触发上拉
+				this.triggerUpScroll(true);
+			}
+		}
+	}
+}
+
+/* 根据点击滑动事件获取第一个手指的坐标 */
+MeScroll.prototype.getPoint = function(e) {
+	if (!e) {
+		return {
+			x: 0,
+			y: 0
+		}
+	}
+	if (e.touches && e.touches[0]) {
+		return {
+			x: e.touches[0].pageX,
+			y: e.touches[0].pageY
+		}
+	} else if (e.changedTouches && e.changedTouches[0]) {
+		return {
+			x: e.changedTouches[0].pageX,
+			y: e.changedTouches[0].pageY
+		}
+	} else {
+		return {
+			x: e.clientX,
+			y: e.clientY
+		}
+	}
+}
+
+/* 计算两点之间的角度: 区间 [0,90]*/
+MeScroll.prototype.getAngle = function(p1, p2) {
+	let x = Math.abs(p1.x - p2.x);
+	let y = Math.abs(p1.y - p2.y);
+	let z = Math.sqrt(x * x + y * y);
+	let angle = 0;
+	if (z !== 0) {
+		angle = Math.asin(y / z) / Math.PI * 180;
+	}
+	return angle
+}
+
+/* 触发下拉刷新 */
+MeScroll.prototype.triggerDownScroll = function() {
+	if (this.optDown.beforeLoading && this.optDown.beforeLoading(this)) {
+		//return true则处于完全自定义状态
+	} else {
+		this.showDownScroll(); // 下拉刷新中...
+		this.optDown.callback && this.optDown.callback(this); // 执行回调,联网加载数据
+	}
+}
+
+/* 显示下拉进度布局 */
+MeScroll.prototype.showDownScroll = function() {
+	this.isDownScrolling = true; // 标记下拉中
+	if (this.optDown.native) {
+		uni.startPullDownRefresh(); // 系统自带的下拉刷新
+		this.optDown.showLoading && this.optDown.showLoading(this, 0); // 仍触发showLoading,因为上拉加载用到
+	} else{
+		this.downHight = this.optDown.offset; // 更新下拉区域高度
+		this.optDown.showLoading && this.optDown.showLoading(this, this.downHight); // 下拉刷新中...
+	}
+}
+
+/* 显示系统自带的下拉刷新时需要处理的业务 */
+MeScroll.prototype.onPullDownRefresh = function() {
+	this.isDownScrolling = true; // 标记下拉中
+	this.optDown.showLoading && this.optDown.showLoading(this, 0); // 仍触发showLoading,因为上拉加载用到
+	this.optDown.callback && this.optDown.callback(this); // 执行回调,联网加载数据
+}
+
+/* 结束下拉刷新 */
+MeScroll.prototype.endDownScroll = function() {
+	if (this.optDown.native) { // 结束原生下拉刷新
+		this.isDownScrolling = false;
+		this.optDown.endDownScroll && this.optDown.endDownScroll(this);
+		uni.stopPullDownRefresh();
+		return
+	}
+	let me = this;
+	// 结束下拉刷新的方法
+	let endScroll = function() {
+		me.downHight = 0;
+		me.isDownScrolling = false;
+		me.optDown.endDownScroll && me.optDown.endDownScroll(me);
+		!me.isScrollBody && me.setScrollHeight(0) // scroll-view重置滚动区域,使数据不满屏时仍可检查触发翻页
+	}
+	// 结束下拉刷新时的回调
+	let delay = 0;
+	if (me.optDown.afterLoading) delay = me.optDown.afterLoading(me); // 结束下拉刷新的延时,单位ms
+	if (typeof delay === 'number' && delay > 0) {
+		setTimeout(endScroll, delay);
+	} else {
+		endScroll();
+	}
+}
+
+/* 锁定下拉刷新:isLock=ture,null锁定;isLock=false解锁 */
+MeScroll.prototype.lockDownScroll = function(isLock) {
+	if (isLock == null) isLock = true;
+	this.optDown.isLock = isLock;
+}
+
+/* 锁定上拉加载:isLock=ture,null锁定;isLock=false解锁 */
+MeScroll.prototype.lockUpScroll = function(isLock) {
+	if (isLock == null) isLock = true;
+	this.optUp.isLock = isLock;
+}
+
+/* -------初始化上拉加载------- */
+MeScroll.prototype.initUpScroll = function() {
+	let me = this;
+	// 配置参数
+	me.optUp = me.options.up || {use: false}
+	if(!me.optUp.textColor && me.hasColor(me.optUp.bgColor)) me.optUp.textColor = "#fff"; // 当bgColor有值且textColor未设置,则textColor默认白色
+	me.extendUpScroll(me.optUp);
+
+	if (!me.optUp.isBounce) me.setBounce(false); // 不允许bounce时,需禁止window的touchmove事件
+
+	if (me.optUp.use === false) return; // 配置不使用上拉加载时,则不初始化上拉布局
+	me.optUp.hasNext = true; // 如果使用上拉,则默认有下一页
+	me.startNum = me.optUp.page.num + 1; // 记录page开始的页码
+
+	// 初始化完毕的回调
+	if (me.optUp.inited) {
+		setTimeout(function() { // 待主线程执行完毕再执行,避免new MeScroll未初始化,在回调获取不到mescroll的实例
+			me.optUp.inited(me);
+		}, 0)
+	}
+}
+
+/*滚动到底部的事件 (仅mescroll-body生效)*/
+MeScroll.prototype.onReachBottom = function() {
+	if (this.isScrollBody && !this.isUpScrolling) { // 只能支持下拉刷新的时候同时可以触发上拉加载,否则滚动到底部就需要上滑一点才能触发onReachBottom
+		if (!this.optUp.isLock && this.optUp.hasNext) {
+			this.triggerUpScroll();
+		}
+	}
+}
+
+/*列表滚动事件 (仅mescroll-body生效)*/
+MeScroll.prototype.onPageScroll = function(e) {
+	if (!this.isScrollBody) return;
+	
+	// 更新滚动条的位置 (主要用于判断下拉刷新时,滚动条是否在顶部)
+	this.setScrollTop(e.scrollTop);
+
+	// 顶部按钮的显示隐藏
+	if (e.scrollTop >= this.optUp.toTop.offset) {
+		this.showTopBtn();
+	} else {
+		this.hideTopBtn();
+	}
+}
+
+/*列表滚动事件*/
+MeScroll.prototype.scroll = function(e, onScroll) {
+	// 更新滚动条的位置
+	this.setScrollTop(e.scrollTop);
+	// 更新滚动内容高度
+	this.setScrollHeight(e.scrollHeight);
+
+	// 向上滑还是向下滑动
+	if (this.preScrollY == null) this.preScrollY = 0;
+	this.isScrollUp = e.scrollTop - this.preScrollY > 0;
+	this.preScrollY = e.scrollTop;
+
+	// 上滑 && 检查并触发上拉
+	this.isScrollUp && this.triggerUpScroll(true);
+
+	// 顶部按钮的显示隐藏
+	if (e.scrollTop >= this.optUp.toTop.offset) {
+		this.showTopBtn();
+	} else {
+		this.hideTopBtn();
+	}
+
+	// 滑动监听
+	this.optUp.onScroll && onScroll && onScroll()
+}
+
+/* 触发上拉加载 */
+MeScroll.prototype.triggerUpScroll = function(isCheck) {
+	if (!this.isUpScrolling && this.optUp.use && this.optUp.callback) {
+		// 是否校验在底部; 默认不校验
+		if (isCheck === true) {
+			let canUp = false;
+			// 还有下一页 && 没有锁定 && 不在下拉中
+			if (this.optUp.hasNext && !this.optUp.isLock && !this.isDownScrolling) {
+				if (this.getScrollBottom() <= this.optUp.offset) { // 到底部
+					canUp = true; // 标记可上拉
+				}
+			}
+			if (canUp === false) return;
+		}
+		this.showUpScroll(); // 上拉加载中...
+		this.optUp.page.num++; // 预先加一页,如果失败则减回
+		this.isUpAutoLoad = true; // 标记上拉已经自动执行过,避免初始化时多次触发上拉回调
+		this.num = this.optUp.page.num; // 把最新的页数赋值在mescroll上,避免对page的影响
+		this.size = this.optUp.page.size; // 把最新的页码赋值在mescroll上,避免对page的影响
+		this.time = this.optUp.page.time; // 把最新的页码赋值在mescroll上,避免对page的影响
+		this.optUp.callback(this); // 执行回调,联网加载数据
+	}
+}
+
+/* 显示上拉加载中 */
+MeScroll.prototype.showUpScroll = function() {
+	this.isUpScrolling = true; // 标记上拉加载中
+	this.optUp.showLoading && this.optUp.showLoading(this); // 回调
+}
+
+/* 显示上拉无更多数据 */
+MeScroll.prototype.showNoMore = function() {
+	this.optUp.hasNext = false; // 标记无更多数据
+	this.optUp.showNoMore && this.optUp.showNoMore(this); // 回调
+}
+
+/* 隐藏上拉区域**/
+MeScroll.prototype.hideUpScroll = function() {
+	this.optUp.hideUpScroll && this.optUp.hideUpScroll(this); // 回调
+}
+
+/* 结束上拉加载 */
+MeScroll.prototype.endUpScroll = function(isShowNoMore) {
+	if (isShowNoMore != null) { // isShowNoMore=null,不处理下拉状态,下拉刷新的时候调用
+		if (isShowNoMore) {
+			this.showNoMore(); // isShowNoMore=true,显示无更多数据
+		} else {
+			this.hideUpScroll(); // isShowNoMore=false,隐藏上拉加载
+		}
+	}
+	this.isUpScrolling = false; // 标记结束上拉加载
+}
+
+/* 重置上拉加载列表为第一页
+ *isShowLoading 是否显示进度布局;
+ * 1.默认null,不传参,则显示上拉加载的进度布局
+ * 2.传参true, 则显示下拉刷新的进度布局
+ * 3.传参false,则不显示上拉和下拉的进度 (常用于静默更新列表数据)
+ */
+MeScroll.prototype.resetUpScroll = function(isShowLoading) {
+	if (this.optUp && this.optUp.use) {
+		let page = this.optUp.page;
+		this.prePageNum = page.num; // 缓存重置前的页码,加载失败可退回
+		this.prePageTime = page.time; // 缓存重置前的时间,加载失败可退回
+		page.num = this.startNum; // 重置为第一页
+		page.time = null; // 重置时间为空
+		if (!this.isDownScrolling && isShowLoading !== false) { // 如果不是下拉刷新触发的resetUpScroll并且不配置列表静默更新,则显示进度;
+			if (isShowLoading == null) {
+				this.removeEmpty(); // 移除空布局
+				this.showUpScroll(); // 不传参,默认显示上拉加载的进度布局
+			} else {
+				this.showDownScroll(); // 传true,显示下拉刷新的进度布局,不清空列表
+			}
+		}
+		this.isUpAutoLoad = true; // 标记上拉已经自动执行过,避免初始化时多次触发上拉回调
+		this.num = page.num; // 把最新的页数赋值在mescroll上,避免对page的影响
+		this.size = page.size; // 把最新的页码赋值在mescroll上,避免对page的影响
+		this.time = page.time; // 把最新的页码赋值在mescroll上,避免对page的影响
+		this.optUp.callback && this.optUp.callback(this); // 执行上拉回调
+	}
+}
+
+/* 设置page.num的值 */
+MeScroll.prototype.setPageNum = function(num) {
+	this.optUp.page.num = num - 1;
+}
+
+/* 设置page.size的值 */
+MeScroll.prototype.setPageSize = function(size) {
+	this.optUp.page.size = size;
+}
+
+/* 联网回调成功,结束下拉刷新和上拉加载
+ * dataSize: 当前页的数据量(必传)
+ * totalPage: 总页数(必传)
+ * systime: 服务器时间 (可空)
+ */
+MeScroll.prototype.endByPage = function(dataSize, totalPage, systime) {
+	let hasNext;
+	if (this.optUp.use && totalPage != null) hasNext = this.optUp.page.num < totalPage; // 是否还有下一页
+	this.endSuccess(dataSize, hasNext, systime);
+}
+
+/* 联网回调成功,结束下拉刷新和上拉加载
+ * dataSize: 当前页的数据量(必传)
+ * totalSize: 列表所有数据总数量(必传)
+ * systime: 服务器时间 (可空)
+ */
+MeScroll.prototype.endBySize = function(dataSize, totalSize, systime) {
+	let hasNext;
+	if (this.optUp.use && totalSize != null) {
+		let loadSize = (this.optUp.page.num - 1) * this.optUp.page.size + dataSize; // 已加载的数据总数
+		hasNext = loadSize < totalSize; // 是否还有下一页
+	}
+	this.endSuccess(dataSize, hasNext, systime);
+}
+
+/* 联网回调成功,结束下拉刷新和上拉加载
+ * dataSize: 当前页的数据个数(不是所有页的数据总和),用于上拉加载判断是否还有下一页.如果不传,则会判断还有下一页
+ * hasNext: 是否还有下一页,布尔类型;用来解决这个小问题:比如列表共有20条数据,每页加载10条,共2页.如果只根据dataSize判断,则需翻到第三页才会知道无更多数据,如果传了hasNext,则翻到第二页即可显示无更多数据.
+ * systime: 服务器时间(可空);用来解决这个小问题:当准备翻下一页时,数据库新增了几条记录,此时翻下一页,前面的几条数据会和上一页的重复;这里传入了systime,那么upCallback的page.time就会有值,把page.time传给服务器,让后台过滤新加入的那几条记录
+ */
+MeScroll.prototype.endSuccess = function(dataSize, hasNext, systime) {
+	let me = this;
+	// 结束下拉刷新
+	if (me.isDownScrolling) me.endDownScroll();
+
+	// 结束上拉加载
+	if (me.optUp.use) {
+		let isShowNoMore; // 是否已无更多数据
+		if (dataSize != null) {
+			let pageNum = me.optUp.page.num; // 当前页码
+			let pageSize = me.optUp.page.size; // 每页长度
+			// 如果是第一页
+			if (pageNum === 1) {
+				if (systime) me.optUp.page.time = systime; // 设置加载列表数据第一页的时间
+			}
+			if (dataSize < pageSize || hasNext === false) {
+				// 返回的数据不满一页时,则说明已无更多数据
+				me.optUp.hasNext = false;
+				if (dataSize === 0 && pageNum === 1) {
+					// 如果第一页无任何数据且配置了空布局
+					isShowNoMore = false;
+					me.showEmpty();
+				} else {
+					// 总列表数少于配置的数量,则不显示无更多数据
+					let allDataSize = (pageNum - 1) * pageSize + dataSize;
+					if (allDataSize < me.optUp.noMoreSize) {
+						isShowNoMore = false;
+					} else {
+						isShowNoMore = true;
+					}
+					me.removeEmpty(); // 移除空布局
+				}
+			} else {
+				// 还有下一页
+				isShowNoMore = false;
+				me.optUp.hasNext = true;
+				me.removeEmpty(); // 移除空布局
+			}
+		}
+
+		// 隐藏上拉
+		me.endUpScroll(isShowNoMore);
+	}
+}
+
+/* 回调失败,结束下拉刷新和上拉加载 */
+MeScroll.prototype.endErr = function(errDistance) {
+	// 结束下拉,回调失败重置回原来的页码和时间
+	if (this.isDownScrolling) {
+		let page = this.optUp.page;
+		if (page && this.prePageNum) {
+			page.num = this.prePageNum;
+			page.time = this.prePageTime;
+		}
+		this.endDownScroll();
+	}
+	// 结束上拉,回调失败重置回原来的页码
+	if (this.isUpScrolling) {
+		this.optUp.page.num--;
+		this.endUpScroll(false);
+		// 如果是mescroll-body,则需往回滚一定距离
+		if(this.isScrollBody && errDistance !== 0){ // 不处理0
+			if(!errDistance) errDistance = this.optUp.errDistance; // 不传,则取默认
+			this.scrollTo(this.getScrollTop() - errDistance, 0) // 往上回滚的距离
+		}
+	}
+}
+
+/* 显示空布局 */
+MeScroll.prototype.showEmpty = function() {
+	this.optUp.empty.use && this.optUp.empty.onShow && this.optUp.empty.onShow(true)
+}
+
+/* 移除空布局 */
+MeScroll.prototype.removeEmpty = function() {
+	this.optUp.empty.use && this.optUp.empty.onShow && this.optUp.empty.onShow(false)
+}
+
+/* 显示回到顶部的按钮 */
+MeScroll.prototype.showTopBtn = function() {
+	if (!this.topBtnShow) {
+		this.topBtnShow = true;
+		this.optUp.toTop.onShow && this.optUp.toTop.onShow(true);
+	}
+}
+
+/* 隐藏回到顶部的按钮 */
+MeScroll.prototype.hideTopBtn = function() {
+	if (this.topBtnShow) {
+		this.topBtnShow = false;
+		this.optUp.toTop.onShow && this.optUp.toTop.onShow(false);
+	}
+}
+
+/* 获取滚动条的位置 */
+MeScroll.prototype.getScrollTop = function() {
+	return this.scrollTop || 0
+}
+
+/* 记录滚动条的位置 */
+MeScroll.prototype.setScrollTop = function(y) {
+	this.scrollTop = y;
+}
+
+/* 滚动到指定位置 */
+MeScroll.prototype.scrollTo = function(y, t) {
+	this.myScrollTo && this.myScrollTo(y, t) // scrollview需自定义回到顶部方法
+}
+
+/* 自定义scrollTo */
+MeScroll.prototype.resetScrollTo = function(myScrollTo) {
+	this.myScrollTo = myScrollTo
+}
+
+/* 滚动条到底部的距离 */
+MeScroll.prototype.getScrollBottom = function() {
+	return this.getScrollHeight() - this.getClientHeight() - this.getScrollTop()
+}
+
+/* 计步器
+ star: 开始值
+ end: 结束值
+ callback(step,timer): 回调step值,计步器timer,可自行通过window.clearInterval(timer)结束计步器;
+ t: 计步时长,传0则直接回调end值;不传则默认300ms
+ rate: 周期;不传则默认30ms计步一次
+ * */
+MeScroll.prototype.getStep = function(star, end, callback, t, rate) {
+	let diff = end - star; // 差值
+	if (t === 0 || diff === 0) {
+		callback && callback(end);
+		return;
+	}
+	t = t || 300; // 时长 300ms
+	rate = rate || 30; // 周期 30ms
+	let count = t / rate; // 次数
+	let step = diff / count; // 步长
+	let i = 0; // 计数
+	let timer = setInterval(function() {
+		if (i < count - 1) {
+			star += step;
+			callback && callback(star, timer);
+			i++;
+		} else {
+			callback && callback(end, timer); // 最后一次直接设置end,避免计算误差
+			clearInterval(timer);
+		}
+	}, rate);
+}
+
+/* 滚动容器的高度 */
+MeScroll.prototype.getClientHeight = function(isReal) {
+	let h = this.clientHeight || 0
+	if (h === 0 && isReal !== true) { // 未获取到容器的高度,可临时取body的高度 (可能会有误差)
+		h = this.getBodyHeight()
+	}
+	return h
+}
+MeScroll.prototype.setClientHeight = function(h) {
+	this.clientHeight = h;
+}
+
+/* 滚动内容的高度 */
+MeScroll.prototype.getScrollHeight = function() {
+	return this.scrollHeight || 0;
+}
+MeScroll.prototype.setScrollHeight = function(h) {
+	this.scrollHeight = h;
+}
+
+/* body的高度 */
+MeScroll.prototype.getBodyHeight = function() {
+	return this.bodyHeight || 0;
+}
+MeScroll.prototype.setBodyHeight = function(h) {
+	this.bodyHeight = h;
+}
+
+/* 阻止浏览器默认滚动事件 */
+MeScroll.prototype.preventDefault = function(e) {
+	// 小程序不支持e.preventDefault
+	// app的bounce只能通过配置pages.json的style.app-plus.bounce为"none"来禁止
+	// cancelable:是否可以被禁用; defaultPrevented:是否已经被禁用
+	if (e && e.cancelable && !e.defaultPrevented) e.preventDefault()
+}
+
+/* 是否允许下拉回弹(橡皮筋效果); true或null为允许; false禁止bounce */
+MeScroll.prototype.setBounce = function(isBounce) {
+	// #ifdef H5
+	if (isBounce === false) {
+		this.optUp.isBounce = false; // 禁止
+		// 标记当前页使用了mescroll (需延时,确保page已切换)
+		setTimeout(function() {
+			let uniPageDom = document.getElementsByTagName('uni-page')[0];
+			uniPageDom && uniPageDom.setAttribute('use_mescroll', true)
+		}, 30);
+		// 避免重复添加事件
+		if (window.isSetBounce) return;
+		window.isSetBounce = true;
+		// 需禁止window的touchmove事件才能有效的阻止bounce
+		window.bounceTouchmove = function(e) {
+			if(!window.isPreventDefault) return; // 根据标记判断是否阻止
+			
+			let el = e.target;
+			// 当前touch的元素及父元素是否要拦截touchmove事件
+			let isPrevent = true;
+			while (el !== document.body && el !== document) {
+				if (el.tagName === 'UNI-PAGE') { // 只扫描当前页
+					if (!el.getAttribute('use_mescroll')) {
+						isPrevent = false; // 如果当前页没有使用mescroll,则不阻止
+					}
+					break;
+				}
+				let cls = el.classList;
+				if (cls) {
+					if (cls.contains('mescroll-touch')) { // 采用scroll-view 此处不能过滤mescroll-uni,否则下拉仍然有回弹
+						isPrevent = false; // mescroll-touch无需拦截touchmove事件
+						break;
+					} else if (cls.contains('mescroll-touch-x') || cls.contains('mescroll-touch-y')) {
+						// 如果配置了水平或者垂直滑动
+						let curX = e.touches ? e.touches[0].pageX : e.clientX; // 当前第一个手指距离列表顶部的距离x
+						let curY = e.touches ? e.touches[0].pageY : e.clientY; // 当前第一个手指距离列表顶部的距离y
+						if (!this.preWinX) this.preWinX = curX; // 设置上次移动的距离x
+						if (!this.preWinY) this.preWinY = curY; // 设置上次移动的距离y
+						// 计算两点之间的角度
+						let x = Math.abs(this.preWinX - curX);
+						let y = Math.abs(this.preWinY - curY);
+						let z = Math.sqrt(x * x + y * y);
+						this.preWinX = curX; // 记录本次curX的值
+						this.preWinY = curY; // 记录本次curY的值
+						if (z !== 0) {
+							let angle = Math.asin(y / z) / Math.PI * 180; // 角度区间 [0,90]
+							if ((angle <= 45 && cls.contains('mescroll-touch-x')) || (angle > 45 && cls.contains('mescroll-touch-y'))) {
+								isPrevent = false; // 水平滑动或者垂直滑动,不拦截touchmove事件
+								break;
+							}
+						}
+					}
+				}
+				el = el.parentNode; // 继续检查其父元素
+			}
+			// 拦截touchmove事件:是否可以被禁用&&是否已经被禁用 (这里不使用me.preventDefault(e)的方法,因为某些情况下会报找不到方法的异常)
+			if (isPrevent && e.cancelable && !e.defaultPrevented && typeof e.preventDefault === "function") e.preventDefault();
+		}
+		window.addEventListener('touchmove', window.bounceTouchmove, {
+			passive: false
+		});
+	} else {
+		this.optUp.isBounce = true; // 允许
+		if (window.bounceTouchmove) {
+			window.removeEventListener('touchmove', window.bounceTouchmove);
+			window.bounceTouchmove = null;
+			window.isSetBounce = false;
+		}
+	}
+	// #endif
+}

+ 364 - 0
components/mescroll-uni/mescroll-uni.vue

@@ -0,0 +1,364 @@
+<template>
+	<view class="mescroll-uni-warp">
+		<scroll-view :id="viewId" class="mescroll-uni" :class="{'mescroll-uni-fixed':isFixed}" :style="{'height':scrollHeight,'padding-top':padTop,'padding-bottom':padBottom,'padding-bottom':padBottomConstant,'padding-bottom':padBottomEnv,'top':fixedTop,'bottom':fixedBottom,'bottom':fixedBottomConstant,'bottom':fixedBottomEnv}" :scroll-top="scrollTop" :scroll-into-view="scrollToViewId" :scroll-with-animation="scrollAnim" @scroll="scroll" @touchstart="touchstartEvent" @touchmove="touchmoveEvent" @touchend="touchendEvent" @touchcancel="touchendEvent" :scroll-y='isDownReset' :enable-back-to-top="true">
+			<view class="mescroll-uni-content" :style="{'transform': translateY, 'transition': transition}">
+				<!-- 下拉加载区域 (支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-down组件实现)-->
+				<!-- <mescroll-down :option="mescroll.optDown" :type="downLoadType" :rate="downRate"></mescroll-down> -->
+				<view v-if="mescroll.optDown.use" class="mescroll-downwarp" :style="{'background-color':mescroll.optDown.bgColor,'color':mescroll.optDown.textColor}">
+					<view class="downwarp-content">
+						<view class="downwarp-progress" :class="{'mescroll-rotate': isDownLoading}" :style="{'border-color':mescroll.optDown.textColor, 'transform': downRotate}"></view>
+						<view class="downwarp-tip">{{downText}}</view>
+					</view>
+				</view>
+
+				<!-- 列表内容 -->
+				<slot></slot>
+
+				<!-- 空布局 -->
+				<mescroll-empty v-if="isShowEmpty" :option="mescroll.optUp.empty" @emptyclick="emptyClick"></mescroll-empty>
+
+				<!-- 上拉加载区域 (下拉刷新时不显示, 支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-up组件实现)-->
+				<!-- <mescroll-up v-if="mescroll.optUp.use && !isDownLoading" :option="mescroll.optUp" :type="upLoadType"></mescroll-up> -->
+				<view v-if="mescroll.optUp.use && !isDownLoading" class="mescroll-upwarp" :style="{'background-color':mescroll.optUp.bgColor,'color':mescroll.optUp.textColor}">
+					<!-- 加载中 (此处不能用v-if,否则android小程序快速上拉可能会不断触发上拉回调) -->
+					<view v-show="upLoadType===1">
+						<view class="upwarp-progress mescroll-rotate" :style="{'border-color':mescroll.optUp.textColor}"></view>
+						<view class="upwarp-tip">{{ mescroll.optUp.textLoading }}</view>
+					</view>
+					<!-- 无数据 -->
+					<view v-if="upLoadType===2" class="upwarp-nodata">{{ mescroll.optUp.textNoMore }}</view>
+				</view>
+			</view>
+		</scroll-view>
+
+		<!-- 回到顶部按钮 (fixed元素,需写在scroll-view外面,防止滚动的时候抖动)-->
+		<mescroll-top v-model="isShowToTop" :option="mescroll.optUp.toTop" @click="toTopClick"></mescroll-top>
+	</view>
+</template>
+
+<script>
+	// 引入mescroll-uni.js,处理核心逻辑
+	import MeScroll from './mescroll-uni.js';
+	// 引入全局配置
+	import GlobalOption from './mescroll-uni-option.js';
+	// 引入空布局组件
+	import MescrollEmpty from './components/mescroll-empty.vue';
+	// 引入回到顶部组件
+	import MescrollTop from './components/mescroll-top.vue';
+
+	export default {
+		components: {
+			MescrollEmpty,
+			MescrollTop
+		},
+		data() {
+			return {
+				mescroll: {optDown:{},optUp:{}}, // mescroll实例
+				viewId: 'id_' + Math.random().toString(36).substr(2), // 随机生成mescroll的id(不能数字开头,否则找不到元素)
+				downHight: 0, //下拉刷新: 容器高度
+				downRate: 0, // 下拉比率(inOffset: rate<1; outOffset: rate>=1)
+				downLoadType: 4, // 下拉刷新状态 (inOffset:1, outOffset:2, showLoading:3, endDownScroll:4)
+				upLoadType: 0, // 上拉加载状态:0(loading前),1(loading中),2(没有更多了)
+				isShowEmpty: false, // 是否显示空布局
+				isShowToTop: false, // 是否显示回到顶部按钮
+				scrollTop: 0, // 滚动条的位置
+				scrollAnim: false, // 是否开启滚动动画
+				windowTop: 0, // 可使用窗口的顶部位置
+				windowBottom: 0, // 可使用窗口的底部位置
+				windowHeight: 0, // 可使用窗口的高度
+				statusBarHeight: 0, // 状态栏高度
+				isSafearea: false, // 支持安全区
+				scrollToViewId: '' // 滚动到指定view的id
+			}
+		},
+		props: {
+			down: Object, // 下拉刷新的参数配置
+			up: Object, // 上拉加载的参数配置
+			top: [String, Number], // 下拉布局往下的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
+			topbar: Boolean, // top的偏移量是否加上状态栏高度, 默认false (使用场景:取消原生导航栏时,配置此项可自动加上状态栏高度的偏移量)
+			bottom: [String, Number], // 上拉布局往上的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
+			safearea: Boolean, // bottom的偏移量是否加上底部安全区的距离, 默认false (需要适配iPhoneX时使用)
+			fixed: { // 是否通过fixed固定mescroll的高度, 默认true
+				type: Boolean,
+				default () {
+					return true
+				}
+			},
+			height: [String, Number] // 指定mescroll的高度, 此项有值,则不使用fixed. (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
+		},
+		computed: {
+			// 是否使用fixed定位 (当height有值,则不使用)
+			isFixed(){
+				return !this.height && this.fixed
+			},
+			// mescroll的高度
+			scrollHeight(){
+				if (this.isFixed) {
+					return "auto"
+				} else if(this.height){
+					return this.toPx(this.height) + 'px'
+				}else{
+					return "100%"
+				}
+			},
+			// 下拉布局往下偏移的距离 (px)
+			numTop() {
+				return this.toPx(this.top) + (this.topbar ? this.statusBarHeight : 0)
+			},
+			fixedTop() {
+				return this.isFixed ? (this.numTop + this.windowTop) + 'px' : 0
+			},
+			padTop() {
+				return !this.isFixed ? this.numTop + 'px' : 0
+			},
+			// 上拉布局往上偏移 (px)
+			numBottom() {
+				return this.toPx(this.bottom)
+			},
+			fixedBottom() {
+				return this.isFixed ? (this.numBottom + this.windowBottom) + 'px' : 0
+			},
+			fixedBottomConstant(){
+				return this.isSafearea ? "calc("+this.fixedBottom+" + constant(safe-area-inset-bottom))" : this.fixedBottom
+			},
+			fixedBottomEnv(){
+				return this.isSafearea ? "calc("+this.fixedBottom+" + env(safe-area-inset-bottom))" : this.fixedBottom
+			},
+			padBottom() {
+				return !this.isFixed ? this.numBottom + 'px' : 0
+			},
+			padBottomConstant(){
+				return this.isSafearea ? "calc("+this.padBottom+" + constant(safe-area-inset-bottom))" : this.padBottom
+			},
+			padBottomEnv(){
+				return this.isSafearea ? "calc("+this.padBottom+" + env(safe-area-inset-bottom))" : this.padBottom
+			},
+			// 是否为重置下拉的状态
+			isDownReset(){
+				return this.downLoadType===3 || this.downLoadType===4
+			},
+			// 过渡
+			transition() {
+				return this.isDownReset ? 'transform 300ms' : '';
+			},
+			translateY() {
+				return this.downHight > 0 ? 'translateY(' + this.downHight + 'px)' : ''; // transform会使fixed失效,需注意把fixed元素写在mescroll之外
+			},
+			// 是否在加载中
+			isDownLoading(){
+				return this.downLoadType === 3
+			},
+			// 旋转的角度
+			downRotate(){
+				return 'rotate(' + 360 * this.downRate + 'deg)'
+			},
+			// 文本提示
+			downText(){
+				switch (this.downLoadType){
+					case 1: return this.mescroll.optDown.textInOffset;
+					case 2: return this.mescroll.optDown.textOutOffset;
+					case 3: return this.mescroll.optDown.textLoading;
+					case 4: return this.mescroll.optDown.textLoading;
+					default: return this.mescroll.optDown.textInOffset;
+				}
+			}
+		},
+		methods: {
+			//number,rpx,upx,px,% --> px的数值
+			toPx(num){
+				if(typeof num === "string"){
+					if (num.indexOf('px') !== -1) {
+						if(num.indexOf('rpx') !== -1) { // "10rpx"
+							num = num.replace('rpx', '');
+						} else if(num.indexOf('upx') !== -1) { // "10upx"
+							num = num.replace('upx', '');
+						} else { // "10px"
+							return Number(num.replace('px', ''))
+						}
+					}else if (num.indexOf('%') !== -1){
+						// 传百分比,则相对于windowHeight,传"10%"则等于windowHeight的10%
+						let rate = Number(num.replace("%","")) / 100
+						return this.windowHeight * rate
+					}
+				}
+				return num ? uni.upx2px(Number(num)) : 0
+			},
+			//注册列表滚动事件,用于下拉刷新和上拉加载
+			scroll(e) {
+				this.mescroll.scroll(e.detail, () => {
+					this.$emit('scroll', this.mescroll) // 此时可直接通过 this.mescroll.scrollTop获取滚动条位置; this.mescroll.isScrollUp获取是否向上滑动
+				})
+			},
+			//注册列表touchstart事件,用于下拉刷新
+			touchstartEvent(e) {
+				this.mescroll.touchstartEvent(e);
+			},
+			//注册列表touchmove事件,用于下拉刷新
+			touchmoveEvent(e) {
+				this.mescroll.touchmoveEvent(e);
+			},
+			//注册列表touchend事件,用于下拉刷新
+			touchendEvent(e) {
+				this.mescroll.touchendEvent(e);
+			},
+			// 点击空布局的按钮回调
+			emptyClick() {
+				this.$emit('emptyclick', this.mescroll)
+			},
+			// 点击回到顶部的按钮回调
+			toTopClick() {
+				this.mescroll.scrollTo(0, this.mescroll.optUp.toTop.duration); // 执行回到顶部
+				this.$emit('topclick', this.mescroll); // 派发点击回到顶部按钮的回调
+			},
+			// 更新滚动区域的高度 (使内容不满屏和到底,都可继续翻页)
+			setClientHeight() {
+				if (this.mescroll.getClientHeight(true) === 0 && !this.isExec) {
+					this.isExec = true; // 避免多次获取
+					this.$nextTick(() => { // 确保dom已渲染
+						let view = uni.createSelectorQuery().in(this).select('#' + this.viewId);
+						view.boundingClientRect(data => {
+							this.isExec = false;
+							if (data) {
+								this.mescroll.setClientHeight(data.height);
+							} else if (this.clientNum != 3) { // 极少部分情况,可能dom还未渲染完毕,递归获取,最多重试3次
+								this.clientNum = this.clientNum == null ? 1 : this.clientNum + 1;
+								setTimeout(() => {
+									this.setClientHeight()
+								}, this.clientNum * 100)
+							}
+						}).exec();
+					})
+				}
+			}
+		},
+		// 使用created初始化mescroll对象; 如果用mounted部分css样式编译到H5会失效
+		created() {
+			let vm = this;
+
+			let diyOption = {
+				// 下拉刷新的配置
+				down: {
+					inOffset(mescroll) {
+						vm.downLoadType = 1; // 下拉的距离进入offset范围内那一刻的回调 (自定义mescroll组件时,此行不可删)
+					},
+					outOffset(mescroll) {
+						vm.downLoadType = 2; // 下拉的距离大于offset那一刻的回调 (自定义mescroll组件时,此行不可删)
+					},
+					onMoving(mescroll, rate, downHight) {
+						// 下拉过程中的回调,滑动过程一直在执行;
+						vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
+						vm.downRate = rate; //下拉比率 (inOffset: rate<1; outOffset: rate>=1)
+					},
+					showLoading(mescroll, downHight) {
+						vm.downLoadType = 3; // 显示下拉刷新进度的回调 (自定义mescroll组件时,此行不可删)
+						vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
+					},
+					endDownScroll(mescroll) {
+						vm.downLoadType = 4; // 结束下拉 (自定义mescroll组件时,此行不可删)
+						vm.downHight = 0; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
+					},
+					// 派发下拉刷新的回调
+					callback: function(mescroll) {
+						vm.$emit('down', mescroll)
+					}
+				},
+				// 上拉加载的配置
+				up: {
+					// 显示加载中的回调
+					showLoading() {
+						vm.upLoadType = 1;
+					},
+					// 显示无更多数据的回调
+					showNoMore() {
+						vm.upLoadType = 2;
+					},
+					// 隐藏上拉加载的回调
+					hideUpScroll() {
+						vm.upLoadType = 0;
+					},
+					// 空布局
+					empty: {
+						onShow(isShow) { // 显示隐藏的回调
+							vm.isShowEmpty = isShow;
+						}
+					},
+					// 回到顶部
+					toTop: {
+						onShow(isShow) { // 显示隐藏的回调
+							vm.isShowToTop = isShow;
+						}
+					},
+					// 派发上拉加载的回调
+					callback: function(mescroll) {
+						vm.$emit('up', mescroll);
+						// 更新容器的高度 (多mescroll的情况)
+						vm.setClientHeight()
+					}
+				}
+			}
+
+			MeScroll.extend(diyOption, GlobalOption); // 混入全局的配置
+			let myOption = JSON.parse(JSON.stringify({
+				'down': vm.down,
+				'up': vm.up
+			})) // 深拷贝,避免对props的影响
+			MeScroll.extend(myOption, diyOption); // 混入具体界面的配置
+
+			// 初始化MeScroll对象
+			vm.mescroll = new MeScroll(myOption);
+			vm.mescroll.viewId = vm.viewId; // 附带id
+			// init回调mescroll对象
+			vm.$emit('init', vm.mescroll);
+			
+			// 设置高度
+			const sys = uni.getSystemInfoSync();
+			if(sys.windowTop) vm.windowTop = sys.windowTop;
+			if(sys.windowBottom) vm.windowBottom = sys.windowBottom;
+			if(sys.windowHeight) vm.windowHeight = sys.windowHeight;
+			if(sys.statusBarHeight) vm.statusBarHeight = sys.statusBarHeight;
+			// 使down的bottomOffset生效
+			vm.mescroll.setBodyHeight(sys.windowHeight);
+
+			// 因为使用的是scrollview,这里需自定义scrollTo
+			vm.mescroll.resetScrollTo((y, t) => {
+				vm.scrollAnim = (t !== 0); // t为0,则不使用动画过渡
+				if(typeof y === 'string'){ // 第一个参数如果为字符串,则使用scroll-into-view
+					vm.scrollToViewId = y;
+					return;
+				}
+				let curY = vm.mescroll.getScrollTop()
+				if (t === 0 || t === 300) { // 当t使用默认配置的300时,则使用系统自带的动画过渡
+					vm.scrollTop = curY;
+					vm.$nextTick(function() {
+						vm.scrollTop = y
+					})
+				} else {
+					vm.mescroll.getStep(curY, y, step => { // 此写法可支持配置t
+						vm.scrollTop = step
+					}, t)
+				}
+			})
+			
+			// 具体的界面如果不配置up.toTop.safearea,则取本vue的safearea值
+			if(sys.platform == "ios"){
+				vm.isSafearea = vm.safearea;
+				if (vm.up && vm.up.toTop && vm.up.toTop.safearea != null) {} else {
+					vm.mescroll.optUp.toTop.safearea = vm.safearea;
+				}
+			}else{
+				vm.isSafearea = false
+				vm.mescroll.optUp.toTop.safearea = false
+			}
+		},
+		mounted() {
+			// 设置容器的高度
+			this.setClientHeight()
+		}
+	}
+</script>
+
+<style>
+	@import "./mescroll-uni.css";
+	@import "./components/mescroll-down.css";
+	@import './components/mescroll-up.css';
+</style>

+ 23 - 0
components/mescroll-uni/mixins/mescroll-comp.js

@@ -0,0 +1,23 @@
+/**
+ * mescroll-body写在子组件时,需通过mescroll的mixins补充子组件缺少的生命周期:
+ * 当一个页面只有一个mescroll-body写在子组件时, 则使用mescroll-comp.js (参考 mescroll-comp 案例)
+ * 当一个页面有多个mescroll-body写在子组件时, 则使用mescroll-more.js (参考 mescroll-more 案例)
+ */
+const MescrollCompMixin = {
+	// 因为子组件无onPageScroll和onReachBottom的页面生命周期,需在页面传递进到子组件
+	onPageScroll(e) {
+		let item = this.$refs["mescrollItem"];
+		if(item && item.mescroll) item.mescroll.onPageScroll(e);
+	},
+	onReachBottom() {
+		let item = this.$refs["mescrollItem"];
+		if(item && item.mescroll) item.mescroll.onReachBottom();
+	},
+	// 当down的native: true时, 还需传递此方法进到子组件
+	onPullDownRefresh(){
+		let item = this.$refs["mescrollItem"];
+		if(item && item.mescroll) item.mescroll.onPullDownRefresh();
+	}
+}
+
+export default MescrollCompMixin;

+ 48 - 0
components/mescroll-uni/mixins/mescroll-more-item.js

@@ -0,0 +1,48 @@
+/**
+ * mescroll-more-item的mixins, 仅在多个 mescroll-body 写在子组件时使用 (参考 mescroll-more 案例)
+ */
+const MescrollMoreItemMixin = {
+	props:{
+		i: Number, // 每个tab页的专属下标
+		index: { // 当前tab的下标
+			type: Number,
+			default(){
+				return 0
+			}
+		}
+	},
+	data() {
+		return {
+			downOption:{
+				auto:false // 不自动加载
+			},
+			upOption:{
+				auto:false // 不自动加载
+			},
+			isInit: false // 当前tab是否已初始化
+		}
+	},
+	watch:{
+		// 监听下标的变化
+		index(val){
+			if (this.i === val && !this.isInit) {
+				this.isInit = true; // 标记为true
+				this.mescroll && this.mescroll.triggerDownScroll();
+			}
+		}
+	},
+	methods: {
+		// mescroll组件初始化的回调,可获取到mescroll对象
+		mescrollInit(mescroll) {
+			this.mescroll = mescroll;
+			this.mescrollInitByRef && this.mescrollInitByRef(); // 兼容字节跳动小程序 (mescroll-mixins.js)
+			// 自动加载当前tab的数据
+			if(this.i === this.index){
+				this.isInit = true; // 标记为true
+				this.mescroll.triggerDownScroll();
+			}
+		},
+	}
+}
+
+export default MescrollMoreItemMixin;

+ 56 - 0
components/mescroll-uni/mixins/mescroll-more.js

@@ -0,0 +1,56 @@
+/**
+ * mescroll-body写在子组件时,需通过mescroll的mixins补充子组件缺少的生命周期:
+ * 当一个页面只有一个mescroll-body写在子组件时, 则使用mescroll-comp.js (参考 mescroll-comp 案例)
+ * 当一个页面有多个mescroll-body写在子组件时, 则使用mescroll-more.js (参考 mescroll-more 案例)
+ */
+const MescrollMoreMixin = {
+	data() {
+		return {
+			tabIndex: 0 // 当前tab下标
+		}
+	},
+	// 因为子组件无onPageScroll和onReachBottom的页面生命周期,需在页面传递进到子组件
+	onPageScroll(e) {
+		let mescroll = this.getMescroll(this.tabIndex);
+		mescroll && mescroll.onPageScroll(e);
+	},
+	onReachBottom() {
+		let mescroll = this.getMescroll(this.tabIndex);
+		mescroll && mescroll.onReachBottom();
+	},
+	// 当down的native: true时, 还需传递此方法进到子组件
+	onPullDownRefresh(){
+		let mescroll = this.getMescroll(this.tabIndex);
+		mescroll && mescroll.onPullDownRefresh();
+	},
+	methods:{
+		// 根据下标获取对应子组件的mescroll
+		getMescroll(i){
+			if(!this.mescrollItems) this.mescrollItems = [];
+			if(!this.mescrollItems[i]) {
+				// v-for中的refs
+				let vForItem = this.$refs["mescrollItem"];
+				if(vForItem){
+					this.mescrollItems[i] = vForItem[i]
+				}else{
+					// 普通的refs,不可重复
+					this.mescrollItems[i] = this.$refs["mescrollItem"+i];
+				}
+			}
+			let item = this.mescrollItems[i]
+			return item ? item.mescroll : null
+		},
+		// 切换tab,恢复滚动条位置
+		tabChange(i){
+			let mescroll = this.getMescroll(i);
+			if(mescroll){
+				// 延时(比$nextTick靠谱一些),确保元素已渲染
+				setTimeout(()=>{
+					mescroll.scrollTo(mescroll.getScrollTop(),0)
+				},30)
+			}
+		}
+	}
+}
+
+export default MescrollMoreMixin;

+ 158 - 0
components/my-componets/appSelect.vue

@@ -0,0 +1,158 @@
+<template>
+	<view class="cu-form-group" style="z-index:10">
+		<view class="flex align-center">
+			<view class="title">
+				<text class="text-red" v-if="required">*</text>
+				<text space="ensp">{{label}}</text>
+			</view>
+			<picker
+				 @change="pickerChange"
+				 :range="selections"
+				 :value="valueIndex"
+				 :disabled="disabled">
+				<input
+				   :placeholder="placeholder"
+				   name="input"
+				   v-model="selected"
+				   :disabled="true"
+				></input>
+			</picker>
+		</view>
+	</view>
+</template>
+
+<script>
+
+    export default {
+        name: "AppSecelt",
+		behaviors: ['uni://form-field'],
+        props:{
+            display:{
+                type:String,
+                default:'inline-block',
+                required:false
+            },
+            placeholder:{
+                type:String,
+                default:'请选择',
+                required:false
+            },
+            label:{
+                type:String,
+                default:'',
+                required:false
+            },
+            value:{
+                type:String,
+                required:false
+            },
+            border:{
+                type:Boolean,
+                default:false,
+                required:false
+            },
+            dict:{
+                type:Array,
+                default:()=>[],
+                required:true
+            },
+            name:{
+                type:String,
+                default:'',
+                required:false
+            },
+			required:{
+			    type:Boolean,
+				default:false,
+			    required:false
+			},
+			disabled:{
+			    type:Boolean,
+				default:false,
+			    required:false
+			},
+			space:{
+			    type:Boolean,
+				default:false,
+			    required:false
+			}
+        },
+        data(){
+            return {
+                show:false,
+                selected:'',
+                valueIndex:0,
+                selections:[]
+            }
+        },
+        watch:{
+            value:{
+                immediate:true,
+                handler(val){
+                  if(!val){
+                    this.selected = ''
+                    this.valueIndex = 0
+                }else{
+                  this.dict.map((item,index)=>{
+					 if(item.value == val){
+                      this.selected = item.text;
+                      this.valueIndex = index
+					}
+                  })
+                }
+              }
+            },
+            dict(){
+                this.initSelections();
+            }
+        },
+        created(){
+            this.initSelections();
+        },
+        methods:{
+            initSelections(){
+                let arr = [];
+                this.dict.map(item=>{
+                    arr.push(item.text)
+                });
+                this.selections = arr
+            },
+            pickerChange(e){
+				console.log("appselect::pickerChange",e.detail.value)
+                let backString = '';
+				let obj=this.dict[e.detail.value];
+				this.selected=obj.text;
+				backString=obj.value;
+				console.log("backString",backString)
+				console.log("this.selected",this.selected)
+				
+				this.$emit('input',backString);
+				// #ifndef MP-WEIXIN
+				this.$emit('change',backString);
+				// #endif
+            }
+        },
+        model: {
+            prop: 'value',
+			event:'change'
+        }
+    }
+</script>
+
+<style scoped>
+	.cu-form-group uni-picker::after {
+		font-family: cuIcon;
+		display: block;
+		content: "\e6a3";
+		position: absolute;
+		font-size: 14px;
+		color: #FFFFFF;
+		line-height: 42px;
+		width: 25px;
+		text-align: center;
+		top: 0;
+		bottom: 0;
+		right: -8px;
+		margin: auto;
+	}
+</style>

+ 93 - 0
components/my-componets/my-date.vue

@@ -0,0 +1,93 @@
+<template>
+		<view class="cu-form-group" @click="timechange">
+			<view class="title"><text class="text-red" v-if="required">*</text>{{label}}</view>
+			<input
+			  :placeholder="placeholder"
+			   name="input"
+			   v-model="selected"
+			   disabled="true"
+			></input>
+			<w-picker
+			    :visible.sync="visible"
+				ref="picker" 
+			    mode="date" 
+			    startYear="2020" 
+			    endYear="2100"
+				:value="value"
+			    :fields="fields"
+			    @confirm="onConfirm($event,'date')"
+			 ></w-picker>
+		</view>
+</template>
+
+<script>
+	export default {
+	        name: "AppSecelt",
+	        props:{
+	            placeholder:{
+	                type:String,
+	                default:'请选择',
+	                required:false
+	            },
+	            label:{
+	                type:String,
+	                default:'',
+	                required:false
+	            },
+				fields:{
+				    type:String,
+				    default:'second',
+				    required:false
+				},
+	            value:{
+	                type:String,
+	                required:false
+	            } ,
+				required:{
+	                type:Boolean,
+					default:false,
+	                required:false
+	            }
+	        },
+	        data(){
+	            return {
+	                visible:false,
+	                selected:''
+	            }
+	        },
+	        watch:{
+	            value:{
+	                immediate:true,
+	                handler(val){
+	                  if(!val){
+	                    this.selected = ''
+	                  }else{
+	                    this.selected = val;
+	                }
+	              }
+	            }
+	        },
+	        created(){
+				
+	        },
+	        methods:{
+				timechange(){
+					this.$refs.picker.show()
+				},
+	            onConfirm(e){
+					console.log("confirm",e)
+					let backString = e.value;
+					this.selected=e.value;
+	                this.$emit('input',backString);
+	            }
+	        },
+	        model: {
+	            prop: 'value',
+	            event: 'input'
+	        }
+	    }
+</script>
+	
+<style scoped>
+	
+</style>

+ 326 - 0
components/my-componets/my-map.vue

@@ -0,0 +1,326 @@
+<template>
+	<view>
+		<map
+			style="width: 100%; height:250px;"
+			:latitude="latitude"
+			:longitude="longitude"
+			:markers="marker"
+			:scale="scale"
+			:circles="circles"
+		>
+		<!--  :circles="circles" -->
+		</map>
+	</view>
+</template>
+
+<script>
+	import { geoDistance } from '@/common/util/util.js'
+	import amap from "@/common/js-sdk/js-amap/amap-wx.js";
+	// #ifdef H5
+	import AMap  from "@/common/js-sdk/js-amap/amap-h5.js";
+	// #endif
+	
+	export default {
+		 props:{
+			compLatitude:{
+				type:Number,
+				default:40.009390,
+				required:false
+			},
+			compLongitude:{
+				type:Number,
+				default:116.374322,
+				required:false
+			}
+		},
+		data() {
+			return {
+				amapPlugin:null,
+				wxMapKey:"53324ee357405c4a65f35a1aa05ffaf2",
+				id:0,
+			    title: 'map',
+				distance:0,
+				address:"",
+		       latitude: this.compLatitude,  //纬度
+			    longitude: this.compLongitude,  //经度
+				scale:16,//地图缩放程度
+				tipText:'打卡范围',
+				bgColor:'#00c16f',
+			    marker: [],
+			   circles:[{//在地图上显示圆
+				    latitude: this.compLatitude,
+				    longitude: this.compLongitude,
+				    radius:80,//半径
+					  fillColor:"#ffffffAA",//填充颜色
+				    color:"#55aaffAA",//描边的颜色
+				    strokeWidth:1//描边的宽度
+				}],
+				resAmap:null
+			}
+		},
+	    created() {
+			// #ifdef MP-WEIXIN || APP-PLUS
+	    	this.amapPlugin = new amap.AMapWX({
+	    	    key: this.wxMapKey
+	    	});
+			// #endif
+			
+			// #ifdef H5
+			this.initAMap()
+			// #endif
+	    },
+		mounted() {
+			// #ifdef MP-WEIXIN
+			  this.getAuthorizeInfo();
+			// #endif
+			// #ifdef APP-PLUS
+			  this.getLocationInfoWx();
+			// #endif
+			// #ifdef H5
+			  //this.getLocationInfo()
+			// #endif
+		},
+		computed:{
+		  inCircle(){
+			return this.address && this.distance <= 80
+		  }
+		},
+		methods: {
+			allowed(){
+				return this.inCircle
+			},
+			getMyAddress(){
+				return this.address
+			},
+			refreshLocation(){
+				// #ifdef MP-WEIXIN
+				  this.getAuthorizeInfo();
+				// #endif
+				// #ifdef APP-PLUS
+				  this.getLocationInfoWx();
+				// #endif
+				// #ifdef H5
+				  this.initAMap()
+				// #endif
+			},
+			getAuthorizeInfo(){
+				//1. uniapp弹窗弹出获取授权(地理,个人微信信息等授权信息)弹窗
+				var _this=this;
+				uni.authorize({
+					scope: "scope.userLocation",
+					success() { //1.1 允许授权
+						_this.getLocationInfoWx();
+					},
+					fail(){    //1.2 拒绝授权
+						console.log("你拒绝了授权,无法获得周边信息")
+						_this.openConfirm();
+					}
+				})
+			},
+			getLocationInfoWx(){
+				var that=this;
+				this.amapPlugin.getRegeo({
+				    type: 'gcj02', //map 组件使用的经纬度是国测局坐标, type 为 gcj02
+				    success: function(res) {
+						console.log("success",res);
+						that.latitude = res[0].latitude;
+						that.longitude = res[0].longitude;
+						that.address = res[0].name + res[0].desc;
+						that.distance=geoDistance(that.longitude, that.latitude,that.compLongitude,that.compLatitude)
+						console.log("that.distance",that.distance);
+						let tipText=(that.distance>80?"未在":"已在")+"打卡范围内";
+						let bgColor=that.distance>80?"#ff0000":"#00c16f";
+						let marker={
+							   id:0,
+						     latitude:that.latitude,//纬度
+						     longitude:that.longitude,//经度
+						       iconPath: '/static/location.png',
+							   width:35,
+							   height:35,
+							   // #ifdef MP-WEIXIN
+							   label:{//为标记点旁边增加标签
+							     content:tipText,//文本
+						        color:'#ffffff',//文本颜色
+								  fontSize:14,//文字大小
+								   borderWidth:2,//边框宽度
+								   borderColor:bgColor,//边框颜色 
+								   bgColor:bgColor,//背景颜色
+								  borderRadius:2,//边框圆角  
+								  padding:5,//文本边缘留白
+								   textAlign:'center',//文本对齐方式
+								   x:0,//label的坐标,原点是 marker 对应的经纬度
+								   y:0,//label的坐标,原点是 marker 对应的经纬度 
+							    }, 
+							   // #endif
+							   // #ifdef APP-PLUS
+							   callout:{//自定义标记点上方的气泡窗口 点击有效
+							     content:tipText,//文本
+							     color:'#ffffff',//文字颜色
+							     fontSize:14,//文本大小
+							     //borderRadius:2,//边框圆角
+							     bgColor:bgColor,//背景颜色
+							     display:'ALWAYS',//常显
+								   textAlign:'center'
+							    },
+							   // #endif
+						    }
+							that.marker=[marker];
+				    },
+				    fail: (res) => {
+				      console.log(JSON.stringify(res));
+				    }
+				 });
+			},
+			getLocationInfo() {
+				var _this=this;
+				uni.showLoading({
+					title: '获取信息中',
+					mask:true
+				});
+				uni.getLocation({
+					//type: 'wgs84',
+					type:'gcj02',
+					success: function (res) {
+						console.log('当前位置的经度:' + res.longitude);
+						console.log('当前位置的纬度:' + res.latitude);
+					    _this.distance=geoDistance(res.longitude, res.latitude,_this.compLongitude,_this.compLatitude)
+						let tipText=(_this.distance>80?"未在":"已在")+"打卡范围内";
+						let bgColor=_this.distance>80?"#ff0000":"#00c16f";
+						_this.longitude=res.longitude
+						_this.latitude=res.latitude
+						let marker={
+					         latitude: res.latitude,//纬度
+					         longitude:res.longitude,//经度
+					         callout:{//自定义标记点上方的气泡窗口 点击有效
+					    	     content:tipText,//文本
+					    	     color:'#ffffff',//文字颜色
+					    	     fontSize:14,//文本大小
+					    	     borderRadius:2,//边框圆角
+					    	     bgColor:bgColor,//背景颜色
+					    	     display:'ALWAYS'//常显
+					           }
+					        }
+							_this.marker=[marker];
+					},
+					fail: function (res){
+						console.log('getLocation==> fail:' + res);
+						console.log(res);
+					}
+				});
+				uni.hideLoading();
+			},
+			 // 当用户第一次拒绝后再次请求授权
+			openConfirm(){
+				uni.showModal({
+					title: '请求授权当前位置',
+					content: '需要获取您的地理位置,请确认授权',
+					success: (res)=> {
+						if (res.confirm) {
+							uni.openSetting();// 打开地图权限设置
+						} else if (res.cancel) {
+							uni.showToast({
+								title: '你拒绝了授权,无法获得位置信息',
+								icon: 'none',
+								duration: 1000
+							})
+						}
+					}
+				});
+			},
+		  // 根据坐标返回地址(逆地理编码)
+	      /* async getAddress (points) {
+			  try {
+			  	this.resAmap = await AMap();
+			  	this.$nextTick(function() {
+			  		this.resAmap.plugin('AMap.Geocoder', () => {
+			  			var geocoder  = new this.resAmap.Geocoder({
+			  				radius: 1000,
+						});
+			  			geocoder.getAddress(points, (status, result) => {
+							if (status === 'complete' && result.regeocode) {
+							  this.address = result.regeocode.formattedAddress
+							}
+						})
+			  		});
+			  	})
+			  } catch (e) {
+			  	console.log(e)
+			  }
+			}, */
+			// #ifdef H5
+			async initAMap() {
+				try {
+					uni.showLoading({
+						title: '定位中...',
+						mask:true
+					});
+					this.resAmap = await AMap();
+					this.$nextTick(function() {
+						this.resAmap.plugin('AMap.Geolocation', () => {
+							var geolocation = new this.resAmap.Geolocation({
+								enableHighAccuracy: true, //是否使用高精度定位,默认:true
+								timeout: 10000, //超过10秒后停止定位,默认:5s
+								buttonPosition: 'RB', //定位按钮的停靠位置
+								// buttonOffset: new AMap.Pixel(10, 20),//定位按钮与设置的停靠位置的偏移量,默认:Pixel(10, 20)
+								zoomToAccuracy: true, //定位成功后是否自动调整地图视野到定位点
+					
+							});
+							geolocation.getCurrentPosition(function(status, result) {
+								if (status == 'complete') {
+									onComplete(result)
+								} else {
+									onError(result)
+								}
+							});
+					
+						});
+						
+						//解析定位结果
+						var _this = this;
+					
+						function onComplete(data) {
+							console.log("H5高德定位",data) 
+							console.log('当前位置的经度:' + data.position.lat);
+							console.log('当前位置的纬度:' + data.position.lng);
+							_this.distance=geoDistance(data.position.lng, data.position.lat,_this.compLongitude,_this.compLatitude)
+							let tipText=(_this.distance>80?"未在":"已在")+"打卡范围内";
+							let bgColor=_this.distance>80?"#ff0000":"#00c16f";
+							_this.longitude=data.position.lng
+							_this.latitude=data.position.lat
+							_this.address=data.formattedAddress
+							let marker={
+							     latitude: _this.latitude,//纬度
+							     longitude:_this.longitude,//经度
+							     callout:{//自定义标记点上方的气泡窗口 点击有效
+								     content:tipText,//文本
+								     color:'#ffffff',//文字颜色
+								     fontSize:14,//文本大小
+								     borderRadius:2,//边框圆角
+								     bgColor:bgColor,//背景颜色
+								     display:'ALWAYS'//常显
+							       }
+							    }
+								_this.marker=[marker];
+								uni.hideLoading();
+								_this.$tip.success("定位成功")
+						}
+					
+						function onError(data) {
+							console.log(data) // 定位失败的信息
+						}
+					
+					})
+				} catch (e) {
+					console.log(e)
+				   _this.$tip.alert("定位失败")
+			    }
+			}, 
+			// #endif
+			
+		}
+	}
+</script>
+
+<style>
+
+</style>

+ 79 - 0
components/my-componets/my-nav.vue

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

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

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

+ 179 - 0
components/uni-calendar/uni-calendar-item.vue

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

+ 512 - 0
components/uni-calendar/uni-calendar.vue

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

+ 352 - 0
components/uni-calendar/util.js

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

+ 54 - 0
components/uni-collapse/uni-collapse.vue

@@ -0,0 +1,54 @@
+<template>
+	<view class="uni-collapse">
+		<slot />
+	</view>
+</template>
+<script>
+	export default {
+		name: 'UniCollapse',
+		props: {
+			accordion: {
+				// 是否开启手风琴效果
+				type: [Boolean, String],
+				default: false
+			}
+		},
+		data() {
+			return {}
+		},
+		provide() {
+			return {
+				collapse: this
+			}
+		},
+		created() {
+			this.childrens = []
+		},
+		methods: {
+			onChange() {
+				let activeItem = []
+				this.childrens.forEach((vm, index) => {
+					if (vm.isOpen) {
+						activeItem.push(vm.nameSync)
+					}
+				})
+				this.$emit('change', activeItem)
+			}
+		}
+	}
+</script>
+<style lang="scss" scoped>
+	@import '@/uni.scss';
+
+	.uni-collapse {
+		/* #ifndef APP-NVUE */
+		width: 100%;
+		display: flex;
+		/* #endif */
+		/* #ifdef APP-NVUE */
+		flex: 1;
+		/* #endif */
+		flex-direction: column;
+		background-color: $uni-bg-color;
+	}
+</style>

File diff suppressed because it is too large
+ 0 - 0
components/w-picker/areadata/areadata.js


+ 742 - 0
components/w-picker/date-picker.vue

@@ -0,0 +1,742 @@
+<template>
+	<view class="w-picker-view">
+		<picker-view v-if="fields=='year'" class="d-picker-view" :indicator-style="itemHeight" :value="pickVal" @change="handlerChange">
+			<picker-view-column>
+				<view class="w-picker-item" v-for="(item,index) in range.years" :key="index">{{item}}年</view>
+			</picker-view-column>
+		</picker-view>
+		<picker-view v-if="fields=='month'" class="d-picker-view" :indicator-style="itemHeight" :value="pickVal" @change="handlerChange">
+			<picker-view-column>
+				<view class="w-picker-item" v-for="(item,index) in range.years" :key="index">{{item}}年</view>
+			</picker-view-column>
+			<picker-view-column>
+				<view class="w-picker-item" v-for="(item,index) in range.months" :key="index">{{item}}月</view>
+			</picker-view-column>
+		</picker-view>
+		<picker-view v-if="fields=='day'" class="d-picker-view" :indicator-style="itemHeight" :value="pickVal" @change="handlerChange">
+			<picker-view-column>
+				<view class="w-picker-item" v-for="(item,index) in range.years" :key="index">{{item}}年</view>
+			</picker-view-column>
+			<picker-view-column>
+				<view class="w-picker-item" v-for="(item,index) in range.months" :key="index">{{item}}月</view>
+			</picker-view-column>
+			<picker-view-column>
+				<view class="w-picker-item" v-for="(item,index) in range.days" :key="index">{{item}}日</view>
+			</picker-view-column>
+		</picker-view>
+		<picker-view v-if="fields=='hour'" class="d-picker-view" :indicator-style="itemHeight" :value="pickVal" @change="handlerChange">
+			<picker-view-column>
+				<view class="w-picker-item" v-for="(item,index) in range.years" :key="index">{{item}}年</view>
+			</picker-view-column>
+			<picker-view-column>
+				<view class="w-picker-item" v-for="(item,index) in range.months" :key="index">{{item}}月</view>
+			</picker-view-column>
+			<picker-view-column>
+				<view class="w-picker-item" v-for="(item,index) in range.days" :key="index">{{item}}日</view>
+			</picker-view-column>
+			<picker-view-column>
+				<view class="w-picker-item" v-for="(item,index) in range.hours" :key="index">{{item}}时</view>
+			</picker-view-column>
+		</picker-view>
+		<picker-view v-if="fields=='minute'" class="d-picker-view" :indicator-style="itemHeight" :value="pickVal" @change="handlerChange">
+			<picker-view-column>
+				<view class="w-picker-item" v-for="(item,index) in range.years" :key="index">{{item}}年</view>
+			</picker-view-column>
+			<picker-view-column>
+				<view class="w-picker-item" v-for="(item,index) in range.months" :key="index">{{item}}月</view>
+			</picker-view-column>
+			<picker-view-column>
+				<view class="w-picker-item" v-for="(item,index) in range.days" :key="index">{{item}}日</view>
+			</picker-view-column>
+			<picker-view-column>
+				<view class="w-picker-item" v-for="(item,index) in range.hours" :key="index">{{item}}时</view>
+			</picker-view-column>
+			<picker-view-column>
+				<view class="w-picker-item" v-for="(item,index) in range.minutes" :key="index">{{item}}分</view>
+			</picker-view-column>
+		</picker-view>
+		<picker-view v-if="fields=='second'" class="d-picker-view" :indicator-style="itemHeight" :value="pickVal" @change="handlerChange">
+			<picker-view-column>
+				<view class="w-picker-item" v-for="(item,index) in range.years" :key="index">{{item}}年</view>
+			</picker-view-column>
+			<picker-view-column>
+				<view class="w-picker-item" v-for="(item,index) in range.months" :key="index">{{item}}月</view>
+			</picker-view-column>
+			<picker-view-column>
+				<view class="w-picker-item" v-for="(item,index) in range.days" :key="index">{{item}}日</view>
+			</picker-view-column>
+			<picker-view-column>
+				<view class="w-picker-item" v-for="(item,index) in range.hours" :key="index">{{item}}时</view>
+			</picker-view-column>
+			<picker-view-column>
+				<view class="w-picker-item" v-for="(item,index) in range.minutes" :key="index">{{item}}分</view>
+			</picker-view-column>
+			<picker-view-column>
+				<view class="w-picker-item" v-for="(item,index) in range.seconds" :key="index">{{item}}秒</view>
+			</picker-view-column>
+		</picker-view>
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				pickVal:[],
+				range:{
+					years:[],
+					months:[],
+					days:[],
+					hours:[],
+					minutes:[],
+					seconds:[]
+				},
+				checkObj:{}
+			};
+		},
+		props:{
+			itemHeight:{
+				type:String,
+				default:"44px"
+			},
+			startYear:{
+				type:[String,Number],
+				default:""
+			},
+			endYear:{
+				type:[String,Number],
+				default:""
+			},
+			value:{
+				type:[String,Array,Number],
+				default:""
+			},
+			current:{//是否默认选中当前日期
+				type:Boolean,
+				default:false
+			},
+			disabledAfter:{//是否禁用当前之后的日期
+				type:Boolean,
+				default:false
+			},
+			fields:{
+				type:String,
+				default:"day"
+			}
+		},
+		watch:{
+			fields(val){
+				this.initData();
+			},
+			value(val){
+				this.initData();
+			}
+		},
+		created() {
+			this.initData();
+		},
+		methods:{
+			formatNum(n){
+				return (Number(n)<10?'0'+Number(n):Number(n)+'');
+			},
+			checkValue(value){
+				let strReg,example
+				switch(this.fields){
+					case "year":
+						strReg=/^\d{4}$/;
+						example="2019";
+						break;
+					case "month":
+						strReg=/^\d{4}-\d{2}$/;
+						example="2019-02";
+						break;
+					case "day":
+						strReg=/^\d{4}-\d{2}-\d{2}$/;
+						example="2019-02-01";
+						break;
+					case "hour":
+						strReg=/^\d{4}-\d{2}-\d{2} \d{2}(:\d{2}){1,2}?$/;
+						example="2019-02-01 18:00:00或2019-02-01 18";
+						break;
+					case "minute":
+						strReg=/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}(:\d{2}){0,1}?$/;
+						example="2019-02-01 18:06:00或2019-02-01 18:06";
+						break;
+					case "second":
+						strReg=/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/;
+						example="2019-02-01 18:06:01";
+						break;
+				}
+				if(!strReg.test(value)){
+					console.log(new Error("请传入与mode、fields匹配的value值,例value="+example+""))
+				}
+				return strReg.test(value);
+			},
+			resetData(year,month,day,hour,minute){
+				let curDate=this.getCurrenDate();
+				let curFlag=this.current;
+				let curYear=curDate.curYear;
+				let curMonth=curDate.curMonth;
+				let curDay=curDate.curDay;
+				let curHour=curDate.curHour;
+				let curMinute=curDate.curMinute;
+				let curSecond=curDate.curSecond;
+				let months=[],days=[],hours=[],minutes=[],seconds=[];
+				let disabledAfter=this.disabledAfter;
+				let monthsLen=disabledAfter?(year*1<curYear?12:curMonth):12;
+				let totalDays=new Date(year,month,0).getDate();//计算当月有几天;
+				let daysLen=disabledAfter?((year*1<curYear||month*1<curMonth)?totalDays:curDay):totalDays;
+				let hoursLen=disabledAfter?((year*1<curYear||month*1<curMonth||day*1<curDay)?24:curHour+1):24;
+				let minutesLen=disabledAfter?((year*1<curYear||month*1<curMonth||day*1<curDay||hour*1<curHour)?60:curMinute+1):60;
+				let secondsLen=disabledAfter?((year*1<curYear||month*1<curMonth||day*1<curDay||hour*1<curHour||minute*1<curMinute)?60:curSecond+1):60;
+				for(let month=1;month<=monthsLen;month++){
+					months.push(this.formatNum(month));
+				};
+				for(let day=1;day<=daysLen;day++){
+					days.push(this.formatNum(day));
+				}
+				for(let hour=0;hour<hoursLen;hour++){
+					hours.push(this.formatNum(hour));
+				}
+				for(let minute=0;minute<minutesLen;minute++){
+					minutes.push(this.formatNum(minute));
+				}
+				for(let second=0;second<secondsLen;second++){
+					seconds.push(this.formatNum(second));
+				}
+				return{
+					months,
+					days,
+					hours,
+					minutes,
+					seconds
+				}
+			},
+			isLeapYear (Year) {
+				if (((Year % 4)==0) && ((Year % 100)!=0) || ((Year % 400)==0)) {
+					return true;
+				} else { 
+					return false; 
+				}
+			},
+			getData(dVal){
+				//用来处理初始化数据
+				let curFlag=this.current;
+				let disabledAfter=this.disabledAfter;
+				let fields=this.fields;
+				let curDate=this.getCurrenDate();
+				let curYear=curDate.curYear;
+				let curMonthdays=curDate.curMonthdays;
+				let curMonth=curDate.curMonth;
+				let curDay=curDate.curDay;
+				let curHour=curDate.curHour;
+				let curMinute=curDate.curMinute;
+				let curSecond=curDate.curSecond;
+				let defaultDate=this.getDefaultDate();
+				let startYear=this.getStartDate().getFullYear();
+				let endYear=this.getEndDate().getFullYear();
+				//颗粒度,禁用当前之后日期仅对year,month,day,hour生效;分钟秒禁用没有意义,
+				let years=[],months=[],days=[],hours=[],minutes=[],seconds=[];
+				let year=dVal[0]*1;
+				let month=dVal[1]*1;
+				let day=dVal[2]*1;
+				let hour=dVal[3]*1;
+				let minute=dVal[4]*1;
+				let monthsLen=disabledAfter?(year<curYear?12:curDate.curMonth):12;
+				let daysLen=disabledAfter?((year<curYear||month<curMonth)?defaultDate.defaultDays:curDay):(curFlag?curMonthdays:defaultDate.defaultDays);
+				let hoursLen=disabledAfter?((year<curYear||month<curMonth||day<curDay)?24:curHour+1):24;
+				let minutesLen=disabledAfter?((year<curYear||month<curMonth||day<curDay||hour<curHour)?60:curMinute+1):60;
+				let secondsLen=disabledAfter?((year<curYear||month<curMonth||day<curDay||hour<curHour||minute<curMinute)?60:curSecond+1):60;
+				for(let year=startYear;year<=(disabledAfter?curYear:endYear);year++){
+					years.push(year.toString())
+				}
+				for(let month=1;month<=monthsLen;month++){
+					months.push(this.formatNum(month));
+				}
+				for(let day=1;day<=daysLen;day++){
+					days.push(this.formatNum(day));
+				}
+				for(let hour=0;hour<hoursLen;hour++){
+					hours.push(this.formatNum(hour));
+				}
+				for(let minute=0;minute<minutesLen;minute++){
+					minutes.push(this.formatNum(minute));
+				}
+				// for(let second=0;second<(disabledAfter?curDate.curSecond+1:60);second++){
+				// 	seconds.push(this.formatNum(second));
+				// }
+				for(let second=0;second<60;second++){
+					seconds.push(this.formatNum(second));
+				}
+				return {
+					years,
+					months,
+					days,
+					hours,
+					minutes,
+					seconds
+				}
+			},
+			getCurrenDate(){
+				let curDate=new Date();
+				let curYear=curDate.getFullYear();
+				let curMonth=curDate.getMonth()+1;
+				let curMonthdays=new Date(curYear,curMonth,0).getDate();
+				let curDay=curDate.getDate();
+				let curHour=curDate.getHours();
+				let curMinute=curDate.getMinutes();
+				let curSecond=curDate.getSeconds();
+				return{
+					curDate,
+					curYear,
+					curMonth,
+					curMonthdays,
+					curDay,
+					curHour,
+					curMinute,
+					curSecond
+				}
+			},
+			getDefaultDate(){
+				let value=this.value;
+				let reg=/-/g;
+				let defaultDate=value?new Date(value.replace(reg,"/")):new Date();
+				let defaultYear=defaultDate.getFullYear();
+				let defaultMonth=defaultDate.getMonth()+1;
+				let defaultDay=defaultDate.getDate();
+				let defaultDays=new Date(defaultYear,defaultMonth,0).getDate()*1;
+				return{
+					defaultDate,
+					defaultYear,
+					defaultMonth,
+					defaultDay,
+					defaultDays
+				}
+			},
+			getStartDate(){
+				let start=this.startYear;
+				let startDate="";
+				let reg=/-/g;
+				if(start){
+					startDate=new Date(start+"/01/01");
+				}else{
+					startDate=new Date("1970/01/01");
+				}
+				return startDate;
+			},
+			getEndDate(){
+				let end=this.endYear;
+				let reg=/-/g;
+				let endDate="";
+				if(end){
+					endDate=new Date(end+"/12/01");
+				}else{
+					endDate=new Date();
+				}
+				return endDate;
+			},
+			getDval(){
+				let value=this.value;
+				let fields=this.fields;
+				let dVal=null;
+				let aDate=new Date();
+				let year=this.formatNum(aDate.getFullYear());
+				let month=this.formatNum(aDate.getMonth()+1);
+				let day=this.formatNum(aDate.getDate());
+				let hour=this.formatNum(aDate.getHours());
+				let minute=this.formatNum(aDate.getMinutes());
+				let second=this.formatNum(aDate.getSeconds());
+				if(value){
+					let flag=this.checkValue(value);
+					if(!flag){
+						dVal=[year,month,day,hour,minute,second]
+					}else{
+						switch(this.fields){
+							case "year":
+								dVal=value?[value]:[];	
+								break;
+							case "month":
+								dVal=value?value.split("-"):[];
+								break;
+							case "day":
+								dVal=value?value.split("-"):[];
+								break;
+							case "hour":
+								dVal=[...value.split(" ")[0].split("-"),...value.split(" ")[1].split(":")];
+								break;
+							case "minute":
+								dVal=value?[...value.split(" ")[0].split("-"),...value.split(" ")[1].split(":")]:[];
+								break;
+							case "second":
+								dVal=[...value.split(" ")[0].split("-"),...value.split(" ")[1].split(":")];
+								break;
+						}
+					}
+				}else{
+					dVal=[year,month,day,hour,minute,second]
+				}
+				return dVal;
+			},
+			initData(){
+				let startDate,endDate,startYear,endYear,startMonth,endMonth,startDay,endDay;
+				let years=[],months=[],days=[],hours=[],minutes=[],seconds=[];
+				let dVal=[],pickVal=[];
+				let value=this.value;
+				let reg=/-/g;
+				let range={};
+				let result="",full="",year,month,day,hour,minute,second,obj={};
+				let defaultDate=this.getDefaultDate();
+				let defaultYear=defaultDate.defaultYear;
+				let defaultMonth=defaultDate.defaultMonth;
+				let defaultDay=defaultDate.defaultDay;
+				let defaultDays=defaultDate.defaultDays;
+				let curFlag=this.current;
+				let disabledAfter=this.disabledAfter;
+				let curDate=this.getCurrenDate();
+				let curYear=curDate.curYear;
+				let curMonth=curDate.curMonth;
+				let curMonthdays=curDate.curMonthdays;
+				let curDay=curDate.curDay;
+				let curHour=curDate.curHour;
+				let curMinute=curDate.curMinute;
+				let curSecond=curDate.curSecond;
+				let dateData=[];
+				dVal=this.getDval();
+				
+				startDate=this.getStartDate();
+				endDate=this.getEndDate();
+				startYear=startDate.getFullYear();
+				startMonth=startDate.getMonth();
+				startDay=startDate.getDate();
+				endYear=endDate.getFullYear();
+				endMonth=endDate.getMonth();
+				endDay=endDate.getDate();
+				dateData=this.getData(dVal);
+				years=dateData.years;
+				months=dateData.months;
+				days=dateData.days;
+				hours=dateData.hours;
+				minutes=dateData.minutes;
+				seconds=dateData.seconds;
+				switch(this.fields){
+					case "year":
+						pickVal=disabledAfter?[
+							dVal[0]&&years.indexOf(dVal[0])!=-1?years.indexOf(dVal[0]):0
+						]:(curFlag?[
+							years.indexOf(curYear+'')
+						]:[
+							dVal[0]&&years.indexOf(dVal[0])!=-1?years.indexOf(dVal[0]):0
+						]);
+						range={years};
+						year=dVal[0]?dVal[0]:years[0];
+						result=full=`${year}`;
+						obj={
+							year
+						}
+						break;
+					case "month":
+						pickVal=disabledAfter?[
+							dVal[0]&&years.indexOf(dVal[0])!=-1?years.indexOf(dVal[0]):0,
+							dVal[1]&&months.indexOf(dVal[1])!=-1?months.indexOf(dVal[1]):0
+						]:(curFlag?[
+							years.indexOf(curYear+''),
+							months.indexOf(this.formatNum(curMonth))
+						]:[
+							dVal[0]&&years.indexOf(dVal[0])!=-1?years.indexOf(dVal[0]):0,
+							dVal[1]&&months.indexOf(dVal[1])!=-1?months.indexOf(dVal[1]):0
+						]);
+						range={years,months};
+						year=dVal[0]?dVal[0]:years[0];
+						month=dVal[1]?dVal[1]:months[0];
+						result=full=`${year+'-'+month}`;
+						obj={
+							year,
+							month
+						}
+						break;
+					case "day":
+						pickVal=disabledAfter?[
+							dVal[0]&&years.indexOf(dVal[0])!=-1?years.indexOf(dVal[0]):0,
+							dVal[1]&&months.indexOf(dVal[1])!=-1?months.indexOf(dVal[1]):0,
+							dVal[2]&&days.indexOf(dVal[2])!=-1?days.indexOf(dVal[2]):0
+						]:(curFlag?[
+							years.indexOf(curYear+''),
+							months.indexOf(this.formatNum(curMonth)),
+							days.indexOf(this.formatNum(curDay)),
+						]:[
+							dVal[0]&&years.indexOf(dVal[0])!=-1?years.indexOf(dVal[0]):0,
+							dVal[1]&&months.indexOf(dVal[1])!=-1?months.indexOf(dVal[1]):0,
+							dVal[2]&&days.indexOf(dVal[2])!=-1?days.indexOf(dVal[2]):0
+						]);
+						range={years,months,days};
+						year=dVal[0]?dVal[0]:years[0];
+						month=dVal[1]?dVal[1]:months[0];
+						day=dVal[2]?dVal[2]:days[0];
+						result=full=`${year+'-'+month+'-'+day}`;
+						obj={
+							year,
+							month,
+							day
+						}
+						break;
+					case "hour":
+						pickVal=disabledAfter?[
+							dVal[0]&&years.indexOf(dVal[0])!=-1?years.indexOf(dVal[0]):0,
+							dVal[1]&&months.indexOf(dVal[1])!=-1?months.indexOf(dVal[1]):0,
+							dVal[2]&&days.indexOf(dVal[2])!=-1?days.indexOf(dVal[2]):0,
+							dVal[3]&&hours.indexOf(dVal[3])!=-1?hours.indexOf(dVal[3]):0
+						]:(curFlag?[
+							years.indexOf(curYear+''),
+							months.indexOf(this.formatNum(curMonth)),
+							days.indexOf(this.formatNum(curDay)),
+							hours.indexOf(this.formatNum(curHour)),
+						]:[
+							dVal[0]&&years.indexOf(dVal[0])!=-1?years.indexOf(dVal[0]):0,
+							dVal[1]&&months.indexOf(dVal[1])!=-1?months.indexOf(dVal[1]):0,
+							dVal[2]&&days.indexOf(dVal[2])!=-1?days.indexOf(dVal[2]):0,
+							dVal[3]&&hours.indexOf(dVal[3])!=-1?hours.indexOf(dVal[3]):0
+						]);
+						range={years,months,days,hours};
+						year=dVal[0]?dVal[0]:years[0];
+						month=dVal[1]?dVal[1]:months[0];
+						day=dVal[2]?dVal[2]:days[0];
+						hour=dVal[3]?dVal[3]:hours[0];
+						result=`${year+'-'+month+'-'+day+' '+hour}`;
+						full=`${year+'-'+month+'-'+day+' '+hour+':00:00'}`;
+						obj={
+							year,
+							month,
+							day,
+							hour
+						}
+						break;
+					case "minute":
+						pickVal=disabledAfter?[
+							dVal[0]&&years.indexOf(dVal[0])!=-1?years.indexOf(dVal[0]):0,
+							dVal[1]&&months.indexOf(dVal[1])!=-1?months.indexOf(dVal[1]):0,
+							dVal[2]&&days.indexOf(dVal[2])!=-1?days.indexOf(dVal[2]):0,
+							dVal[3]&&hours.indexOf(dVal[3])!=-1?hours.indexOf(dVal[3]):0,
+							dVal[4]&&minutes.indexOf(dVal[4])!=-1?minutes.indexOf(dVal[4]):0
+						]:(curFlag?[
+							years.indexOf(curYear+''),
+							months.indexOf(this.formatNum(curMonth)),
+							days.indexOf(this.formatNum(curDay)),
+							hours.indexOf(this.formatNum(curHour)),
+							minutes.indexOf(this.formatNum(curMinute)),
+						]:[
+							dVal[0]&&years.indexOf(dVal[0])!=-1?years.indexOf(dVal[0]):0,
+							dVal[1]&&months.indexOf(dVal[1])!=-1?months.indexOf(dVal[1]):0,
+							dVal[2]&&days.indexOf(dVal[2])!=-1?days.indexOf(dVal[2]):0,
+							dVal[3]&&hours.indexOf(dVal[3])!=-1?hours.indexOf(dVal[3]):0,
+							dVal[4]&&minutes.indexOf(dVal[4])!=-1?minutes.indexOf(dVal[4]):0
+						]);
+						range={years,months,days,hours,minutes};
+						year=dVal[0]?dVal[0]:years[0];
+						month=dVal[1]?dVal[1]:months[0];
+						day=dVal[2]?dVal[2]:days[0];
+						hour=dVal[3]?dVal[3]:hours[0];
+						minute=dVal[4]?dVal[4]:minutes[0];
+						full=`${year+'-'+month+'-'+day+' '+hour+':'+minute+':00'}`;
+						result=`${year+'-'+month+'-'+day+' '+hour+':'+minute}`;
+						obj={
+							year,
+							month,
+							day,
+							hour,
+							minute
+						}
+						break;
+					case "second":
+						pickVal=disabledAfter?[
+							dVal[0]&&years.indexOf(dVal[0])!=-1?years.indexOf(dVal[0]):0,
+							dVal[1]&&months.indexOf(dVal[1])!=-1?months.indexOf(dVal[1]):0,
+							dVal[2]&&days.indexOf(dVal[2])!=-1?days.indexOf(dVal[2]):0,
+							dVal[3]&&hours.indexOf(dVal[3])!=-1?hours.indexOf(dVal[3]):0,
+							dVal[4]&&minutes.indexOf(dVal[4])!=-1?minutes.indexOf(dVal[4]):0,
+							dVal[5]&&seconds.indexOf(dVal[5])!=-1?seconds.indexOf(dVal[5]):0
+						]:(curFlag?[
+							years.indexOf(curYear+''),
+							months.indexOf(this.formatNum(curMonth)),
+							days.indexOf(this.formatNum(curDay)),
+							hours.indexOf(this.formatNum(curHour)),
+							minutes.indexOf(this.formatNum(curMinute)),
+							seconds.indexOf(this.formatNum(curSecond)),
+						]:[
+							dVal[0]&&years.indexOf(dVal[0])!=-1?years.indexOf(dVal[0]):0,
+							dVal[1]&&months.indexOf(dVal[1])!=-1?months.indexOf(dVal[1]):0,
+							dVal[2]&&days.indexOf(dVal[2])!=-1?days.indexOf(dVal[2]):0,
+							dVal[3]&&hours.indexOf(dVal[3])!=-1?hours.indexOf(dVal[3]):0,
+							dVal[4]&&minutes.indexOf(dVal[4])!=-1?minutes.indexOf(dVal[4]):0,
+							dVal[5]&&seconds.indexOf(dVal[5])!=-1?seconds.indexOf(dVal[5]):0
+						]);
+						range={years,months,days,hours,minutes,seconds};
+						year=dVal[0]?dVal[0]:years[0];
+						month=dVal[1]?dVal[1]:months[0];
+						day=dVal[2]?dVal[2]:days[0];
+						hour=dVal[3]?dVal[3]:hours[0];
+						minute=dVal[4]?dVal[4]:minutes[0];
+						second=dVal[5]?dVal[5]:seconds[0];
+						result=full=`${year+'-'+month+'-'+day+' '+hour+':'+minute+':'+second}`;
+						obj={
+							year,
+							month,
+							day,
+							hour,
+							minute,
+							second
+						}
+						break;
+					default:
+						range={years,months,days};
+						break;
+				}
+				this.range=range;
+				this.checkObj=obj;
+				this.$emit("change",{
+					result:result,
+					value:full,
+					obj:obj
+				});
+				this.$nextTick(()=>{
+					this.pickVal=pickVal;
+				})
+			},
+			handlerChange(e){
+				let arr=[...e.detail.value];
+				let data=this.range;
+				let year="",month="",day="",hour="",minute="",second="";
+				let result="",full="",obj={};
+				let months=null,days=null,hours=null,minutes=null,seconds=null;
+				let disabledAfter=this.disabledAfter;
+				let leapYear=false,resetData={};
+				year=(arr[0]||arr[0]==0)?data.years[arr[0]]||data.years[data.years.length-1]:"";
+				month=(arr[1]||arr[1]==0)?data.months[arr[1]]||data.months[data.months.length-1]:"";
+				day=(arr[2]||arr[2]==0)?data.days[arr[2]]||data.days[data.days.length-1]:"";
+				hour=(arr[3]||arr[3]==0)?data.hours[arr[3]]||data.hours[data.hours.length-1]:"";
+				minute=(arr[4]||arr[4]==0)?data.minutes[arr[4]]||data.minutes[data.minutes.length-1]:"";
+				second=(arr[5]||arr[5]==0)?data.seconds[arr[5]]||data.seconds[data.seconds.length-1]:"";
+				resetData=this.resetData(year,month,day,hour,minute);//重新拉取当前日期数据;
+				leapYear=this.isLeapYear(year);//判断是否为闰年;
+				switch(this.fields){
+					case "year":
+						result=full=`${year}`;
+						obj={
+							year
+						};
+						break;
+					case "month":
+						result=full=`${year+'-'+month}`;
+						if(this.disabledAfter)months=resetData.months;
+						if(months)this.range.months=months;
+						obj={
+							year,
+							month
+						}
+						break;
+					case "day":
+						result=full=`${year+'-'+month+'-'+day}`;
+						if(this.disabledAfter){
+							months=resetData.months;
+							days=resetData.days;
+						}else{
+							if(leapYear||(month!=this.checkObj.month)||month==2){
+								days=resetData.days;
+							}
+						}
+						if(months)this.range.months=months;
+						if(days)this.range.days=days;
+						obj={
+							year,
+							month,
+							day
+						}
+						break;
+					case "hour":
+						result=`${year+'-'+month+'-'+day+' '+hour}`;
+						full=`${year+'-'+month+'-'+day+' '+hour+':00:00'}`;
+						if(this.disabledAfter){
+							months=resetData.months;
+							days=resetData.days;
+							hours=resetData.hours;
+						}else{
+							if(leapYear||(month!=this.checkObj.month)||month==2){
+								days=resetData.days;
+							}
+						}
+						if(months)this.range.months=months;
+						if(days)this.range.days=days;
+						if(hours)this.range.hours=hours;
+						obj={
+							year,
+							month,
+							day,
+							hour
+						}
+						break;
+					case "minute":
+						full=`${year+'-'+month+'-'+day+' '+hour+':'+minute+':00'}`;
+						result=`${year+'-'+month+'-'+day+' '+hour+':'+minute}`;
+						if(this.disabledAfter){
+							months=resetData.months;
+							days=resetData.days;
+							hours=resetData.hours;
+							minutes=resetData.minutes;
+						}else{
+							if(leapYear||(month!=this.checkObj.month)||month==2){
+								days=resetData.days;
+							}
+						}
+						if(months)this.range.months=months;
+						if(days)this.range.days=days;
+						if(hours)this.range.hours=hours;
+						if(minutes)this.range.minutes=minutes;
+						obj={
+							year,
+							month,
+							day,
+							hour,
+							minute
+						};
+						break;
+					case "second":
+						result=full=`${year+'-'+month+'-'+day+' '+hour+':'+minute+':'+second}`;
+						if(this.disabledAfter){
+							months=resetData.months;
+							days=resetData.days;
+							hours=resetData.hours;
+							minutes=resetData.minutes;
+							//seconds=resetData.seconds;
+						}else{
+							if(leapYear||(month!=this.checkObj.month)||month==2){
+								days=resetData.days;
+							}
+						}
+						if(months)this.range.months=months;
+						if(days)this.range.days=days;
+						if(hours)this.range.hours=hours;
+						if(minutes)this.range.minutes=minutes;
+						//if(seconds)this.range.seconds=seconds;
+						obj={
+							year,
+							month,
+							day,
+							hour,
+							minute,
+							second
+						}
+						break;
+				}
+				this.checkObj=obj;
+				this.$emit("change",{
+					result:result,
+					value:full,
+					obj:obj
+				})
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	@import "./w-picker.css";
+</style>

+ 346 - 0
components/w-picker/half-picker.vue

@@ -0,0 +1,346 @@
+<template>
+	<view class="w-picker-view">
+		<picker-view class="d-picker-view" :indicator-style="itemHeight" :value="pickVal" @change="handlerChange">
+			<picker-view-column>
+				<view class="w-picker-item" v-for="(item,index) in range.years" :key="index">{{item}}年</view>
+			</picker-view-column>
+			<picker-view-column>
+				<view class="w-picker-item" v-for="(item,index) in range.months" :key="index">{{item}}月</view>
+			</picker-view-column>
+			<picker-view-column>
+				<view class="w-picker-item" v-for="(item,index) in range.days" :key="index">{{item}}日</view>
+			</picker-view-column>
+			<picker-view-column>
+				<view class="w-picker-item" v-for="(item,index) in range.sections" :key="index">{{item}}</view>
+			</picker-view-column>
+		</picker-view>
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				pickVal:[],
+				range:{},
+				checkObj:{}
+			};
+		},
+		props:{
+			itemHeight:{
+				type:String,
+				default:"44px"
+			},
+			startYear:{
+				type:String,
+				default:""
+			},
+			endYear:{
+				type:String,
+				default:""
+			},
+			value:{
+				type:[String,Array,Number],
+				default:""
+			},
+			current:{//是否默认选中当前日期
+				type:Boolean,
+				default:false
+			},
+			disabledAfter:{//是否禁用当前之后的日期
+				type:Boolean,
+				default:false
+			}
+		},
+		watch:{
+			value(val){
+				this.initData();
+			}
+		},
+		created() {
+			this.initData();
+		},
+		methods:{
+			formatNum(n){
+				return (Number(n)<10?'0'+Number(n):Number(n)+'');
+			},
+			checkValue(value){
+				let strReg=/^\d{4}-\d{2}-\d{2} [\u4e00-\u9fa5]{2}$/,example;
+				if(!strReg.test(value)){
+					console.log(new Error("请传入与mode、fields匹配的value值,例value="+example+""))
+				}
+				return strReg.test(value);
+			},
+			resetData(year,month,day){
+				let curDate=this.getCurrenDate();
+				let curFlag=this.current;
+				let curYear=curDate.curYear;
+				let curMonth=curDate.curMonth;
+				let curDay=curDate.curDay;
+				let curHour=curDate.curHour;
+				let months=[],days=[],sections=[];
+				let disabledAfter=this.disabledAfter;
+				let monthsLen=disabledAfter?(year*1<curYear?12:curMonth):12;
+				let totalDays=new Date(year,month,0).getDate();//计算当月有几天;
+				let daysLen=disabledAfter?((year*1<curYear||month*1<curMonth)?totalDays:curDay):totalDays;
+				let sectionFlag=disabledAfter?((year*1<curYear||month*1<curMonth||day*1<curDay)==true?false:true):(curHour>12==true?true:false);
+				sections=["上午","下午"];
+				for(let month=1;month<=monthsLen;month++){
+					months.push(this.formatNum(month));
+				};
+				for(let day=1;day<=daysLen;day++){
+					days.push(this.formatNum(day));
+				}
+				if(sectionFlag){
+					sections=["上午"];
+				}
+				return{
+					months,
+					days,
+					sections
+				}
+			},
+			getData(dVal){
+				//用来处理初始化数据
+				let curFlag=this.current;
+				let disabledAfter=this.disabledAfter;
+				let curDate=this.getCurrenDate();
+				let curYear=curDate.curYear;
+				let curMonthdays=curDate.curMonthdays;
+				let curMonth=curDate.curMonth;
+				let curDay=curDate.curDay;
+				let curHour=curDate.curHour;
+				let defaultDate=this.getDefaultDate();
+				let startYear=this.getStartDate().getFullYear();
+				let endYear=this.getEndDate().getFullYear();
+				let years=[],months=[],days=[],sections=[];
+				let year=dVal[0]*1;
+				let month=dVal[1]*1;
+				let day=dVal[2]*1;
+				let monthsLen=disabledAfter?(year<curYear?12:curDate.curMonth):12;
+				let daysLen=disabledAfter?((year<curYear||month<curMonth)?defaultDate.defaultDays:curDay):(curFlag?curMonthdays:defaultDate.defaultDays);
+				let sectionFlag=disabledAfter?((year*1<curYear||month*1<curMonth||day*1<curDay)==true?false:true):(curHour>12==true?true:false);
+				for(let year=startYear;year<=(disabledAfter?curYear:endYear);year++){
+					years.push(year.toString())
+				}
+				for(let month=1;month<=monthsLen;month++){
+					months.push(this.formatNum(month));
+				}
+				for(let day=1;day<=daysLen;day++){
+					days.push(this.formatNum(day));
+				}
+				if(sectionFlag){
+					sections=["下午"];
+				}else{
+					sections=["上午","下午"];
+				}
+				sections=["上午","下午"];
+				return {
+					years,
+					months,
+					days,
+					sections
+				}
+			},
+			getCurrenDate(){
+				let curDate=new Date();
+				let curYear=curDate.getFullYear();
+				let curMonth=curDate.getMonth()+1;
+				let curMonthdays=new Date(curYear,curMonth,0).getDate();
+				let curDay=curDate.getDate();
+				let curHour=curDate.getHours();
+				let curSection="上午";
+				if(curHour>=12){
+					curSection="下午";
+				}
+				return{
+					curDate,
+					curYear,
+					curMonth,
+					curMonthdays,
+					curDay,
+					curHour,
+					curSection
+				}
+			},
+			getDefaultDate(){
+				let value=this.value;
+				let reg=/-/g;
+				let defaultDate=value?new Date(value.split(" ")[0].replace(reg,"/")):new Date();
+				let defaultYear=defaultDate.getFullYear();
+				let defaultMonth=defaultDate.getMonth()+1;
+				let defaultDay=defaultDate.getDate();
+				let defaultDays=new Date(defaultYear,defaultMonth,0).getDate()*1;
+				return{
+					defaultDate,
+					defaultYear,
+					defaultMonth,
+					defaultDay,
+					defaultDays
+				}
+			},
+			getStartDate(){
+				let start=this.startYear;
+				let startDate="";
+				let reg=/-/g;
+				if(start){
+					startDate=new Date(start+"/01/01");
+				}else{
+					startDate=new Date("1970/01/01");
+				}
+				return startDate;
+			},
+			getEndDate(){
+				let end=this.endYear;
+				let reg=/-/g;
+				let endDate="";
+				if(end){
+					endDate=new Date(end+"/12/31");
+				}else{
+					endDate=new Date();
+				}
+				return endDate;
+			},
+			getDval(){
+				let value=this.value;
+				let dVal=null;
+				let aDate=new Date();
+				let year=this.formatNum(aDate.getFullYear());
+				let month=this.formatNum(aDate.getMonth()+1);
+				let day=this.formatNum(aDate.getDate());
+				let hour=aDate.getHours();
+				let section="上午";
+				if(hour>=12)section="下午";
+				if(value){
+					let flag=this.checkValue(value);
+					if(!flag){
+						dVal=[year,month,day,section]
+					}else{
+						let v=value.split(" ");
+						dVal=[...v[0].split("-"),v[1]];
+					}
+				}else{
+					dVal=[year,month,day,section]
+				}
+				return dVal;
+			},
+			initData(){
+				let startDate,endDate,startYear,endYear,startMonth,endMonth,startDay,endDay;
+				let years=[],months=[],days=[],sections=[];
+				let dVal=[],pickVal=[];
+				let value=this.value;
+				let reg=/-/g;
+				let range={};
+				let result="",full="",year,month,day,section,obj={};
+				let defaultDate=this.getDefaultDate();
+				let defaultYear=defaultDate.defaultYear;
+				let defaultMonth=defaultDate.defaultMonth;
+				let defaultDay=defaultDate.defaultDay;
+				let defaultDays=defaultDate.defaultDays;
+				let curFlag=this.current;
+				let disabledAfter=this.disabledAfter;
+				let curDate=this.getCurrenDate();
+				let curYear=curDate.curYear;
+				let curMonth=curDate.curMonth;
+				let curMonthdays=curDate.curMonthdays;
+				let curDay=curDate.curDay;
+				let curSection=curDate.curSection;
+				let dateData=[];
+				dVal=this.getDval();
+				startDate=this.getStartDate();
+				endDate=this.getEndDate();
+				startYear=startDate.getFullYear();
+				startMonth=startDate.getMonth();
+				startDay=startDate.getDate();
+				endYear=endDate.getFullYear();
+				endMonth=endDate.getMonth();
+				endDay=endDate.getDate();
+				dateData=this.getData(dVal);
+				years=dateData.years;
+				months=dateData.months;
+				days=dateData.days;
+				sections=dateData.sections;
+				pickVal=disabledAfter?[
+					dVal[0]&&years.indexOf(dVal[0])!=-1?years.indexOf(dVal[0]):0,
+					dVal[1]&&months.indexOf(dVal[1])!=-1?months.indexOf(dVal[1]):0,
+					dVal[2]&&days.indexOf(dVal[2])!=-1?days.indexOf(dVal[2]):0,
+					dVal[3]&&sections.indexOf(dVal[3])!=-1?sections.indexOf(dVal[3]):0
+				]:(curFlag?[
+					years.indexOf(curYear+''),
+					months.indexOf(this.formatNum(curMonth)),
+					days.indexOf(this.formatNum(curDay)),
+					sections.indexOf(curSection),
+				]:[
+					dVal[0]&&years.indexOf(dVal[0])!=-1?years.indexOf(dVal[0]):0,
+					dVal[1]&&months.indexOf(dVal[1])!=-1?months.indexOf(dVal[1]):0,
+					dVal[2]&&days.indexOf(dVal[2])!=-1?days.indexOf(dVal[2]):0,
+					dVal[3]&&sections.indexOf(dVal[3])!=-1?sections.indexOf(dVal[3]):0
+				]);
+				range={years,months,days,sections};
+				year=dVal[0]?dVal[0]:years[0];
+				month=dVal[1]?dVal[1]:months[0];
+				day=dVal[2]?dVal[2]:days[0];
+				section=dVal[3]?dVal[3]:sections[0];
+				result=full=`${year+'-'+month+'-'+day+' '+section}`;
+				obj={
+					year,
+					month,
+					day,
+					section
+				}
+				this.range=range;
+				this.checkObj=obj;
+				this.$nextTick(()=>{
+					this.pickVal=pickVal;
+				});
+				this.$emit("change",{
+					result:result,
+					value:full,
+					obj:obj
+				})
+			},
+			handlerChange(e){
+				let arr=[...e.detail.value];
+				let data=this.range;
+				let year="",month="",day="",section="";
+				let result="",full="",obj={};
+				let months=null,days=null,sections=null;
+				let disabledAfter=this.disabledAfter;
+				year=(arr[0]||arr[0]==0)?data.years[arr[0]]||data.years[data.years.length-1]:"";
+				month=(arr[1]||arr[1]==0)?data.months[arr[1]]||data.months[data.months.length-1]:"";
+				day=(arr[2]||arr[2]==0)?data.days[arr[2]]||data.days[data.days.length-1]:"";
+				section=(arr[3]||arr[3]==0)?data.sections[arr[3]]||data.sections[data.sections.length-1]:"";
+				result=full=`${year+'-'+month+'-'+day+' '+section}`;
+				let resetData=this.resetData(year,month,day);
+				if(this.disabledAfter){
+					months=resetData.months;
+					days=resetData.days;
+					sections=resetData.sections;
+				}else{
+					if(year%4==0||(month!=this.checkObj.month)){
+						days=resetData.days;
+					}
+				}
+				if(months)this.range.months=months;
+				if(days)this.range.days=days;
+				if(sections)this.range.sections=sections;
+				obj={
+					year,
+					month,
+					day,
+					section
+				}
+				this.checkObj=obj;
+				this.$emit("change",{
+					result:result,
+					value:full,
+					obj:obj
+				})
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	@import "./w-picker.css";
+</style>

+ 274 - 0
components/w-picker/linkage-picker.vue

@@ -0,0 +1,274 @@
+<template>
+	<view class="w-picker-view">
+		<picker-view class="d-picker-view" :indicator-style="itemHeight" :value="pickVal" @change="handlerChange">
+			<picker-view-column v-for="(group,gIndex) in range" :key="gIndex">
+				<view class="w-picker-item" v-for="(item,index) in group" :key="index">{{item[nodeKey]}}</view>
+			</picker-view-column>
+		</picker-view>
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				pickVal:[],
+				range:[],
+				checkObj:{}
+			};
+		},
+		props:{
+			itemHeight:{
+				type:String,
+				default:"44px"
+			},
+			value:{
+				type:[Array,String],
+				default:""
+			},
+			defaultType:{
+				type:String,
+				default:"label"
+			},
+			options:{
+				type:Array,
+				default(){
+					return []
+				}
+			},
+			defaultProps:{
+				type:Object,
+				default(){
+					return{
+						lable:"label",
+						value:"value",
+						children:"children"
+					}
+				}
+			},
+			level:{
+				//多级联动层级,表示几级联动
+				type:[Number,String],
+				default:2
+			}
+		},
+		computed:{
+			nodeKey(){
+				return this.defaultProps.label;
+			},
+			nodeVal(){
+				return this.defaultProps.value;
+			},
+			nodeChild(){
+				return this.defaultProps.children;
+			}
+		},
+		watch:{
+			value(val){
+				if(this.options.length!=0){
+					this.initData();
+				}
+			},
+			options(val){
+				this.initData();
+			}
+		},
+		created() {
+			if(this.options.length!=0){
+				this.initData();
+			}
+		},
+		methods:{
+			getData(){
+				//用来处理初始化数据
+				let options=this.options;
+				let col1={},col2={},col3={},col4={};
+				let arr1=options,arr2=[],arr3=[],arr4=[];
+				let col1Index=0,col2Index=0,col3Index=0,col4Index=0;
+				let a1="",a2="",a3="",a4="";
+				let dVal=[],obj={};
+				let value=this.value;
+				let data=[];
+				a1=value[0];
+				a2=value[1];
+				if(this.level>2){
+					a3=value[2];
+				}
+				if(this.level>3){
+					a4=value[3];
+				};
+				/*第1列*/
+				col1Index=arr1.findIndex((v)=>{
+					return v[this.defaultType]==a1
+				});
+				col1Index=value?(col1Index!=-1?col1Index:0):0;
+				col1=arr1[col1Index];
+				
+				/*第2列*/
+				arr2=arr1[col1Index][this.nodeChild];
+				col2Index=arr2.findIndex((v)=>{
+					return v[this.defaultType]==a2
+				});
+				col2Index=value?(col2Index!=-1?col2Index:0):0;
+				col2=arr2[col2Index];
+				
+				/*第3列*/
+				if(this.level>2){
+					arr3=arr2[col2Index][this.nodeChild];
+					col3Index=arr3.findIndex((v)=>{
+						return v[this.defaultType]==a3;
+					});
+					col3Index=value?(col3Index!=-1?col3Index:0):0;
+					col3=arr3[col3Index];
+				};
+				
+				
+				/*第4列*/
+				if(this.level>3){
+					arr4=arr3[col4Index][this.nodeChild];
+					col4Index=arr4.findIndex((v)=>{
+						return v[this.defaultType]==a4;
+					});
+					col4Index=value?(col4Index!=-1?col4Index:0):0;
+					col4=arr4[col4Index];
+				};
+				switch(this.level*1){
+					case 2:
+						dVal=[col1Index,col2Index];
+						obj={
+							col1,
+							col2
+						}
+						data=[arr1,arr2];
+						break;
+					case 3:
+						dVal=[col1Index,col2Index,col3Index];
+						obj={
+							col1,
+							col2,
+							col3
+						}
+						data=[arr1,arr2,arr3];
+						break;
+					case 4:
+						dVal=[col1Index,col2Index,col3Index,col4Index];
+						obj={
+							col1,
+							col2,
+							col3,
+							col4
+						}
+						data=[arr1,arr2,arr3,arr4];
+						break
+				}
+				return {
+					data,
+					dVal,
+					obj
+				}
+			},
+			initData(){
+				let dataData=this.getData();
+				let data=dataData.data;
+				let arr1=data[0];
+				let arr2=data[1];
+				let arr3=data[2]||[];
+				let arr4=data[3]||[];
+				let obj=dataData.obj;
+				let col1=obj.col1,col2=obj.col2,col3=obj.col3||{},col4=obj.col4||{};
+				let result="",value=[];
+				let range=[];
+				switch(this.level){
+					case 2:
+						value=[col1[this.nodeVal],col2[this.nodeVal]];
+						result=`${col1[this.nodeKey]+col2[this.nodeKey]}`;
+						range=[arr1,arr2];
+						break;
+					case 3:
+						value=[col1[this.nodeVal],col2[this.nodeVal],col3[this.nodeVal]];
+						result=`${col1[this.nodeKey]+col2[this.nodeKey]+col3[this.nodeKey]}`;
+						range=[arr1,arr2,arr3];
+						break;
+					case 4:
+						value=[col1[this.nodeVal],col2[this.nodeVal],col3[this.nodeVal],col4[this.nodeVal]];
+						result=`${col1[this.nodeKey]+col2[this.nodeKey]+col3[this.nodeKey]+col4[this.nodeKey]}`;
+						range=[arr1,arr2,arr3,arr4];
+						break;
+				}
+				this.range=range;
+				this.checkObj=obj;
+				this.$nextTick(()=>{
+					this.pickVal=dataData.dVal;
+				});
+				this.$emit("change",{
+					result:result,
+					value:value,
+					obj:obj
+				})
+			},
+			handlerChange(e){
+				let arr=[...e.detail.value];
+				let col1Index=arr[0],col2Index=arr[1],col3Index=arr[2]||0,col4Index=arr[3]||0;
+				let arr1=[],arr2=[],arr3=[],arr4=[];
+				let col1,col2,col3,col4,obj={};
+				let result="",value=[];
+				arr1=this.options;
+				arr2=(arr1[col1Index]&&arr1[col1Index][this.nodeChild])||arr1[arr1.length-1][this.nodeChild]||[];
+				col1=arr1[col1Index]||arr1[arr1.length-1]||{};
+				col2=arr2[col2Index]||arr2[arr2.length-1]||{};
+				if(this.level>2){
+					arr3=(arr2[col2Index]&&arr2[col2Index][this.nodeChild])||arr2[arr2.length-1][this.nodeChild];
+					col3=arr3[col3Index]||arr3[arr3.length-1]||{};
+				}
+				if(this.level>3){
+					arr4=(arr3[col3Index]&&arr3[col3Index][this.nodeChild])||arr3[arr3.length-1][this.nodeChild]||[];
+					col4=arr4[col4Index]||arr4[arr4.length-1]||{};
+				}
+				switch(this.level){
+					case 2:
+						obj={
+							col1,
+							col2
+						}
+						this.range=[arr1,arr2];
+						result=`${(col1[this.nodeKey]||'')+(col2[this.nodeKey]||'')}`;
+						value=[col1[this.nodeVal]||'',col2[this.nodeVal]||''];
+						break;
+					case 3:
+						obj={
+							col1,
+							col2,
+							col3
+						}
+						this.range=[arr1,arr2,arr3];
+						result=`${(col1[this.nodeKey]||'')+(col2[this.nodeKey]||'')+(col3[this.nodeKey]||'')}`;
+						value=[col1[this.nodeVal]||'',col2[this.nodeVal]||'',col3[this.nodeVal]||''];
+						break;
+					case 4:
+						obj={
+							col1,
+							col2,
+							col3,
+							col4
+						}
+						this.range=[arr1,arr2,arr3,arr4];
+						result=`${(col1[this.nodeKey]||'')+(col2[this.nodeKey]||'')+(col3[this.nodeKey]||'')+(col4[this.nodeKey]||'')}`;
+						value=[col1[this.nodeVal]||'',col2[this.nodeVal]||'',col3[this.nodeVal]||'',col4[this.nodeVal]||''];
+						break;
+				}
+				this.checkObj=obj;
+				this.pickVal=arr;
+				this.$emit("change",{
+					result:result,
+					value:value,
+					obj:obj
+				})
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	@import "./w-picker.css";	
+</style>
+

+ 344 - 0
components/w-picker/range-picker.vue

@@ -0,0 +1,344 @@
+<template>
+	<view class="w-picker-view">
+		<picker-view  class="d-picker-view" :indicator-style="itemHeight" :value="pickVal" @change="handlerChange">
+			<picker-view-column class="w-picker-flex2">
+				<view class="w-picker-item" v-for="(item,index) in range.fyears" :key="index">{{item}}年</view>
+			</picker-view-column>
+			<picker-view-column class="w-picker-flex2">
+				<view class="w-picker-item" v-for="(item,index) in range.fmonths" :key="index">{{item}}月</view>
+			</picker-view-column>
+			<picker-view-column class="w-picker-flex2">
+				<view class="w-picker-item" v-for="(item,index) in range.fdays" :key="index">{{item}}日</view>
+			</picker-view-column>
+			<picker-view-column class="w-picker-flex1">
+				<view class="w-picker-item">-</view>
+			</picker-view-column>
+			<picker-view-column class="w-picker-flex2">
+				<view class="w-picker-item" v-for="(item,index) in range.tyears" :key="index">{{item}}年</view>
+			</picker-view-column>
+			<picker-view-column class="w-picker-flex2">
+				<view class="w-picker-item" v-for="(item,index) in range.tmonths" :key="index">{{item}}月</view>
+			</picker-view-column>
+			<picker-view-column class="w-picker-flex2">
+				<view class="w-picker-item" v-for="(item,index) in range.tdays" :key="index">{{item}}日</view>
+			</picker-view-column>
+		</picker-view>
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				pickVal:[],
+				range:{},
+				checkObj:{}
+			};
+		},
+		props:{
+			itemHeight:{
+				type:String,
+				default:"44px"
+			},
+			value:{
+				type:[String,Array],
+				default(){
+					return []
+				}
+			},
+			current:{//是否默认选中当前日期
+				type:Boolean,
+				default:false
+			},
+			startYear:{
+				type:[String,Number],
+				default:1970
+			},
+			endYear:{
+				type:[String,Number],
+				default:new Date().getFullYear()
+			}
+		},
+		watch:{
+			value(val){
+				this.initData();
+			}
+		},
+		created() {
+			this.initData();
+		},
+		methods:{
+			formatNum(n){
+				return (Number(n)<10?'0'+Number(n):Number(n)+'');
+			},
+			checkValue(value){
+				let strReg=/^\d{4}-\d{2}-\d{2}$/,example="2020-04-03";
+				if(!strReg.test(value[0])||!strReg.test(value[1])){
+					console.log(new Error("请传入与mode匹配的value值,例["+example+","+example+"]"))
+				}
+				return strReg.test(value[0])&&strReg.test(value[1]);
+			},
+			resetToData(fmonth,fday,tyear,tmonth){
+				let range=this.range;
+				let tmonths=[],tdays=[];
+				let yearFlag=tyear!=range.tyears[0];
+				let monthFlag=tyear!=range.tyears[0]||tmonth!=range.tmonths[0];
+				let ttotal=new Date(tyear,tmonth,0).getDate();
+				for(let i=yearFlag?1:fmonth*1;i<=12;i++){
+					tmonths.push(this.formatNum(i))
+				}
+				for(let i=monthFlag?1:fday*1;i<=ttotal;i++){
+					tdays.push(this.formatNum(i))
+				}
+				return{
+					tmonths,
+					tdays
+				}
+			},
+			resetData(fyear,fmonth,fday,tyear,tmonth){
+				let fyears=[],fmonths=[],fdays=[],tyears=[],tmonths=[],tdays=[];
+				let startYear=this.startYear;
+				let endYear=this.endYear;
+				let ftotal=new Date(fyear,fmonth,0).getDate();
+				let ttotal=new Date(tyear,tmonth,0).getDate();
+				for(let i=startYear*1;i<=endYear;i++){
+					fyears.push(this.formatNum(i))
+				}
+				for(let i=1;i<=12;i++){
+					fmonths.push(this.formatNum(i))
+				}
+				for(let i=1;i<=ftotal;i++){
+					fdays.push(this.formatNum(i))
+				}
+				for(let i=fyear*1;i<=endYear;i++){
+					tyears.push(this.formatNum(i))
+				}
+				for(let i=fmonth*1;i<=12;i++){
+					tmonths.push(this.formatNum(i))
+				}
+				for(let i=fday*1;i<=ttotal;i++){
+					tdays.push(this.formatNum(i))
+				}
+				return {
+					fyears,
+					fmonths,
+					fdays,
+					tyears,
+					tmonths,
+					tdays
+				}
+			},
+			getData(dVal){
+				let start=this.startYear*1;
+				let end=this.endYear*1;
+				let value=dVal;
+				let flag=this.current;
+				let aToday=new Date();
+				let tYear,tMonth,tDay,tHours,tMinutes,tSeconds,pickVal=[];
+				let initstartDate=new Date(start.toString());
+				let endDate=new Date(end.toString());
+				if(start>end){
+					initstartDate=new Date(end.toString());
+					endDate=new Date(start.toString());
+				};
+				let startYear=initstartDate.getFullYear();
+				let startMonth=initstartDate.getMonth()+1;
+				let endYear=endDate.getFullYear();
+				let fyears=[],fmonths=[],fdays=[],tyears=[],tmonths=[],tdays=[],returnArr=[],startDVal=[],endDVal=[];
+				let curMonth=flag?value[1]*1:(startDVal[1]*1+1);
+				let curMonth1=flag?value[5][1]*1:(value[5]*1+1);
+				let totalDays=new Date(value[0],value[1],0).getDate();
+				let totalDays1=new Date(value[4],value[5],0).getDate();
+				for(let s=startYear;s<=endYear;s++){
+					fyears.push(this.formatNum(s));
+				};
+				for(let m=1;m<=12;m++){
+					fmonths.push(this.formatNum(m));
+				};
+				for(let d=1;d<=totalDays;d++){
+					fdays.push(this.formatNum(d));
+				};
+				for(let s=value[0]*1;s<=endYear;s++){
+					tyears.push(this.formatNum(s));
+				};
+				
+				if(value[4]*1>value[0]*1){
+					for(let m=1;m<=12;m++){
+						tmonths.push(this.formatNum(m));
+					};
+					for(let d=1;d<=totalDays1;d++){
+						tdays.push(this.formatNum(d));
+					};
+				}else{
+					for(let m=value[1]*1;m<=12;m++){
+						tmonths.push(this.formatNum(m));
+					};
+					for(let d=value[2]*1;d<=totalDays1;d++){
+						tdays.push(this.formatNum(d));
+					};
+				};
+				
+				pickVal=[
+					fyears.indexOf(value[0])==-1?0:fyears.indexOf(value[0]),
+					fmonths.indexOf(value[1])==-1?0:fmonths.indexOf(value[1]),
+					fdays.indexOf(value[2])==-1?0:fdays.indexOf(value[2]),
+					0,
+					tyears.indexOf(value[4])==-1?0:tyears.indexOf(value[4]),
+					tmonths.indexOf(value[5])==-1?0:tmonths.indexOf(value[5]),
+					tdays.indexOf(value[6])==-1?0:tdays.indexOf(value[6])
+				];
+				return {
+					fyears,
+					fmonths,
+					fdays,
+					tyears,
+					tmonths,
+					tdays,
+					pickVal
+				}
+			},
+			getDval(){
+				let value=this.value;
+				let fields=this.fields;
+				let dVal=null;
+				let aDate=new Date();
+				let fyear=this.formatNum(aDate.getFullYear());
+				let fmonth=this.formatNum(aDate.getMonth()+1);
+				let fday=this.formatNum(aDate.getDate());
+				let tyear=this.formatNum(aDate.getFullYear());
+				let tmonth=this.formatNum(aDate.getMonth()+1);
+				let tday=this.formatNum(aDate.getDate());
+				if(value&&value.length>0){
+					let flag=this.checkValue(value);
+					if(!flag){
+						dVal=[fyear,fmonth,fday,"-",tyear,tmonth,tday]
+					}else{
+						dVal=[...value[0].split("-"),"-",...value[1].split("-")];
+					}
+				}else{
+					dVal=[fyear,fmonth,fday,"-",tyear,tmonth,tday]
+				}
+				return dVal;
+			},
+			initData(){
+				let range=[],pickVal=[];
+				let result="",full="",obj={};
+				let dVal=this.getDval();
+				let dateData=this.getData(dVal);
+				let fyears=[],fmonths=[],fdays=[],tyears=[],tmonths=[],tdays=[];
+				let fyear,fmonth,fday,tyear,tmonth,tday;
+				pickVal=dateData.pickVal;
+				fyears=dateData.fyears;
+				fmonths=dateData.fmonths;
+				fdays=dateData.fdays;
+				tyears=dateData.tyears;
+				tmonths=dateData.tmonths;
+				tdays=dateData.tdays;
+				range={
+					fyears,
+					fmonths,
+					fdays,
+					tyears,
+					tmonths,
+					tdays,
+				}
+				fyear=range.fyears[pickVal[0]];
+				fmonth=range.fmonths[pickVal[1]];
+				fday=range.fdays[pickVal[2]];
+				tyear=range.tyears[pickVal[4]];
+				tmonth=range.tmonths[pickVal[5]];
+				tday=range.tdays[pickVal[6]];
+				obj={
+					fyear,
+					fmonth,
+					fday,
+					tyear,
+					tmonth,
+					tday
+				}
+				result=`${fyear+'-'+fmonth+'-'+fday+'至'+tyear+'-'+tmonth+'-'+tday}`;
+				this.range=range;
+				this.checkObj=obj;
+				this.$nextTick(()=>{
+					this.pickVal=pickVal;
+				});
+				this.$emit("change",{
+					result:result,
+					value:result.split("至"),
+					obj:obj
+				})
+			},
+			handlerChange(e){
+				let arr=[...e.detail.value];
+				let result="",full="",obj={};
+				let year="",month="",day="",hour="",minute="",second="",note=[],province,city,area;
+				let checkObj=this.checkObj;
+				let days=[],months=[],endYears=[],endMonths=[],endDays=[],startDays=[];
+				let mode=this.mode;
+				let col1,col2,col3,d,a,h,m;
+				let xDate=new Date().getTime();
+				let range=this.range;
+				let fyear=range.fyears[arr[0]]||range.fyears[range.fyears.length-1];
+				let fmonth=range.fmonths[arr[1]]||range.fmonths[range.fmonths.length-1];
+				let fday=range.fdays[arr[2]]||range.fdays[range.fdays.length-1];
+				let tyear=range.tyears[arr[4]]||range.tyears[range.tyears.length-1];
+				let tmonth=range.tmonths[arr[5]]||range.tmonths[range.tmonths.length-1];
+				let tday=range.tdays[arr[6]]||range.tdays[range.tdays.length-1];
+				let resetData=this.resetData(fyear,fmonth,fday,tyear,tmonth);
+				if(fyear!=checkObj.fyear||fmonth!=checkObj.fmonth||fday!=checkObj.fday){
+					arr[4]=0;
+					arr[5]=0;
+					arr[6]=0;
+					range.tyears=resetData.tyears;
+					range.tmonths=resetData.tmonths;
+					range.tdays=resetData.tdays;
+					tyear=range.tyears[0];
+					checkObj.tyears=range.tyears[0];
+					tmonth=range.tmonths[0];
+					checkObj.tmonths=range.tmonths[0];
+					tday=range.tdays[0];
+					checkObj.tdays=range.tdays[0];
+				}
+				if(fyear!=checkObj.fyear||fmonth!=checkObj.fmonth){
+					range.fdays=resetData.fdays;
+				};
+				if(tyear!=checkObj.tyear){
+					arr[5]=0;
+					arr[6]=0;
+					let toData=this.resetToData(fmonth,fday,tyear,tmonth);
+					range.tmonths=toData.tmonths;
+					range.tdays=toData.tdays;
+					tmonth=range.tmonths[0];
+					checkObj.tmonths=range.tmonths[0];
+					tday=range.tdays[0];
+					checkObj.tdays=range.tdays[0];
+				};
+				if(tmonth!=checkObj.tmonth){
+					arr[6]=0;
+					let toData=this.resetToData(fmonth,fday,tyear,tmonth);
+					range.tdays=toData.tdays;
+					tday=range.tdays[0];
+					checkObj.tdays=range.tdays[0];
+				};
+				result=`${fyear+'-'+fmonth+'-'+fday+'至'+tyear+'-'+tmonth+'-'+tday}`;
+				obj={
+					fyear,fmonth,fday,tyear,tmonth,tday
+				}
+				this.checkObj=obj;
+				this.$nextTick(()=>{
+					this.pickVal=arr;
+				})
+				this.$emit("change",{
+					result:result,
+					value:result.split("至"),
+					obj:obj
+				})
+				
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	@import "./w-picker.css";
+</style>

+ 183 - 0
components/w-picker/region-picker.vue

@@ -0,0 +1,183 @@
+<template>
+	<view class="w-picker-view">
+		<picker-view class="d-picker-view" :indicator-style="itemHeight" :value="pickVal" @change="handlerChange">
+			<picker-view-column>
+				<view class="w-picker-item" v-for="(item,index) in range.provinces" :key="index">{{item.label}}</view>
+			</picker-view-column>
+			<picker-view-column>
+				<view class="w-picker-item" v-for="(item,index) in range.citys" :key="index">{{item.label}}</view>
+			</picker-view-column>
+			<picker-view-column v-if="!hideArea">
+				<view class="w-picker-item" v-for="(item,index) in range.areas" :key="index">{{item.label}}</view>
+			</picker-view-column>
+		</picker-view>
+	</view>
+</template>
+
+<script>
+	import areaData from "./areadata/areadata.js"
+	export default {
+		data() {
+			return {
+				pickVal:[],
+				range:{
+					provinces:[],
+					citys:[],
+					areas:[]
+				},
+				checkObj:{}
+			};
+		},
+		props:{
+			itemHeight:{
+				type:String,
+				default:"44px"
+			},
+			value:{
+				type:[Array,String],
+				default:""
+			},
+			defaultType:{
+				type:String,
+				default:"label"
+			},
+			hideArea:{
+				type:Boolean,
+				default:false
+			}
+		},
+		watch:{
+			value(val){
+				this.initData();
+			}
+		},
+		created() {
+			this.initData();
+		},
+		methods:{
+			getData(){
+				//用来处理初始化数据
+				let provinces=areaData;
+				let dVal=[];
+				let value=this.value;
+				let a1=value[0];//默认值省
+				let a2=value[1];//默认值市
+				let a3=value[2];//默认值区、县
+				let province,city,area;
+				let provinceIndex=provinces.findIndex((v)=>{
+					return v[this.defaultType]==a1
+				});
+				provinceIndex=value?(provinceIndex!=-1?provinceIndex:0):0;
+				let citys=provinces[provinceIndex].children;
+				let cityIndex=citys.findIndex((v)=>{
+					return v[this.defaultType]==a2
+				});
+				cityIndex=value?(cityIndex!=-1?cityIndex:0):0;
+				let areas=citys[cityIndex].children;
+				let areaIndex=areas.findIndex((v)=>{
+					return v[this.defaultType]==a3;
+				});
+				areaIndex=value?(areaIndex!=-1?areaIndex:0):0;
+				dVal=this.hideArea?[provinceIndex,cityIndex]:[provinceIndex,cityIndex,areaIndex];
+				province=provinces[provinceIndex];
+				city=citys[cityIndex];
+				area=areas[areaIndex];
+				let obj=this.hideArea?{
+					province,
+					city
+				}:{
+					province,
+					city,
+					area
+				}
+				return this.hideArea?{
+					provinces,
+					citys,
+					dVal,
+					obj
+				}:{
+					provinces,
+					citys,
+					areas,
+					dVal,
+					obj
+				}
+			},
+			initData(){
+				let dataData=this.getData();
+				let provinces=dataData.provinces;
+				let citys=dataData.citys;
+				let areas=this.hideArea?[]:dataData.areas;
+				let obj=dataData.obj;
+				let province=obj.province,city=obj.city,area=this.hideArea?{}:obj.area;
+				let value=this.hideArea?[province.value,city.value]:[province.value,city.value,area.value];
+				let result=this.hideArea?`${province.label+city.label}`:`${province.label+city.label+area.label}`;
+				this.range=this.hideArea?{
+					provinces,
+					citys,
+				}:{
+					provinces,
+					citys,
+					areas
+				};
+				this.checkObj=obj;
+				this.$nextTick(()=>{
+					this.pickVal=dataData.dVal;
+				});
+				this.$emit("change",{
+					result:result,
+					value:value,
+					obj:obj
+				})
+			},
+			handlerChange(e){
+				let arr=[...e.detail.value];
+				let provinceIndex=arr[0],cityIndex=arr[1],areaIndex=this.hideArea?0:arr[2];
+				let provinces=areaData;
+				let citys=(provinces[provinceIndex]&&provinces[provinceIndex].children)||provinces[provinces.length-1].children||[];
+				let areas=this.hideArea?[]:((citys[cityIndex]&&citys[cityIndex].children)||citys[citys.length-1].children||[]);
+				let province=provinces[provinceIndex]||provinces[provinces.length-1],
+				city=citys[cityIndex]||[citys.length-1],
+				area=this.hideArea?{}:(areas[areaIndex]||[areas.length-1]);
+				let obj=this.hideArea?{
+					province,
+					city
+				}:{
+					province,
+					city,
+					area
+				}
+				if(this.checkObj.province.label!=province.label){
+					//当省更新的时候需要刷新市、区县的数据;
+					this.range.citys=citys;
+					if(!this.hideArea){
+						this.range.areas=areas;
+					}
+					
+				}
+				if(this.checkObj.city.label!=city.label){
+					//当市更新的时候需要刷新区县的数据;
+					if(!this.hideArea){
+						this.range.areas=areas;
+					}
+				}
+				this.checkObj=obj;
+				this.$nextTick(()=>{
+					this.pickVal=arr;
+				})
+				let result=this.hideArea?`${province.label+city.label}`:`${province.label+city.label+area.label}`;
+				let value=this.hideArea?[province.value,city.value]:[province.value,city.value,area.value];
+				this.$emit("change",{
+					result:result,
+					value:value,
+					obj:obj
+				})
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	@import "./w-picker.css";	
+</style>
+

+ 129 - 0
components/w-picker/selector-picker.vue

@@ -0,0 +1,129 @@
+<template>
+	<view class="w-picker-view">
+		<picker-view class="d-picker-view" :indicator-style="itemHeight" :value="pickVal" @change="handlerChange">
+			<picker-view-column>
+				<view class="w-picker-item" v-for="(item,index) in range" :key="index">{{item[nodeKey]}}</view>
+			</picker-view-column>
+		</picker-view>
+	</view>
+</template>
+
+<script>
+	export default {
+		props:{
+			itemHeight:{
+				type:String,
+				default:"44px"
+			},
+			options:{
+				type:[Array,Object],
+				default(){
+					return []
+				}
+			},
+			value:{
+				type:String,
+				default:""
+			},
+			defaultType:{
+				type:String,
+				default:"label"
+			},
+			defaultProps:{
+				type:Object,
+				default(){
+					return{
+						label:"label",
+						value:"value"
+					}
+				}
+			}
+		},
+		data() {
+			return {
+				pickVal:[]
+			};
+		},
+		computed:{
+			nodeKey(){
+				return this.defaultProps.label;
+			},
+			nodeValue(){
+				return this.defaultProps.value;
+			},
+			range(){
+				return this.options
+			}
+		},
+		watch:{
+			value(val){
+				if(this.options.length!=0){
+					this.initData();
+				}
+			},
+			options(val){
+				this.initData();
+			}
+		},
+		created() {
+			if(this.options.length!=0){
+				this.initData();
+			}
+		},
+		methods:{
+			initData(){
+				let dVal=this.value||"";
+				let data=this.range;
+				let pickVal=[0];
+				let cur=null;
+				let label="";
+				let value,idx;
+				if(this.defaultType==this.nodeValue){
+					value=data.find((v)=>v[this.nodeValue]==dVal);
+					idx=data.findIndex((v)=>v[this.nodeValue]==dVal);
+				}else{
+					value=data.find((v)=>v[this.nodeKey]==dVal);
+					idx=data.findIndex((v)=>v[this.nodeKey]==dVal);
+				}
+				pickVal=[idx!=-1?idx:0];
+				this.$nextTick(()=>{
+					this.pickVal=pickVal;
+				});
+				if(this.defaultType==this.nodeValue){
+					this.$emit("change",{
+						result:value?value[this.nodeKey]:data[0][this.nodeKey],
+						value:dVal||data[0][this.nodeKey],
+						obj:value?value:data[0]
+					})
+				}else{
+					this.$emit("change",{
+						result:dVal||data[0][this.nodeKey],
+						value:value?value[this.nodeValue]:data[0][this.nodeValue],
+						obj:value?value:data[0]
+					})
+				}
+				
+			},
+			handlerChange(e){
+				let arr=[...e.detail.value];
+				let pickVal=[arr[0]||0];
+				let data=this.range;
+				let cur=data[arr[0]];
+				let label="";
+				let value="";
+				this.$nextTick(()=>{
+					this.pickVal=pickVal;
+				});
+				this.$emit("change",{
+					result:cur[this.nodeKey],
+					value:cur[this.nodeValue],
+					obj:cur
+				})
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	@import "./w-picker.css";
+</style>

+ 250 - 0
components/w-picker/shortterm-picker.vue

@@ -0,0 +1,250 @@
+<template>
+	<view class="w-picker-view">
+		<picker-view class="d-picker-view" :indicator-style="itemHeight" :value="pickVal" @change="handlerChange">
+			<picker-view-column>
+				<view class="w-picker-item" v-for="(item,index) in range.dates" :key="index">{{item.label}}</view>
+			</picker-view-column>
+			<picker-view-column>
+				<view class="w-picker-item" v-for="(item,index) in range.hours" :key="index">{{item.label}}时</view>
+			</picker-view-column>
+			<picker-view-column>
+				<view class="w-picker-item" v-for="(item,index) in range.minutes" :key="index">{{item.label}}分</view>
+			</picker-view-column>
+		</picker-view>
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				pickVal:[],
+				range:{},
+				checkObj:{}
+			};
+		},
+		props:{
+			itemHeight:{
+				type:String,
+				default:"44px"
+			},
+			value:{
+				type:[String,Array,Number],
+				default:""
+			},
+			current:{//是否默认选中当前日期
+				type:Boolean,
+				default:false
+			},
+			expand:{
+				type:[Number,String],
+				default:30
+			}
+		},
+		watch:{
+			value(val){
+				this.initData();
+			}
+		},
+		created() {
+			this.initData();
+		},
+		methods:{
+			formatNum(n){
+				return (Number(n)<10?'0'+Number(n):Number(n)+'');
+			},
+			checkValue(value){
+				let strReg=/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}(:\d{2})?$/,example="2019-12-12 18:05:00或者2019-12-12 18:05";
+				if(!strReg.test(value)){
+					console.log(new Error("请传入与mode、fields匹配的value值,例value="+example+""))
+				}
+				return strReg.test(value);
+			},
+			resetData(year,month,day){
+				let curDate=this.getCurrenDate();
+				let curFlag=this.current;
+				let curYear=curDate.curYear;
+				let curMonth=curDate.curMonth;
+				let curDay=curDate.curDay;
+				let curHour=curDate.curHour;
+				let months=[],days=[],sections=[];
+				let disabledAfter=this.disabledAfter;
+				let monthsLen=disabledAfter?(year*1<curYear?12:curMonth):12;
+				let totalDays=new Date(year,month,0).getDate();//计算当月有几天;
+				for(let month=1;month<=monthsLen;month++){
+					months.push(this.formatNum(month));
+				};
+				for(let day=1;day<=daysLen;day++){
+					days.push(this.formatNum(day));
+				}
+				return{
+					months,
+					days,
+					sections
+				}
+			},
+			getData(dVal){
+				//用来处理初始化数据
+				let curFlag=this.current;
+				let disabledAfter=this.disabledAfter;
+				let dates=[],hours=[],minutes=[];
+				let curDate=new Date();
+				let curYear=curDate.getFullYear();
+				let curMonth=curDate.getMonth();
+				let curDay=curDate.getDate();
+				let aDate=new Date(curYear,curMonth,curDay);
+				for(let i=0;i<this.expand*1;i++){
+					aDate=new Date(curYear,curMonth,curDay+i);
+					let year=aDate.getFullYear();
+					let month=aDate.getMonth()+1;
+					let day=aDate.getDate();
+					let label=year+"-"+this.formatNum(month)+"-"+this.formatNum(day);
+					switch(i){
+						case 0:
+							label="今天";
+							break;
+						case 1:
+							label="明天";
+							break;
+						case 2:
+							label="后天";
+							break
+					}
+					dates.push({
+						label:label,
+						value:year+"-"+this.formatNum(month)+"-"+this.formatNum(day)
+					})
+				};
+				for(let i=0;i<24;i++){
+					hours.push({
+						label:this.formatNum(i),
+						value:this.formatNum(i)
+					})
+				}
+				for(let i=0;i<60;i++){
+					minutes.push({
+						label:this.formatNum(i),
+						value:this.formatNum(i)
+					})
+				}
+				return {
+					dates,
+					hours,
+					minutes
+				}
+			},
+			getDefaultDate(){
+				let value=this.value;
+				let reg=/-/g;
+				let defaultDate=value?new Date(value.replace(reg,"/")):new Date();
+				let defaultYear=defaultDate.getFullYear();
+				let defaultMonth=defaultDate.getMonth()+1;
+				let defaultDay=defaultDate.getDate();
+				let defaultDays=new Date(defaultYear,defaultMonth,0).getDate()*1;
+				return{
+					defaultDate,
+					defaultYear,
+					defaultMonth,
+					defaultDay,
+					defaultDays
+				}
+			},
+			getDval(){
+				let value=this.value;
+				let dVal=null;
+				let aDate=new Date();
+				let year=this.formatNum(aDate.getFullYear());
+				let month=this.formatNum(aDate.getMonth()+1);
+				let day=this.formatNum(aDate.getDate());
+				let date=this.formatNum(year)+"-"+this.formatNum(month)+"-"+this.formatNum(day);
+				let hour=aDate.getHours();
+				let minute=aDate.getMinutes();
+				if(value){
+					let flag=this.checkValue(value);
+					if(!flag){
+						dVal=[date,hour,minute]
+					}else{
+						let v=value.split(" ");
+						dVal=[v[0],...v[1].split(":")];
+					}
+				}else{
+					dVal=[date,hour,minute]
+				}
+				return dVal;
+			},
+			initData(){
+				let startDate,endDate,startYear,endYear,startMonth,endMonth,startDay,endDay;
+				let dates=[],hours=[],minutes=[];
+				let dVal=[],pickVal=[];
+				let value=this.value;
+				let reg=/-/g;
+				let range={};
+				let result="",full="",date,hour,minute,obj={};
+				let defaultDate=this.getDefaultDate();
+				let defaultYear=defaultDate.defaultYear;
+				let defaultMonth=defaultDate.defaultMonth;
+				let defaultDay=defaultDate.defaultDay;
+				let defaultDays=defaultDate.defaultDays;
+				let curFlag=this.current;
+				let disabledAfter=this.disabledAfter;
+				let dateData=[];
+				dVal=this.getDval();
+				dateData=this.getData(dVal);
+				dates=dateData.dates;
+				hours=dateData.hours;
+				minutes=dateData.minutes;
+				pickVal=[
+					dates.findIndex(n => n.value == dVal[0])!=-1?dates.findIndex(n => n.value == dVal[0]):0,
+					hours.findIndex(n => n.value == dVal[1])!=-1?hours.findIndex(n => n.value == dVal[1]):0,
+					minutes.findIndex(n => n.value == dVal[2])!=-1?minutes.findIndex(n => n.value == dVal[2]):0,
+				];
+				range={dates,hours,minutes};
+				date=dVal[0]?dVal[0]:dates[0].label;
+				hour=dVal[1]?dVal[1]:hours[0].label;
+				minute=dVal[2]?dVal[2]:minutes[0].label;
+				result=full=`${date+' '+hour+':'+minute}`;
+				obj={
+					date,
+					hour,
+					minute
+				}
+				this.range=range;
+				this.checkObj=obj;
+				this.$nextTick(()=>{
+					this.pickVal=pickVal;
+				});
+				this.$emit("change",{
+					result:result,
+					value:full,
+					obj:obj
+				})
+			},
+			handlerChange(e){
+				let arr=[...e.detail.value];
+				let data=this.range;
+				let date="",hour="",minute="";
+				let result="",full="",obj={};
+				let disabledAfter=this.disabledAfter;
+				date=(arr[0]||arr[0]==0)?data.dates[arr[0]]||data.dates[data.dates.length-1]:"";
+				hour=(arr[1]||arr[1]==0)?data.hours[arr[1]]||data.hours[data.hours.length-1]:"";
+				minute=(arr[2]||arr[2]==0)?data.minutes[arr[2]]||data.minutes[data.minutes.length-1]:"";
+				result=full=`${date.label+' '+hour.label+':'+minute.label+':00'}`;
+				obj={
+					date,
+					hour,
+					minute
+				}
+				this.checkObj=obj;
+				this.$emit("change",{
+					result:result,
+					value:full,
+					obj:obj
+				})
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	@import "./w-picker.css";
+</style>

+ 218 - 0
components/w-picker/time-picker.vue

@@ -0,0 +1,218 @@
+<template>
+	<view class="w-picker-view">
+		<picker-view class="d-picker-view" :indicator-style="itemHeight" :value="pickVal" @change="handlerChange">
+			<picker-view-column>
+				<view class="w-picker-item" v-for="(item,index) in range.hours" :key="index">{{item}}时</view>
+			</picker-view-column>
+			<picker-view-column>
+				<view class="w-picker-item" v-for="(item,index) in range.minutes" :key="index">{{item}}分</view>
+			</picker-view-column>
+			<picker-view-column v-if="second">
+				<view class="w-picker-item" v-for="(item,index) in range.seconds" :key="index">{{item}}秒</view>
+			</picker-view-column>
+		</picker-view>
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				pickVal:[],
+				range:{},
+				checkObj:{}
+			};
+		},
+		props:{
+			itemHeight:{
+				type:String,
+				default:"44px"
+			},
+			value:{
+				type:[String,Array,Number],
+				default:""
+			},
+			current:{//是否默认选中当前日期
+				type:Boolean,
+				default:false
+			},
+			second:{
+				type:Boolean,
+				default:true
+			}
+		},
+		watch:{
+			value(val){
+				this.initData();
+			}
+		},
+		created() {
+			this.initData();
+		},
+		methods:{
+			formatNum(n){
+				return (Number(n)<10?'0'+Number(n):Number(n)+'');
+			},
+			checkValue(value){
+				let strReg=/^\d{2}:\d{2}:\d{2}$/,example="18:00:05";
+				if(!strReg.test(value)){
+					console.log(new Error("请传入与mode、fields匹配的value值,例value="+example+""))
+				}
+				return strReg.test(value);
+			},
+			resetData(year,month,day,hour,minute){
+				let curDate=this.getCurrenDate();
+				let curFlag=this.current;
+				let curHour=curDate.curHour;
+				let curMinute=curDate.curMinute;
+				let curSecond=curDate.curSecond;
+				for(let hour=0;hour<24;hour++){
+					hours.push(this.formatNum(hour));
+				}
+				for(let minute=0;minute<60;minute++){
+					minutes.push(this.formatNum(minute));
+				}
+				for(let second=0;second<60;second++){
+					seconds.push(this.formatNum(second));
+				}
+				return{
+					hours,
+					minutes,
+					seconds
+				}
+			},
+			getData(curDate){
+				//用来处理初始化数据
+				let hours=[],minutes=[],seconds=[];
+				let curFlag=this.current;
+				let disabledAfter=this.disabledAfter;
+				let fields=this.fields;
+				let curHour=curDate.curHour;
+				let curMinute=curDate.curMinute;
+				let curSecond=curDate.curSecond;
+				for(let hour=0;hour<24;hour++){
+					hours.push(this.formatNum(hour));
+				}
+				for(let minute=0;minute<60;minute++){
+					minutes.push(this.formatNum(minute));
+				}
+				for(let second=0;second<60;second++){
+					seconds.push(this.formatNum(second));
+				}
+				return this.second?{
+					hours,
+					minutes,
+					seconds
+				}:{
+					hours,
+					minutes
+				}
+			},
+			getCurrenDate(){
+				let curDate=new Date();
+				let curHour=curDate.getHours();
+				let curMinute=curDate.getMinutes();
+				let curSecond=curDate.getSeconds();
+				return this.second?{
+					curHour,
+					curMinute,
+					curSecond
+				}:{
+					curHour,
+					curMinute,
+				}
+			},
+			getDval(){
+				let value=this.value;
+				let fields=this.fields;
+				let dVal=null;
+				let aDate=new Date();
+				let hour=this.formatNum(aDate.getHours());
+				let minute=this.formatNum(aDate.getMinutes());
+				let second=this.formatNum(aDate.getSeconds());
+				if(value){
+					let flag=this.checkValue(value);
+					if(!flag){
+						dVal=[hour,minute,second]
+					}else{
+						dVal=value?value.split(":"):[];
+					}
+				}else{
+					dVal=this.second?[hour,minute,second]:[hour,minute]
+				}
+				return dVal;
+			},
+			initData(){
+				let curDate=this.getCurrenDate();
+				let dateData=this.getData(curDate);
+				let pickVal=[],obj={},full="",result="",hour="",minute="",second="";
+				let dVal=this.getDval();
+				let curFlag=this.current;
+				let disabledAfter=this.disabledAfter;
+				let hours=dateData.hours;
+				let minutes=dateData.minutes;
+				let seconds=dateData.seconds;
+				let defaultArr=this.second?[
+					dVal[0]&&hours.indexOf(dVal[0])!=-1?hours.indexOf(dVal[0]):0,
+					dVal[1]&&minutes.indexOf(dVal[1])!=-1?minutes.indexOf(dVal[1]):0,
+					dVal[2]&&seconds.indexOf(dVal[2])!=-1?seconds.indexOf(dVal[2]):0
+				]:[
+					dVal[0]&&hours.indexOf(dVal[0])!=-1?hours.indexOf(dVal[0]):0,
+					dVal[1]&&minutes.indexOf(dVal[1])!=-1?minutes.indexOf(dVal[1]):0
+				];
+				pickVal=disabledAfter?defaultArr:(curFlag?(this.second?[
+					hours.indexOf(this.formatNum(curDate.curHour)),
+					minutes.indexOf(this.formatNum(curDate.curMinute)),
+					seconds.indexOf(this.formatNum(curDate.curSecond)),
+				]:[
+					hours.indexOf(this.formatNum(curDate.curHour)),
+					minutes.indexOf(this.formatNum(curDate.curMinute))
+				]):defaultArr);
+				this.range=dateData;
+				this.checkObj=obj;
+				hour=dVal[0]?dVal[0]:hours[0];
+				minute=dVal[1]?dVal[1]:minutes[0];
+				if(this.second)second=dVal[2]?dVal[0]:seconds[0];
+				result=this.second?`${hour+':'+minute+':'+second}`:`${hour+':'+minute}`;
+				full=this.second?`${hour+':'+minute+':'+second}`:`${hour+':'+minute+':00'}`;
+				this.$nextTick(()=>{
+					this.pickVal=pickVal;
+				});
+				this.$emit("change",{
+					result:result,
+					value:full,
+					obj:obj
+				})
+			},
+			handlerChange(e){
+				let arr=[...e.detail.value];
+				let data=this.range;
+				let hour="",minute="",second="",result="",full="",obj={};
+				hour=(arr[0]||arr[0]==0)?data.hours[arr[0]]||data.hours[data.hours.length-1]:"";
+				minute=(arr[1]||arr[1]==0)?data.minutes[arr[1]]||data.minutes[data.minutes.length-1]:"";
+				if(this.second)second=(arr[2]||arr[2]==0)?data.seconds[arr[2]]||data.seconds[data.seconds.length-1]:"";
+				obj=this.second?{
+					hour,
+					minute,
+					second
+				}:{
+					hour,
+					minute
+				};
+				this.checkObj=obj;
+				result=this.second?`${hour+':'+minute+':'+second}`:`${hour+':'+minute}`;
+				full=this.second?`${hour+':'+minute+':'+second}`:`${hour+':'+minute+':00'}`;
+				this.$emit("change",{
+					result:result,
+					value:full,
+					obj:obj
+				})
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	@import "./w-picker.css";	
+</style>
+

+ 26 - 0
components/w-picker/w-picker.css

@@ -0,0 +1,26 @@
+.w-picker-flex2{
+	flex:2;
+}
+.w-picker-flex1{
+	flex:1;
+}
+.w-picker-view {
+	width: 100%;
+	height: 476upx;
+	overflow: hidden;
+	background-color: rgba(255, 255, 255, 1);
+	z-index: 666;
+}
+.d-picker-view{
+	height: 100%;
+}
+
+.w-picker-item {
+  text-align: center;
+  width: 100%;
+  height: 88upx;
+  line-height: 88upx;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+  font-size: 30upx;
+}

+ 340 - 0
components/w-picker/w-picker.vue

@@ -0,0 +1,340 @@
+<template name="w-picker">
+	<view class="w-picker" :key="createKey" :data-key="createKey">
+		<view class="mask" :class="{'visible':visible}" @tap="onCancel" @touchmove.stop.prevent catchtouchmove="true"></view>
+		<view class="w-picker-cnt" :class="{'visible':visible}">
+			<view class="w-picker-header"  @touchmove.stop.prevent catchtouchmove="true">
+				<text @tap.stop.prevent="onCancel">取消</text>
+				<slot></slot>
+				<text :style="{'color':themeColor}" @tap.stop.prevent="pickerConfirm">确定</text>
+			</view>
+			<date-picker 
+				v-if="mode=='date'" 
+				class="w-picker-wrapper"
+				:startYear="startYear"
+				:endYear="endYear"
+				:value="value"
+				:fields="fields"
+				:item-height="itemHeight"
+				:current="current"
+				:disabled-after="disabledAfter"
+				@change="handlerChange"
+				@touchstart="touchStart" 
+				@touchend="touchEnd">
+			</date-picker>
+			
+			<range-picker
+				v-if="mode=='range'" 
+				class="w-picker-wrapper"
+				:startYear="startYear"
+				:endYear="endYear"
+				:value="value"
+				:item-height="itemHeight"
+				:current="current"
+				@change="handlerChange"
+				@touchstart="touchStart" 
+				@touchend="touchEnd">
+			</range-picker>
+			
+			<half-picker
+				v-if="mode=='half'" 
+				class="w-picker-wrapper"
+				:startYear="startYear"
+				:endYear="endYear"
+				:value="value"
+				:item-height="itemHeight"
+				:current="current"
+				:disabled-after="disabledAfter"
+				@change="handlerChange"
+				@touchstart="touchStart" 
+				@touchend="touchEnd">
+			</half-picker>
+			
+			<shortterm-picker
+				v-if="mode=='shortTerm'" 
+				class="w-picker-wrapper"
+				:startYear="startYear"
+				:endYear="endYear"
+				:value="value"
+				:item-height="itemHeight"
+				:current="current"
+				expand="60"
+				:disabled-after="disabledAfter"
+				@change="handlerChange"
+				@touchstart="touchStart" 
+				@touchend="touchEnd">
+			</shortterm-picker>
+			
+			<time-picker
+				v-if="mode=='time'"
+				class="w-picker-wrapper"
+				:value="value"
+				:item-height="itemHeight"
+				:current="current"
+				:disabled-after="disabledAfter"
+				:second="second"
+				@change="handlerChange"
+				@touchstart="touchStart" 
+				@touchend="touchEnd">
+			</time-picker>
+			
+			<selector-picker
+				v-if="mode=='selector'"
+				class="w-picker-wrapper"
+				:value="value"
+				:item-height="itemHeight"
+				:options="options"
+				:default-type="defaultType"
+				:default-props="defaultProps"
+				@change="handlerChange"
+				@touchstart="touchStart" 
+				@touchend="touchEnd">
+			</selector-picker>
+			
+			<region-picker
+				v-if="mode=='region'"
+				class="w-picker-wrapper"
+				:value="value"
+				:hide-area="hideArea"
+				:default-type="defaultType"
+				:item-height="itemHeight"
+				@change="handlerChange"
+				@touchstart="touchStart" 
+				@touchend="touchEnd">
+			</region-picker>
+			
+			<linkage-picker
+				v-if="mode=='linkage'"
+				class="w-picker-wrapper"
+				:value="value"
+				:options="options"
+				:level="level"
+				:default-type="defaultType"
+				:default-props="defaultProps"
+				:item-height="itemHeight"
+				@change="handlerChange"
+				@touchstart="touchStart" 
+				@touchend="touchEnd">
+			</linkage-picker>
+		</view>
+	</view>
+</template>
+
+<script>
+	import datePicker from "./date-picker.vue"
+	import rangePicker from "./range-picker.vue"
+	import halfPicker from "./half-picker.vue"
+	import shorttermPicker from "./shortterm-picker.vue"
+	import timePicker from "./time-picker.vue"
+	import selectorPicker from "./selector-picker.vue"
+	import regionPicker from "./region-picker.vue"
+	import linkagePicker from "./linkage-picker.vue"
+	export default {
+		name:"w-picker",
+		components:{
+			datePicker,
+			rangePicker,
+			halfPicker,
+			timePicker,
+			selectorPicker,
+			shorttermPicker,
+			regionPicker,
+			linkagePicker
+		},
+		props:{
+			mode:{
+				type:String,
+				default:"date"
+			},
+			value:{//默认值
+				type:[String,Array,Number],
+				default:""
+			},
+			current:{//是否默认显示当前时间,如果是,传的默认值将失效
+				type:Boolean,
+				default:false
+			},
+			themeColor:{//确认按钮主题颜色
+				type:String,
+				default:"#f5a200"
+			},
+			fields:{//日期颗粒度:year、month、day、hour、minute、second
+				type:String,
+				default:"date"
+			},
+			disabledAfter:{//是否禁用当前之后的日期
+				type:Boolean,
+				default:false
+			},
+			second:{//time-picker是否显示秒
+				type:Boolean,
+				default:true
+			},
+			options:{//selector,region数据源
+				type:[Array,Object],
+				default(){
+					return []
+				}
+			},
+			defaultProps:{//selector,linkagle字段转换配置
+				type:Object,
+				default(){
+					return{
+						label:"label",
+						value:"value",
+						children:"children"
+					}
+				}
+			},
+			defaultType:{
+				type:String,
+				default:"label"
+			},
+			hideArea:{//mode=region时,是否隐藏区县列
+				type:Boolean,
+				default:false
+			},
+			level:{
+				//多级联动层级,表示几级联动,区间2-4;
+				type:[Number,String],
+				default:2
+			},
+			timeout:{//是否开启点击延迟,当快速滚动 还没有滚动完毕点击关闭时得到的值是不准确的
+				type:Boolean,
+				default:false
+			},
+			expand:{//mode=shortterm 默认往后拓展天数
+				type:[Number,String],
+				default:30
+			},
+			startYear:{
+				type:[String,Number],
+				default:1970
+			},
+			endYear:{
+				type:[String,Number],
+				default:new Date().getFullYear()
+			},
+			visible:{
+				type:Boolean,
+				default:false
+			}
+		},
+		created() {
+			this.createKey=Math.random()*1000;
+		},
+		data() {
+			return {
+				itemHeight:`height: ${uni.upx2px(88)}px;`,
+				result:{},
+				confirmFlag:true
+			};
+		},
+		methods:{
+			touchStart(){
+				if(this.timeout){
+					this.confirmFlag=false;
+				}
+			},
+			touchEnd(){
+				if(this.timeout){
+					setTimeout(()=>{
+						this.confirmFlag=true;
+					},500)
+				}
+			},
+			handlerChange(res){
+				let _this=this;
+				this.result={...res};
+			},
+			show(){
+				this.$emit("update:visible",true);
+			},
+			hide(){
+				this.$emit("update:visible",false);
+			},
+			onCancel(res){
+				this.$emit("update:visible",false);
+				this.$emit("cancel");
+			},
+			pickerConfirm(){
+				if(!this.confirmFlag){
+					return;
+				};
+				this.$emit("confirm",this.result);
+				this.$emit("update:visible",false);
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	.w-picker-item {
+	  text-align: center;
+	  width: 100%;
+	  height: 88upx;
+	  line-height: 88upx;
+	  text-overflow: ellipsis;
+	  white-space: nowrap;
+	  font-size: 30upx;
+	}
+	.w-picker{
+		z-index: 888;
+		.mask {
+		  position: fixed;
+		  z-index: 1000;
+		  top: 0;
+		  right: 0;
+		  left: 0;
+		  bottom: 0;
+		  background: rgba(0, 0, 0, 0.6);
+		  visibility: hidden;
+		  opacity: 0;
+		  transition: all 0.3s ease;
+		}
+		.mask.visible{
+			visibility: visible;
+			opacity: 1;
+		}
+		.w-picker-cnt {
+		  position: fixed;
+		  bottom: 0;
+		  left: 0;
+		  width: 100%;
+		  transition: all 0.3s ease;
+		  transform: translateY(100%);
+		  z-index: 3000;
+		  background-color: #fff;
+		}
+		.w-picker-cnt.visible {
+		  transform: translateY(0);
+		}
+		.w-picker-header{
+		  display: flex;
+		  align-items: center;
+		  padding: 0 30upx;
+		  height: 88upx;
+		  background-color: #fff;
+		  position: relative;
+		  text-align: center;
+		  font-size: 32upx;
+		  justify-content: space-between;
+		  border-bottom: solid 1px #eee;
+		  .w-picker-btn{
+		  	font-size: 30upx;
+		  }
+		}
+		
+		.w-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);
+		}
+	}
+</style>

+ 35 - 1
pages.json

@@ -124,8 +124,42 @@
 		}, {
 			"path": "pages/common/success",
 			"style": {}
+		},{
+			"path": "pages/user/location",
+			"style": {},
+			"permission": {
+				"scope.userLocation": {
+				  "desc": "你的位置信息将用于小程序位置接口的效果展示" // 高速公路行驶持续后台定位
+				}
+			  }
+		},{
+			"path": "pages/addressbook/address-book",
+			"style": {}
+		},{
+			"path": "pages/addressbook/level-address-book",
+			"style": {}
+		},
+		{
+			"path": "pages/addressbook/member",
+			"style": {}
+		},{
+			"path": "pages/addressbook/address-detail",
+			"style": {}
+		},{
+			"path": "pages/annotation/annotationList",
+			"style": {
+				"app-plus" : {
+					"bounce" : "none" //删除此项: mescroll-body支持iOS回弹
+				}
+			}
+		},{
+			"path": "pages/annotation/annotationDetail",
+			"style": {}
+		},{
+			"path": "pages/common/helloWorld",
+			"style": {}
 		}
-
+		
 	],
 	"globalStyle": {
 		"mp-alipay": {

+ 251 - 0
pages/addressbook/address-book.vue

@@ -0,0 +1,251 @@
+<template>
+	
+	<view>
+		<cu-custom :bgColor="NavBarColor" :isBack="true" backRouterName="index">
+			<block slot="backText">返回</block>
+			<block slot="content">通讯录</block>
+		</cu-custom>
+		<view class="cu-bar bg-white search fixed" :style="[{top:CustomBar + 'px'}]">
+			<view class="search-form round">
+				<text class="cuIcon-search"></text>
+				<input type="text" v-model="keyword" placeholder="输入搜索的关键词" confirm-type="search" @confirm="searchUserByKey"></input>
+			</view>
+			<view class="action">
+				<button class="cu-btn bg-gradual-blue shadow-blur round" @tap="searchUserByKey">搜索</button>
+			</view>
+		</view>
+		<scroll-view scroll-y class="indexes margin-top-xl"  :scroll-into-view="'indexes-'+ listCurID" :style="[{top:'calc('+ CustomBar + 'px - 20px)',height:'calc(100vh - '+ CustomBar + 'px - 70px)'}]"
+		 :scroll-with-animation="true" :enable-back-to-top="true">
+			<block v-for="(item,index) in list" :key="index">
+				<view :class="'indexItem-' + item.name" :id="'indexes-' + item.name" :data-index="item.name">
+					<view class="padding">{{item.name}}</view>
+					<view class="cu-list menu-avatar no-padding" >
+						<view class="cu-item" v-for="(items,sub) in userList" :key="sub" v-if="items.szm==item.name" @tap="toAddressDetail(items)">
+						   <view class="cu-avatar round lg" v-if="items.avatar" :style="[{ backgroundImage:'url(' + items.avatar + ')' }]"></view>
+							<view class="cu-avatar round lg" v-else>{{item.name}}</view>
+							<view class="content">
+								<view class="text-grey">{{items.realname?items.realname:items.username}}</view>
+								<view class="text-gray text-sm">
+									{{items.orgCode}}
+								</view>
+							</view>
+						</view>
+					</view>
+				</view>
+			</block>
+		</scroll-view>
+		<view class="indexBar" :style="[{height:'calc(110vh - ' + CustomBar + 'px - 50px)'}]">
+			<view class="indexBar-box" @touchstart="tStart" @touchend="tEnd" @touchmove.stop="tMove">
+				<view class="indexBar-item" v-for="(item,index) in list" :key="index" :id="index" @touchstart="getCur" @touchend="setCur"> {{item.name}}</view>
+			</view>
+		</view>
+		<!--选择显示-->
+		<view v-show="!hidden" class="indexToast">
+			{{listCur}}
+		</view>
+	</view>
+</template>
+
+<script>
+	import vPinyin from '@/common/util/vue-py.js';
+	import {getFileAccessHttpUrl} from '@/api/api.js';
+	export default {
+		data() {
+			return {
+				StatusBar: this.StatusBar,
+				CustomBar: this.CustomBar,
+				hidden: true,
+				listCurID: '',
+				list: [],
+				userList: [],
+				listCur: '',
+				keyword: '',
+				queryUserByKeyWord:'/sys/user/appQueryUser'
+			};
+		},
+		onLoad() {
+			let list = [{}];
+			for (let i = 0; i < 26; i++) {
+				list[i] = {};
+				list[i].name = String.fromCharCode(65 + i);
+			}
+			this.list = list;
+			this.listCur = list[0];
+			this.loadInfo()
+		},
+		onReady() {
+			let that = this;
+			uni.createSelectorQuery().select('.indexBar-box').boundingClientRect(function(res) {
+				that.boxTop = res.top
+			}).exec();
+			uni.createSelectorQuery().select('.indexes').boundingClientRect(function(res) {
+				that.barTop = res.top
+			}).exec()
+		},
+		methods: {
+			backTap(){
+				this.$Router.replace({name:'index'})
+			},
+			toAddressDetail(item){
+				let parmas = {...item}
+				parmas.departName=parmas.orgCode
+				this.$Router.push({name: 'addressDetail',params:parmas})
+			},
+			searchUserByKey(){
+				this.loadInfo()
+			},
+			loadInfo(){
+				this.$http.get(this.queryUserByKeyWord,{params:{'keyword':this.keyword}}).then(res=>{
+			         if (res.data.success) {
+						console.log("res",res)
+					    let arr=res.data.result;
+						let szuArr=[];
+					    this.userList = arr.map(item => {
+					        let { id,realname,avatar,username,phone,email,post,orgCode} = item
+							let pinYin = username.toUpperCase();
+							if(realname){
+								//TODO 判断汉字的位置
+								if(/.*[\u4e00-\u9fa5]+.*$/.test(realname)){
+									pinYin=vPinyin.chineseToPinYin(realname);
+								}
+							}
+							if(avatar){
+								avatar=getFileAccessHttpUrl(avatar);
+							}
+					        let event = {
+					          id, realname ,avatar,username,phone,email,post,orgCode,
+							  szm:pinYin.substr(0,1)
+					        }
+							szuArr.push(event.szm)
+					        return event
+					      })
+						 
+						  this.list=this.list.filter(item=>szuArr.indexOf(item.name)!=-1)	
+					      //this.list.unshift({name:"#"})
+					}
+				}).catch(err => {
+					console.log(err);
+				});
+				
+			},
+			//获取文字信息
+			getCur(e) {
+				this.hidden = false;
+				this.listCur = this.list[e.target.id].name;
+			},
+			setCur(e) {
+				this.hidden = true;
+				this.listCur = this.listCur
+			},
+			//滑动选择Item
+			tMove(e) {
+				let y = e.touches[0].clientY,
+					offsettop = this.boxTop,
+					that = this;
+				//判断选择区域,只有在选择区才会生效
+				if (y > offsettop) {
+					let num = parseInt((y - offsettop) / 20);
+					this.listCur = that.list[num].name
+				};
+			},
+
+			//触发全部开始选择
+			tStart() {
+				this.hidden = false
+			},
+
+			//触发结束选择
+			tEnd() {
+				this.hidden = true;
+				this.listCurID = this.listCur
+			},
+			indexSelect(e) {
+				let that = this;
+				let barHeight = this.barHeight;
+				let list = this.list;
+				let scrollY = Math.ceil(list.length * e.detail.y / barHeight);
+				for (let i = 0; i < list.length; i++) {
+					if (scrollY < i + 1) {
+						that.listCur = list[i].name;
+						that.movableY = i * 20
+						return false
+					}
+				}
+			}
+		}
+	}
+</script>
+
+<style>
+	page {
+		padding-top: 100upx;
+	}
+
+	.indexes {
+		position: relative;
+	}
+
+	.indexBar {
+		position: fixed;
+		right: 0px;
+		bottom: 0px;
+		padding: 20upx 20upx 20upx 60upx;
+		display: flex;
+		align-items: center;
+	}
+
+	.indexBar .indexBar-box {
+		width: 40upx;
+		height: auto;
+		background: #fff;
+		display: flex;
+		flex-direction: column;
+		box-shadow: 0 0 20upx rgba(0, 0, 0, 0.1);
+		border-radius: 10upx;
+	}
+
+	.indexBar-item {
+		flex: 1;
+		width: 40upx;
+		height: 40upx;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		font-size: 24upx;
+		color: #888;
+	}
+
+	movable-view.indexBar-item {
+		width: 40upx;
+		height: 40upx;
+		z-index: 9;
+		position: relative;
+	}
+
+	movable-view.indexBar-item::before {
+		content: "";
+		display: block;
+		position: absolute;
+		left: 0;
+		top: 10upx;
+		height: 20upx;
+		width: 4upx;
+		background-color: #f37b1d;
+	}
+
+	.indexToast {
+		position: fixed;
+		top: 0;
+		right: 80upx;
+		bottom: 0;
+		background: rgba(0, 0, 0, 0.5);
+		width: 100upx;
+		height: 100upx;
+		border-radius: 10upx;
+		margin: auto;
+		color: #fff;
+		line-height: 100upx;
+		text-align: center;
+		font-size: 48upx;
+	}
+</style>

+ 122 - 0
pages/addressbook/address-detail.vue

@@ -0,0 +1,122 @@
+<template>
+	<view>
+		<scroll-view :scroll-y="modalName==null" class="page">
+			<cu-custom :bgColor="NavBarColor" :isBack="true">
+				<block slot="backText">返回</block>
+				<block slot="content">个人信息</block>
+			</cu-custom>
+			<view class="cu-list menu-avatar margin-top">
+				<view class="cu-item">
+					<view class="cu-avatar round lg" v-if="infoList.avatar"  :style="[{backgroundImage:'url('+ infoList.avatar +')'}]"></view>
+					<view class="cu-avatar round lg" v-else :style="[{backgroundImage:'url('+ avatar +')'}]"></view>
+					<view class="content">
+						<view class="text-grey text-lg">{{infoList.realname?infoList.realname:infoList.username}}<text class="cuIcon-peoplefill margin-left-sm" :class="infoList.sex=='2'?'text-pink':'text-blue'"></text></view>
+						<view class="text-gray text-sm flex">
+							<view class="text-cut">
+								{{infoList.post}}
+							</view>
+						</view> 
+					</view>
+				</view>	
+			</view>
+			
+			<view class="cu-list menu margin-top">
+				<view class="cu-item animation-slide-bottom" :style="[{animationDelay: '0.2s'}]">
+					<view class="content">
+						<text class="text-grey">手机</text>
+					</view>
+					<view class="action">
+						<text class="text-grey">{{infoList.phone?infoList.phone:phone}}</text>
+					</view>
+				</view>
+				<view class="cu-item animation-slide-bottom" :style="[{animationDelay: '0.3s'}]">
+					<view class="content">
+						<text class="text-grey">邮箱</text>
+					</view>
+					<view class="action">
+						<text class="text-grey">{{infoList.email?infoList.email:email}}</text>
+					</view>
+				</view>
+				<view class="cu-item animation-slide-bottom" :style="[{animationDelay: '0.4s'}]">
+					<view class="content">
+						<text class="text-grey">部门</text>
+					</view>
+					<view class="action">
+						<text class="text-grey">{{infoList.departName?infoList.departName:company}}</text>
+					</view>
+				</view>
+			</view>
+			
+		</scroll-view>
+	</view>
+</template>
+
+<script>
+	import {phone,email,company} from "@/common/util/constants"
+	export default {
+		data() {
+			return {
+				phone,
+				email,
+				company,
+				NavBarColor:this.NavBarColor,
+				avatar:'/static/login4.png',
+				modalName: null,
+				infoList:{
+					realname:'',
+					post:'',
+					avatar:'',
+					phone:null,
+					email:'',
+					department:'',
+					title:''
+				},
+			};
+		},
+		onLoad() {
+			 this.infoList = this.$Route.query;
+			 console.log("this.infoList>>>",this.infoList)
+		},
+		methods: {
+			backRoute() {
+				//通讯录个人信息页面返回问题
+				if(this.infoList.page){
+					this.$Router.push({name:this.infoList.page})
+				}else{
+					let parmas={
+						title:this.$Route.query.departName,
+						orgCode:this.$Route.query.orgCode
+					}
+					this.$Router.push({name: 'member',params:parmas})
+				}
+			},
+		}
+	}
+</script>
+
+<style>
+	.page {
+		height: 100Vh;
+		width: 100vw;
+	}
+
+	.page.show {
+		overflow: hidden;
+	}
+
+	.switch-sex::after {
+		content: "\e716";
+	}
+
+	.switch-sex::before {
+		content: "\e7a9";
+	}
+
+	.switch-music::after {
+		content: "\e66a";
+	}
+
+	.switch-music::before {
+		content: "\e6db";
+	}
+</style>

+ 207 - 0
pages/addressbook/level-address-book.vue

@@ -0,0 +1,207 @@
+<template>
+
+	<view>
+		<view class="cu-bar fixed" :style="style" :class="[NavBarColor]">
+			<view class="cuIcon-back margin-left" @tap="backRoute()"><text class="margin-left-sm">返回</text></view>
+			<view class="content" :style="[{top:StatusBar + 'px'}]">
+				通讯录
+			</view>
+		</view>
+		<!-- 展示标题 -->
+		<view class="bg-gray text-gray padding" :style="[{marginTop:CustomBar+'px'}]">
+			{{ levelTitle }}
+		</view>
+		<!-- 展示部门和用户 -->
+		<view class="cu-list menu sm-border">
+			<view class="cu-item" v-for="(item, index) in comList" :key="item.key" @tap="goMember(item)">
+				<image class="line2-icon" src="/static/folder.png"></image>
+				<view class="content margin-left-sm">
+					<view class="text-grey">{{item.title}}</view>
+				</view>
+			</view>
+			<view class="cu-item" v-for="(item,index) in childrenUserList" :key="index" @click="goMemberInfo(item)">
+				<view class="cu-avatar round lx" :style="[{backgroundImage:'url('+ item.avatar +')'}]"></view>
+				<view class="content margin-left-sm">
+					<view class="text-grey">{{item.realname}}</view>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+import { getFileAccessHttpUrl } from "../../api/api.js"
+export default {
+	name: "level-address-book",
+	data(){
+		return{
+			CustomBar:this.CustomBar,
+			StatusBar:this.StatusBar,
+			NavBarColor:this.NavBarColor,
+			value:"",
+			derpartment:'通讯录',
+			comList:[],
+			childrenUserList:[],
+			metaList:[],
+			depart2Url:'/sys/sysDepart/queryTreeList',
+			queryTreeByKeyWord:'/sys/sysDepart/queryTreeByKeyWord',
+			queryUserByDid:'/sys/user/appQueryByDepartId',
+			// departUrl:'/sys/user/userDepartList',
+			level:0,
+			titleArray:[],
+			parentId:''
+		}
+	},
+	computed:{
+		levelTitle(){
+			if(this.titleArray && this.titleArray.length>0){
+				return  this.titleArray.join(' > ')
+			}
+			console.log("this.titleArray",this.titleArray)
+			return '企业通讯录'
+		},
+		style() {
+			var StatusBar= this.StatusBar;
+			var CustomBar= this.CustomBar;
+			var style = `height:${CustomBar}px;padding-top:${StatusBar}px;`;
+			return style
+		}
+	},
+	onLoad() {
+		console.log("this.CustomBar",this.CustomBar)
+		this.loadList()
+	},
+	methods: {
+		backRoute() {
+			console.log("this.level",this.level)
+			console.log("this.parentId",this.parentId)
+			if(this.level==0){
+				// 返回首页
+				this.$Router.replaceAll({name: 'index'})
+			}else{
+				//正常列表的回退 只要找parentId的parent的data
+				this.getCyclePnode(this.metaList,'');
+				//还需要设置 title
+				this.titleArray.pop();
+				this.level--;
+			}
+		},
+		onRefresh(){
+			this.comList=[]
+			this.childrenUserList=[]
+			this.titleArray=[]
+			this.loadList()
+		},
+		loadList() {
+			console.log("loadList==>$Route",this.$Route)
+			if(this.$Route.query.level){
+				let params=this.$Route.query
+				this.level=params.level;
+				this.comList=params.comList;
+				this.metaList=params.metaList;
+				this.childrenUserList=params.childrenUserList;
+				this.derpartment=params.derpartment;
+				this.parentId=params.parentId;
+				this.titleArray=params.titleArray;
+				return;
+			}
+
+			this.$http.get(this.queryTreeByKeyWord).then(res => {
+				console.log("部门通讯树::", res)
+				if (res.data.success) {
+					console.log("部门::", res.data.result.departList)
+					if(res.data.result.departList && res.data.result.departList.length >0){
+						for (let item of res.data.result.departList) {
+							console.log("item::",item);
+							this.comList.push(item)
+							this.metaList.push(item)
+						}
+						this.derpartment = this.comList[0].title
+					}
+				}
+			}).catch(e => {
+				console.log("queryTreeByKeyWord 请求错误", e)
+			})
+		},
+		getUser(departId,title){
+			this.$http.get(this.queryUserByDid,{params:{departId:departId}}).then(res => {
+			  this.childrenUserList=res.data.result;
+			  for(let item of this.childrenUserList){
+				  item.departName=title
+				  this.avatarHandler(item)
+			   }
+			})
+		},
+		avatarHandler(item){
+			let avatar = item.avatar
+			if(avatar){
+				let url = getFileAccessHttpUrl(avatar);
+				item.avatar = url
+			}else{
+				if(item.sex=='2'){
+					item.avatar = 'https://static.jeecg.com/upload/test/avatar_girl_1595818025488.png';
+				}else{
+					item.avatar = 'https://static.jeecg.com/upload/test/avatar_boy_1595818012577.png';
+				}
+			}
+
+		},
+		goMember(item){
+			let params = {...item}
+			if (params.children && params.children.length>0){
+				this.level++;
+				this.comList=[];
+				this.childrenUserList=[];
+				for (let item of params.children) {
+				  this.comList.push(item)
+				}
+				this.getUser(params.id,params.title)
+				this.titleArray.push(params.title);
+				this.parentId = params.id;
+				this.derpartment = params.title;
+			}else{
+				console.log("params==>toMember",params)
+				params.level=this.level;
+				params.comList=this.comList;
+				params.metaList=this.metaList;
+				params.childrenUserList=this.childrenUserList;
+				params.derpartment=this.derpartment;
+				params.parentId=this.parentId;
+				params.titleArray=this.titleArray;
+				this.$Router.push({name: 'member',params:params})
+			}
+		},
+		getCyclePnode(arr, pid){
+			for(let item of arr){
+				if(item.id == this.parentId){
+					this.comList = arr;
+					this.childrenUserList=[];
+					this.derpartment = this.comList[0].title
+					this.parentId = pid;
+					this.getUser(this.parentId,item.title)
+					break;
+				}
+				if(item.children && item.children.length>0){
+					this.getCyclePnode(item.children, item.id)
+				}
+
+			}
+		},
+		goMemberInfo: function (item){
+			console.log("item",item)
+			
+			let parmas = {...item}
+			console.log("parmas",parmas)
+			parmas.page='levelAddressBook'
+			this.$Router.push({name: 'addressDetail',params:parmas})
+		},
+	}
+}
+</script>
+
+<style>
+	  .line2-icon {
+		width: 25px;
+		height: 20px;
+	  }
+</style>

+ 128 - 0
pages/addressbook/member.vue

@@ -0,0 +1,128 @@
+<template>
+    <view>
+        <view class="cu-bar fixed" :class="[NavBarColor]" :style="style" >
+        	<view class="cuIcon-back margin-left" @tap="backRoute()"><text class="margin-left-sm">返回</text></view>
+        	<view class="content" :style="[{top:StatusBar + 'px'}]">
+        		{{ memberTitle }}
+        	</view>
+        </view>
+		<!-- 展示部门和用户 -->
+		<view class="cu-list menu-avatar sm-border" :style="[{marginTop:'calc('+CustomBar+ 'px + 10px)'}]">
+		    <view class="cu-item" v-for="(item, index) in memberList" :key="index" @click="goMemberInfo(item)">
+				<view class="cu-avatar round lg" :style="[{backgroundImage:'url('+ item.avatar +')'}]"></view>
+				<view class="content">
+					<view class="text-grey">{{item.realname}}</view>
+					<view class="text-grey">{{item.post}}</view>
+				</view>
+			</view>
+			<view class="text-center text-lg"><text>共{{ memberTotal}}人</text></view>
+		</view>
+    </view>
+</template>
+
+<script>
+	import { getFileAccessHttpUrl } from "../../api/api.js"
+    export default {
+        name: "member",
+        data(){
+            return{
+				CustomBar:this.CustomBar,
+				StatusBar:this.StatusBar,
+				NavBarColor:this.NavBarColor,
+				departUrl:'/sys/sysDepart/queryTreeList',
+                addressSourceUrl:'/sys/user/queryByOrgCodeForAddressList',
+                queryByOrgCodeKeyword:'/sys/user/queryByOrgCodeKeyword',
+                positionUrl:'/sys/position/list',
+                value:'',
+                memberTitle:'',
+                memberList:[],
+                userId:'',
+                orgCode:'',
+                ids:{},
+                memberTotal:0,
+            }
+        },
+        onLoad(){
+			console.log("this.$Router", this.$Route);
+            this.memberTitle = this.$Route.query.title
+            this.orgCode = this.$Route.query.orgCode
+            this.loadList()
+		},
+		computed:{
+			style() {
+				var StatusBar= this.StatusBar;
+				var CustomBar= this.CustomBar;
+				var style = `height:${CustomBar}px;padding-top:${StatusBar}px;`;
+				return style
+			}
+		},
+        methods: {
+            backRoute() {
+                this.$Router.push({name: 'levelAddressBook',params:this.$Route.query})
+            },
+            goMemberInfo(item){
+                let parmas = {...item}
+                parmas.orgCode= this.orgCode
+                this.$Router.push({name: 'addressDetail',params:parmas})
+            },
+            loadList() {
+                let param = {
+                    orgCode:this.orgCode,
+                    realname:this.value
+                }
+                this.$http.get(this.addressSourceUrl,{params:param}).then(res => {
+                    console.log("用户2", res)
+                    if (res.data.success) {
+                        let memberArr = res.data.result.records;
+                        //memberArr = memberArr.filter(item => item.departName == this.memberTitle)
+                        for (let item of memberArr) {
+							this.avatarHandler(item);
+                            this.memberList.push(item)
+                            // this.userId = item.userId
+                            this.memberTotal = memberArr.length
+                            if (this.memberTotal == undefined){
+                                this.memberTotal = 0
+                            }
+                        }
+                    }
+                }).catch(e => {
+                    console.log("请求错误", e)
+                })
+
+                this.$http.get(this.positionUrl).then(res=> {
+                   console.log("用户1",res)
+                    if (res.data.success) {
+                        let postArr = res.data.result.records
+                        for (let item of postArr ){
+                            for (let it of this.memberList){
+                                if (it.post == item.code){
+                                    it.post = item.name
+                                }
+                            }
+                        }
+                    }
+                }).catch(e=>{
+                    console.log("请求错误",e)
+                })
+            },
+			avatarHandler(item){
+				let avatar = item.avatar
+				if(avatar){
+					let url = getFileAccessHttpUrl(avatar);
+					item.avatar = url
+				}else{
+					if(item.sex=='2'){
+						item.avatar = 'static/avatar_girl.png';
+					}else{
+						item.avatar = 'static/avatar_boy.png';
+					}
+				}
+			
+			},
+        }
+    }
+</script>
+
+<style scoped>
+	
+</style>

+ 93 - 0
pages/annotation/annotationDetail.vue

@@ -0,0 +1,93 @@
+<template>
+	<view class="bg-white" style="height: 100vh;">
+		<cu-custom :bgColor="NavBarColor" :isBack="true" backFlag="navigate">
+			<block slot="backText">返回</block>
+			<block slot="content">详情</block>
+		</cu-custom>
+		<view>
+			<view class="title">
+				<view class="padding" style="font-family: '宋体';">
+					<text class="text-black title text-bold text-xl">{{annotation.titile}} </text>
+				</view>
+			</view>
+			<view class=" text-df padding-left " style="color: #999;">
+				<text class="title padding-right-xl" style="color: #999;">
+					{{annotation.sender||''}}
+				</text>	
+				<text  class="" v-html="annotation.sendTime">
+				</text>
+			</view>
+			<view class="desc" style="font-family: '仿宋';font-size: 18px;">
+				<view class="text-content padding" v-html="annotation.msgContent">
+					<!-- <annotation-block :content="annotation.msgContent"/> -->
+				</view>
+			</view>
+			<view class="text-gray padding-left padding-bottom text-gray">
+				<text class="cuIcon-attentionfill margin-lr-xs" @click="numberPlus"></text> 10
+				<text class="cuIcon-appreciatefill padding-left margin-lr-xs" @click="numberPlus"></text> 20
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+
+
+export default {
+	data(){
+		return{
+			NavBarColor:this.NavBarColor,
+			annotation:{
+				id:"",
+				titile:"",
+				startTime:"",
+				sender:"",
+				msgContent:"",
+
+			},
+			goodNumber:null,
+			flg:true,
+		}
+	},
+	// onLoad:function(){
+	// 	console.log("this.$Route.query",this.$Route);
+	// 	let query = this.$Route.query
+	// 	if(query){
+	// 		this.annotation = query;
+	// 		}
+	// },
+	onLoad: function (option) {
+	    const annItem = JSON.parse(decodeURIComponent(option.item));
+		console.log("ann",annItem)
+		this.annotation = annItem 
+		this.readOk();
+	},
+	created(){
+		console.log("created")
+		//this.readOk();
+	},
+	methods:{
+		readOk(){
+			console.log("readOk")
+			let param = {anntId:this.annotation.anntId}
+			this.$http.put('/sys/sysAnnouncementSend/editByAnntIdAndUserId',param);
+		},
+
+		numberPlus(){
+			if (this.flg){
+				this.goodNumber++
+				this.flg = false
+			} else {
+				this.goodNumber--
+				if (this.goodNumber == 0){
+					this.goodNumber = null
+				}
+				this.flg = true
+			}
+		}
+	}
+}
+</script>
+
+<style>
+</style>

+ 308 - 0
pages/annotation/annotationList.vue

@@ -0,0 +1,308 @@
+<template>
+	<view class="bg-white" >
+		<cu-custom :bgColor="NavBarColor" :isBack="true" backRouterName="index">
+			<block slot="backText">返回</block>
+			<block slot="content">公告消息</block>
+		</cu-custom>
+		<view class="container">
+			<view class="solid-bottom">
+				<scroll-view scroll-x class="bg-white nav text-center ">
+					<view class="flex text-center justify-around">
+						<view class="cu-item" :class="item.value==TabCur?'text-blue cur':''" v-for="(item,index) in tabs" :key="index" @tap="tabSelect" :data-id="item.value">
+							{{item.title}}
+						</view>
+					</view>
+				</scroll-view>
+			</view>
+			
+			 <mescroll-uni ref="mescrollRef" @init="mescrollInit" :top="top"  @down="downCallback"  @up="upCallback">
+				<!-- 数据列表 -->
+				<view class="al-list cu-list">
+					<view class="message_text bg-white cu-item flex justify-around align-center solid-bottom margin-bottom-sm margin-top-sm" style="width: 100vw;" v-for="(item,index) in msgList" :key="index" @tap="goAnnotationDetail(item)" :class="modalName=='move-box-'+ index?'move-cur':''"
+					  @touchstart="ListTouchStart" @touchmove="ListTouchMove" @touchend="ListTouchEnd" :data-target="'move-box-' + index">
+						<view v-if="isEmail(item)" class="padding-left">
+							<view class="cu-avatar radius cuIcon-mail bg-orange" ></view>
+						</view>
+						<view v-if="item.msgCategory == '1'&&!isEmail(item)" class="padding-left">
+							<view class="cu-avatar radius cuIcon-notification bg-orange" ></view>
+						</view>
+						<view v-if="item.msgCategory == '2'&&!isEmail(item)" class="padding-left">
+							<view class="cu-avatar radius cuIcon-notice bg-orange" ></view>
+						</view>
+						<view class="titlePad content" style="height: 4em;">
+							<view class="flex justify-start text-cut text-lg" style="width:26em;color: #333;font-family: '黑体';">
+								<!-- <view v-if="item.readFlag == '0'"  class="cu-tag light bg-blue">未读</view> -->
+								<view v-if="item.readFlag == '0'">
+									<text class="cuIcon-title text-red padding-left-sm" style="margin-right: -0.8em;"></text>
+								</view>
+								<view class="padding-left">
+									<text class="text-black" v-if="isEmail(item)&&item.emailTitle">{{titleFilter(item.emailTitle,12)}} </text>
+									<text class="text-black" v-else>{{titleFilter(item.titile,12)}} </text>
+								</view>
+							</view>
+							<view class="flex justify-between margin-top-xs" style="font-family: '黑体';color: #999;">
+								<view v-if="isEmail(item)" class="text-content text-cut" style="padding-left: .8rem;width:18em;"
+								 v-html="item.msgContent" @click="goEmailDetailPage(item);">
+                                </view>
+								<view v-else-if="!isEmail(item)" style="padding-left: .8rem;">
+									<view class="text-content text-cut" v-if="item.msgAbstract && item.msgAbstract.length > 0" style="width:18em;"
+									 v-html="item.msgAbstract">
+									</view>
+									<view v-else>
+										无摘要
+									</view>
+								</view>
+							</view>
+			
+						</view>
+						<view class="action text-sm" style="color: #aaa;margin-top: -2em;margin-left: -13em;width: 10em;">
+							<text >{{formatDate(item.sendTime,10)}}</text>
+						</view>
+						<view class="move">
+							<view class="bg-red" style="margin-right: 3em;margin-left: 2px;" @tap.stop="deleteAnnotation(item)">删除</view>
+						</view>
+					</view>
+				</view>
+			</mescroll-uni>
+		</view>
+
+	</view>
+</template>
+
+<script>
+	const tabs = [{title:'通知公告',value:0}, {title:'系统消息',value:1}];
+	import MescrollMixin from "@/components/mescroll-uni/mescroll-mixins.js";
+	
+	
+	export default {
+	    mixins: [MescrollMixin], // 使用mixin
+		data() {
+			return {
+				tabs,
+				TabCur: 0,
+				scrollLeft: 0,
+				NavBarColor:this.NavBarColor,
+				upOption:{
+					page: {
+						num: 0, // 当前页码,默认0,回调之前会加1,即callback(page)会从1开始
+						size: 10 // 每页数据的数量
+					},
+					noMoreSize: 4, //如果列表已无数据,可设置列表的总数量要大于半页才显示无更多数据;避免列表数据过少(比如只有一条数据),显示无更多数据会不好看; 默认5
+					empty:{
+						tip: '~ 暂无数据 ~', // 提示
+						
+					},
+					loading:'',
+					text:'全部',
+					isShowNoMore:false,
+					textNoMore:'我是有底线的 >_<'
+				},
+				msgList: [], //列表数据
+			    read: "all",
+				announcement1:[],
+				msg1Count:"",
+				msg1Title:"",
+				announcement2:[],
+				msg2Count:"",
+				msg2Title:"",
+				url:"/sys/sysAnnouncementSend/getMyAnnouncementSend",
+				delUrl:'/sys/sysAnnouncementSend/delete',
+				listTouchStart: 0,
+			    modalName: null,
+			    listTouchDirection: null,
+			}
+		},
+		onShow() {
+			if(this.mescroll){
+				this.mescroll.resetUpScroll()
+			}
+		},
+		computed:{
+			top() {
+				return this.CustomBar * 2 + 95
+			},
+			style() {
+				var StatusBar= this.StatusBar;
+				var CustomBar= this.CustomBar;
+				var style = `height:${CustomBar}px;padding-top:${StatusBar}px;`;
+				return style
+			},
+		},
+		methods: {
+			// unique(arr) {
+			//     var obj = {};
+			//     return arr.filter(function(item, index, arr){
+			//         return obj.hasOwnProperty(typeof item + item) ? false : (obj[typeof item + item] = true)
+			//     })
+			// },
+			upCallback(page) {
+				//联网加载数据
+				console.log("tabindex",this.TabCur )
+				let keyword = this.TabCur
+				if(keyword == 0){
+					this.$http.get(this.url,{params:{pageNo: page.num, pageSize:page.size,msgCategory: '1'}}).then(res=>{
+						//联网成功的回调,隐藏下拉刷新和上拉加载的状态;
+						
+						this.announcement1 = res.data.result.records
+						this.mescroll.endSuccess(this.announcement1.length);
+						//console.log("url", res)
+						//设置列表数据
+						  if (res.data.success) {
+							 console.log("res",res.data)
+							 this.msg1Count = res.data.result.total
+							 this.msg1Title = "通知(" + res.data.result.total + ")";
+							 for(let annItem of this.announcement1){
+									 this.msgList.push(annItem)							
+								}
+						 }
+						if(page.num == 1){
+							this.msgList = []; //如果是第一页需手动制空列表
+							this.msgList = this.msgList.concat(this.announcement1); //追加新数据
+						}
+						
+					}).catch(()=>{
+						//联网失败, 结束加载
+						this.mescroll.endErr();
+					})
+				}
+				if(keyword == 1){
+					this.$http.get(this.url,{params:{pageNo: page.num,pageSize: page.size,msgCategory: '2'}}).then(res=>{
+						//联网成功的回调,隐藏下拉刷新和上拉加载的状态;
+						this.announcement2 = res.data.result.records
+						this.mescroll.endSuccess(this.announcement2.length,this.msgCount);
+						
+						//设置列表数据
+						  if (res.data.success) {
+							  console.log("res sys",res.data)
+							 this.msg2Count = res.data.result.total
+							 this.msg2Title = "通知(" + res.data.result.total + ")";
+							 // this.announcement2.filter((item,index) => {
+							 // // console.log("item",item)
+							 //  if(item.anntId == this.announcement2[index+1].anntId){
+								//   this.announcement2.splice(item,1)
+								  for(let item of this.announcement2){
+									  this.msgList.push(item)							
+								  }
+							//   }
+							// })
+						 }
+						if(page.num == 1){
+							this.msgList = []; //如果是第一页需手动制空列表
+							this.msgList = this.msgList.concat(this.announcement2); //追加新数据
+						}
+					}).catch(()=>{
+						//联网失败, 结束加载
+						this.mescroll.endErr();
+					})
+				}
+				
+			},
+		
+			tabSelect(e) {
+				this.TabCur = e.currentTarget.dataset.id;
+				this.scrollLeft = (e.currentTarget.dataset.id - 1) * 60;
+				this.msgList = []// 先置空列表,显示加载进度
+				this.mescroll.resetUpScroll() // 再刷新列表数据
+			},
+			goAnnotationDetail(item){
+				//item.readFlag = '1'
+				this.mescroll.resetUpScroll() 
+			    if(item.openType=="component" && item.openPage.indexOf('email')>= 0){
+					this.goEmailDetailPage(item)
+				}else{
+					// console.log("detail",encodeURIComponent(JSON.stringify(item)))
+					uni.navigateTo({
+						url: '/pages/annotation/annotationDetail?item='+ encodeURIComponent(JSON.stringify(item))
+					});
+				}
+			},
+			isEmail(item){
+				if(item.openType=="component" &&item.openPage.indexOf('email')>=0){
+				  return true;
+				}else{
+				  return false;
+				}
+		    },
+			goEmailDetailPage(item){
+				console.log("go",item.anntId)
+				console.log("item",item)
+				 if(item.readFlag == '0'){
+					//item.readFlag = '1'
+					this.mescroll.resetUpScroll() 
+					let readUrl = '/sys/sysAnnouncementSend/editByAnntIdAndUserId';
+					this.$http.put(readUrl,{anntId:item.anntId})					
+				}
+				// console.log("goe",item.busId)
+				
+				uni.navigateTo({
+					url: '/pages/mail/mailDetail?id='+item.busId
+				});
+				
+			},
+			ListTouchStart(e) {
+				this.listTouchStart = e.touches[0].pageX
+			},
+			
+			// ListTouch计算方向
+			ListTouchMove(e) {
+				this.listTouchDirection = e.touches[0].pageX - this.listTouchStart > 0 ? 'right' : 'left'
+			},
+			
+			// ListTouch计算滚动
+			ListTouchEnd(e) {
+				if (this.listTouchDirection == 'left') {
+					this.modalName = e.currentTarget.dataset.target
+				} else {
+					this.modalName = null
+				}
+				this.listTouchDirection = null
+			},
+			deleteAnnotation(item) {
+				this.$http.delete(this.delUrl+'?id='+item.id).then(res => {
+					console.log("结果数据9", res)
+					if (res.data.success) {
+						this.mescroll.resetUpScroll() 
+					}
+				}).catch(e => {
+					console.log("al delUrl请求错误2", e)
+					this.mescroll.endErr();
+				})
+			},
+			formatDate(text,len){
+				if(!text || text.length==0){
+					return ''
+				}
+				if(text.length<len){
+					return text;
+				}
+				return text.substr(0,len)
+			},
+			titleFilter(text,len){
+				if(!text || text.length==0){
+					return ''
+				}
+				if(text.length<len){
+					return text;
+				}
+				return text.substr(0,len)+"..."
+			}
+			
+		}
+	}
+</script>
+
+<style scoped>
+	.e-btn{margin-bottom: 1rem;}
+
+	.titlePad{margin-top:0.6rem;}
+	.cu-list>.move-cur{
+		transform: translateX(-150rpx);
+		
+	}
+	.nav .cu-item.cur {
+		position: flex;
+		z-index: 9;
+		border-bottom: 4upx solid;
+	}
+
+</style>

+ 59 - 0
pages/common/helloWorld.vue

@@ -0,0 +1,59 @@
+<template>
+	<view>
+		<scroll-view :scroll-y="modalName==null" class="page" :class="modalName!=null?'show':''">
+			<cu-custom bgColor="bg-gradual-pink" :isBack="true">
+				<block slot="content">helloWorld</block>
+			</cu-custom>
+			
+			<view class="padding flex flex-direction">
+				<app-select label=" 类    型:" v-model="type" placeholder="请选择类型" :dict="plan_type" space ></app-select>
+			</view>
+			
+			<view class="padding flex flex-direction">
+			  <my-date label="开始时间:" v-model="beginTime" placeholder="请选择开始时间" required fields="minute"></my-date>
+			</view>
+			
+			
+			<view class="padding flex flex-direction">
+			  <uni-calendar :showMonth="true" :selected="selected" />
+			</view>
+			
+			
+			<view class="padding flex flex-direction">
+			  <my-image-upload />
+			</view>
+			
+		</scroll-view>
+	</view>
+
+</template>
+
+<script>
+	const plan_type = [{text:'日常记录',value:'1'},{text:'本周工作',value:'2'},{text:'下周计划',value:'3'}];
+	import appSelect from '@/components/my-componets/appSelect.vue'
+	import myImageUpload from '@/components/my-componets/my-image-upload.vue'
+	import myDate from '@/components/my-componets/my-date.vue'
+	
+	
+	export default {
+		components:{
+		    appSelect,myImageUpload,myDate
+		 },
+		data() {
+			return {
+				modalName: null,
+				item:{msg:'退出成功'},
+				plan_type,
+				type:"1",
+				selected:[],
+				beginTime:''
+			}
+		},
+		methods: {
+			
+		}
+	}
+</script>
+
+<style>
+</style>

+ 39 - 33
pages/home/home.vue

@@ -10,54 +10,56 @@
 				</swiper-item>
 			</swiper>
 			<!-- 中部应用宫格 -->
-			<view class="bg-white"  :style="[{animation: 'show 0.4s 1'}]">
-				<view class="grid margin-bottom col-2 ">
-				  <navigator  v-for="(item,index) in middleApps" :key="index" :url="'/pages/home/' + item.name" class="nav-li" navigateTo
-					 :style="[{animation: 'show ' + ((index+1)*0.2+1) + 's 1'}]" hover-class="none">
-						<view class="flex align-center">
-							<image :src="'/static/home/'+item.icon"  mode="aspectFill" class="line2-icon"></image>
+			<!--<view class="bg-white"  :style="[{animation: 'show 0.3s 1'}]">
+				<view class="cu-list grid margin-bottom col-2  ">
+				  <view 
+				  v-for="(item,index) in middleApps" 
+				  :key="index" 
+				  class="cu-item text-center" 
+				  :style="[{animation: 'show ' + ((index+1)*0.2+1) + 's 1'}]" hover-class="none">
+						<view class="flex align-center margin-left" @tap="goCheckPage(item.page)">
+							<image :src="item.icon"  mode="aspectFill" class="line2-icon"></image>
 							<view class="text-df">{{item.title}} <br/> <span class="text-light">{{item.text}}</span></view>
 						</view>
-					</navigator>	
+					</view>	
 				</view>
-			</view>
+			</view>-->
 			<!-- 常用服务 -->
-			<view class="cu-bar bg-white solid-bottom"   :style="[{animation: 'show 0.6s 1'}]">
+			<view class="cu-bar bg-white solid-bottom" :style="[{animation: 'show 0.5s 1'}]">
 				<view class="action">
 					<text class='cuIcon-title text-blue'></text>常用服务
 				</view>
 			</view>
-			<view class=" bg-white grid col-3 padding-sm">
-				<view class="padding-sm animation-slide-bottom" :style="[{animationDelay: (index + 1)*0.1 + 's'}]" v-for="(item,index) in usList" :key="index" @tap="goPage(item.page)">
-					<view class="padding radius text-center shadow-blur solid-right ">
-						<!-- <image :src="item.icon"  mode="aspectFill" class="line2-icon"></image> -->
-						<view class="cu-avatar lg " 
-						 :style="{background: 'url(' + item.icon + ') no-repeat',backgroundSize:'100upx 100upx'}">
-						   <view class="cu-tag badge" v-if="getTtemDotInfo(item)">{{getTtemDotInfo(item)}}</view>
-						</view>
-						<view class="text-lg margin-top">{{item.title}}</view>
+				
+			<view class="cu-list grid col-4 text-sm">
+				<view class="cu-item animation-slide-bottom" :style="[{animationDelay: (index + 1)*0.05 + 's'}]" v-for="(item,index) in usList" :key="index" @tap="goPage(item.page)">
+					<view class="padding text-center">
+						<image :src="item.icon" style="width:28px;height:28px;">
+							<view class="cu-tag badge margin-top-sm" style="margin-left:1.2em" v-if="getTtemDotInfo(item)">
+							   <block v-if="getTtemDotInfo(item)">{{getTtemDotInfo(item)}}</block>
+							</view>
+						</image>
+						<view class="margin-top-xs">{{item.title}}</view>
 					</view>
 				</view>
 			</view>
-			
+				
 			<!-- 其他服务 -->
 			<view class="cu-bar bg-white solid-bottom margin-top"  :style="[{animation: 'show 0.6s 1'}]">
 				<view class="action">
-					<text class='cuIcon-title text-yellow'></text>其他服务
+					 <text class='cuIcon-title text-yellow'></text>其他服务
 				</view>
 			</view>
-			<view class=" bg-white grid col-3 padding-sm">
-				<view class="padding-sm animation-slide-bottom" :style="[{animationDelay: (index + 1)*0.1 + 's'}]" v-for="(item,index) in osList" :key="index">
-					<view class="padding radius text-center shadow-blur solid-right ">
-						<view class="cu-avatar lg "  :style="{background: 'url(' + item.icon + ') no-repeat',backgroundSize:'100upx 100upx'}"><!-- <view class="cu-tag badge">99</view> --></view>
-						<view class="text-lg margin-top">{{item.title}}</view>
+			<view class="cu-list grid col-4 text-sm">
+				<view class="cu-item animation-slide-bottom" :style="[{animationDelay: (index + 1)*0.1 + 's'}]" v-for="(item,index) in osList" :key="index" @tap="goPage(item.page)">
+					<view class="padding text-center">
+						<image :src="item.icon" style="width:28px;height:28px;"/>
+						<view class="margin-top-xs">{{item.title}}</view>
 					</view>
 				</view>
 			</view>
-			
-			<view class="cu-tabbar-height">
-			</view>
 		</scroll-view>
+		<view class="cu-tabbar-height margin-top"></view>
 	</view>
 </template>
 
@@ -200,9 +202,13 @@
 	}
 </script>
 
-<style>
-  .line2-icon {
-	width: 60px;
-	height: 60px;
-  }
+<style scoped>
+	.cu-list.grid>.cu-item {
+	  padding: 0px 0px; 
+	}
+    .line2-icon {
+	  width: 60px;
+	  height: 60px;
+    }
+	
 </style>

+ 195 - 58
pages/login/login.vue

@@ -1,28 +1,58 @@
- <template>
-    <view class="zai-box">
+  <template>
+	<view class="zai-box">
         <scroll-view scroll-y class="page">
-            <view style="text-align: center;" :style="[{animation: 'show ' + 0.4+ 's 1'}]">
-				<image src="/static/login3.png" mode='aspectFit' class="zai-logo"></image>
-				<view class="zai-title">JEECG BOOT</view>
+            <view class="text-center" :style="[{animation: 'show ' + 0.4+ 's 1'}]">
+				<image src="https://static.jeecg.com/upload/test/login4_1595818039175.png" mode='aspectFit' class="zai-logo "></image>
+				<view class="zai-title text-shadow ">JEECG BOOT </view>
 			</view>
             <view class="box padding-lr-xl login-paddingtop" :style="[{animation: 'show ' + 0.6+ 's 1'}]">
-
-                <view class="cu-form-group margin-top round shadow-blur">
-                    <view class="title">账号:</view>
-                    <input placeholder="请输入账号" name="input" v-model="userName"></input>
-                </view>
-                <view class="cu-form-group margin-top round">
-                    <view class="title">密码:</view>
-                    <input placeholder="请输入密码" name="input" type="password" v-model="password"></input>
-                </view>
-                <view class="padding  flex  flex-direction">
-                    <button class="cu-btn bg-green shadow-blur round lg" :loading="loading"
-                            @tap="onLogin"> {{loading ? "登录中...":"登 录"}}
-                    </button>
-                </view>
+				<block v-if="loginWay==1">
+					<view class="cu-form-group margin-top  shadow-warp" :class="[shape=='round'?'round':'']">
+						<view class="title"><text class="cuIcon-people margin-right-xs"></text>账号:</view>
+						<input placeholder="请输入账号" name="input" v-model="userName"></input>
+					</view>
+					<view class="cu-form-group margin-top shadow-warp" :class="[shape=='round'?'round':'']">
+						<view class="title"><text class="cuIcon-lock margin-right-xs"></text>密码:</view>
+						<input class="uni-input" placeholder="请输入密码" :password="!showPassword" v-model="password" />
+						<view class="action text-lg">
+						    <text :class="[showPassword ? 'cuIcon-attention' : 'cuIcon-attentionforbid']" @click="changePassword"></text>
+						</view>
+					</view>
+					<view class="padding text-center margin-top">
+						<button class="cu-btn bg-blue lg margin-right shadow" :loading="loading" :class="[shape=='round'?'round':'']"
+							@tap="onLogin"><text space="emsp">{{loading ? "登录中...":" 登录 "}}</text>
+						</button>
+						<button class="cu-btn line-blue lg margin-left shadow" :loading="loading" :class="[shape=='round'?'round':'']"
+							@tap="loginWay=3-loginWay">短信登录
+						</button>
+					</view>
+				</block>
+                <block v-else>
+                	<view class="cu-form-group margin-top  shadow-warp" :class="[shape=='round'?'round':'']">
+                		<view class="title"><text class="cuIcon-mobile margin-right-xs"></text>手机号:</view>
+                		<input placeholder="请输入手机号" type="number" maxlength="11" v-model="phoneNo"></input>
+                	</view>
+                	<view class="cu-form-group margin-top shadow-warp" :class="[shape=='round'?'round':'']">
+                		<view class="title"><text class="cuIcon-lock margin-right-xs"></text>验证码:</view>
+                		<input class="uni-input" placeholder="请输入验证码" v-model="smsCode"/>
+                		<view class="action">
+                			<button class="cu-btn line-blue sm" :disabled="!isSendSMSEnable" @click="onSMSSend"> {{ getSendBtnText }}</button>
+                		</view>
+                	</view>
+                	<view class="padding text-center margin-top">
+                		<button class="cu-btn bg-blue lg margin-right shadow" :loading="loading" :class="[shape=='round'?'round':'']"
+                			@tap="onSMSLogin"><text space="emsp">{{loading ? "登录中...":" 登录 "}}</text>
+                		</button>
+                		<button class="cu-btn line-blue lg margin-left shadow" :loading="loading" :class="[shape=='round'?'round':'']"
+                			@tap="loginWay=1">账户登录
+                		</button>
+                	</view>
+                </block>
+				
+	
 				<!-- #ifdef APP-PLUS -->
-				<view class="padding flex flex-direction  text-center  ">
-						当前版本:{{version}}
+				<view class="padding flex flex-direction  text-center">
+					当前版本:{{version}}
 				</view>
 				<!-- #endif -->
 				
@@ -31,12 +61,9 @@
 		<!-- 登录加载弹窗 -->
 		<view class="cu-load load-modal" v-if="loading">
 			<!-- <view class="cuIcon-emojifill text-orange"></view> -->
-			<image src="/static/login3.png" mode="aspectFit"></image>
+			<image src="https://static.jeecg.com/upload/test/login4_1595818039175.png" mode="aspectFit" class="round"></image>
 			<view class="gray-text">登录中...</view>
 		</view>
-		<!-- <my-image-upload></my-image-upload>
-		<my-select></my-select> -->
-		<!-- <my-page></my-page> -->
     </view>
 
 </template>
@@ -44,20 +71,15 @@
 <script>
 	import { ACCESS_TOKEN,USER_NAME,USER_INFO } from "@/common/util/constants"
 	import { mapActions } from "vuex"
-	import myImageUpload from "@/components/my-componets/my-image-upload.vue"
-	import mypage from "@/components/my-componets/my-page.vue"
-	import myselect from "@/components/my-componets/my-select.vue"
+    import configService from '@/common/service/config.service.js';
 	
     export default {
-		components:{
-			'my-image-upload':myImageUpload,
-			'my-select':myselect
-		},
         data() {
             return {
+				shape:'',//round 圆形
 				loading: false,
-				userName: '',
-				password: '',
+				userName: 'jeecg',
+				password: '123456',
 				phoneNo: '',
 				smsCode: '',
 				showPassword: false, //是否显示明文
@@ -65,20 +87,46 @@
 				smsCountDown: 0,
 				smsCountInterval: null,
 				toggleDelay: false,
-				version:''
+				version:'',
+				//第三方登录相关信息
+				thirdType:"",
+				thirdLoginInfo:"",
+				thirdLoginState:false,
+				bindingPhoneModal:false,
+				thirdUserUuid:'',
+				url: {
+					bindingThirdPhone: '/sys/thirdLogin/bindingThirdPhone'
+				}
             };
         },
 		onLoad:function(){
 			// #ifdef APP-PLUS
 			var that=this
 			plus.runtime.getProperty( plus.runtime.appid, function ( wgtinfo ) {
-					that.version=wgtinfo.version
-				} );
+				that.version=wgtinfo.version
+			});
 			// #endif
 		},
+		computed: {
+		      isSendSMSEnable() {
+		        return this.smsCountDown <= 0 && this.phoneNo.length > 4;
+		      },
+		      getSendBtnText() {
+		        if (this.smsCountDown > 0) {
+		          return this.smsCountDown + '秒后发送';
+		        } else {
+		          return '发送验证码';
+		        }
+		      },
+		      canSMSLogin() {
+		        return this.userName.length > 4 && this.smsCode.length > 4;
+		      },
+		      canPwdLogin() {
+		        return this.userName.length > 4 && this.password.length > 4;
+		      },
+		},
         methods: {
-			 ...mapActions([ "mLogin","PhoneLogin" ]),
-			
+			 ...mapActions([ "mLogin","PhoneLogin","ThirdLogin" ]),
 			onLogin: function (){
 			        if(!this.userName || this.userName.length==0){
 			          this.$tip.toast('请填写用户名');
@@ -94,42 +142,131 @@
 			        }
 					this.loading=true;
 			        this.mLogin(loginParams).then((res) => {
-					  console.log("mLogin",res)
 					  this.loading=false;
 			          if(res.data.success){
+						 // #ifdef APP-PLUS
+						  this.saveClientId()
+						 // #endif
+						 // #ifndef APP-PLUS
 						  this.$tip.success('登录成功!')
 						  this.$Router.replaceAll({name:'index'})
-						 /* uni.reLaunch({
-							url: '/pages/index/index'
-						  }); */
-			          }else{
+						 // #endif
+						}else{
 			              this.$tip.alert(res.data.message);
-			          }
+			            }
 			        }).catch((err) => {
 			          let msg = err.data.message || "请求出现错误,请稍后再试"
-			          this.$tip.alert(msg);
+			          this.loading=false;
+					  this.$tip.alert(msg);
 			        }).finally(()=>{
 					  this.loading=false;
-					})
-			      }
-        }
+				})
+			},
+			saveClientId(){
+				var info = plus.push.getClientInfo();
+				var cid = info.clientid;
+				this.$http.get("/sys/user/saveClientId",{params:{clientId:cid}}).then(res=>{
+					console.log("res::saveClientId>",res)
+					this.$tip.success('登录成功!')
+					this.$Router.replaceAll({name:'index'})
+				})
+			},
+			changePassword() {
+				this.showPassword = !this.showPassword;
+			},
+			onSMSSend() {
+				let smsParams = {};
+				smsParams.mobile=this.phoneNo;
+				smsParams.smsmode="0";
+				let checkPhone = new RegExp(/^[1]([3-9])[0-9]{9}$/);
+                if(!smsParams.mobile || smsParams.mobile.length==0){
+					this.$tip.toast('请输入手机号');
+					return false
+				}
+				if(!checkPhone.test(smsParams.mobile)){
+					this.$tip.toast('请输入正确的手机号');
+					return false
+				}
+				this.$http.post("/sys/sms",smsParams).then(res=>{
+				  if(res.data.success){
+					this.smsCountDown = 60;
+					this.startSMSTimer();
+				  }else{
+					this.smsCountDown = 0;
+					this.$tip.toast(res.data.message);
+				  }
+				});
+			  },
+			startSMSTimer() {
+				this.smsCountInterval = setInterval(() => {
+				  this.smsCountDown--;
+				  if (this.smsCountDown <= 0) {
+					clearInterval(this.smsCountInterval);
+				  }
+				}, 1000);
+			},
+			onSMSLogin() {
+				let checkPhone = new RegExp(/^[1]([3-9])[0-9]{9}$/);
+				
+				if(!this.phoneNo || this.phoneNo.length==0){
+				  this.$tip.toast('请填写手机号');
+				  return;
+				}
+				if(!checkPhone.test(this.phoneNo)){
+					this.$tip.toast('请输入正确的手机号');
+					return false
+				}
+				if(!this.smsCode || this.smsCode.length==0){
+				  this.$tip.toast('请填短信验证码');
+				  return;
+				}
+				let loginParams = {
+				  mobile:this.phoneNo,
+				  captcha:this.smsCode
+				};
+				this.PhoneLogin(loginParams).then((res) => {
+				  console.log("res====》",res)
+				  if(res.data.success){
+					this.$tip.success('登录成功!')
+					this.$Router.replaceAll({name:'index'})
+				  }else{
+					this.$tip.error(res.data.message);
+				  }
+				}).catch((err) => {
+				  let msg = ((err.response || {}).data || {}).message || err.data.message || "请求出现错误,请稍后再试"
+				  this.$tip.error(msg);
+				});
+			},
+			loginSuccess() {
+			  // 登陆成功,重定向到主页
+			  this.$Router.replace({name:'index'})
+			},
+			requestFailed(err) {
+			  this.$message.warning("登录失败")
+			},
+        },
+		beforeDestroy() {
+		    if (this.smsCountInterval) {
+		        clearInterval(this.smsCountInterval);
+		    }
+		},
     }
 </script>
 
 <style>
     .login-paddingtop {
-        padding-top: 200 upx;
+        padding-top: 100upx;
     }
 
     .zai-box {
-        padding: 0 20 upx;
-        padding-top: 100 upx;
+        padding: 0 20upx;
+        padding-top: 100upx;
         position: relative;
     }
 
     .zai-logo {
         width: 200upx;
-        height: 300 upx;
+        height: 150px;
     }
 
     .zai-title {
@@ -143,9 +280,9 @@
     }
 
     .zai-label {
-        padding: 60 upx 0;
+        padding: 60upx 0;
         text-align: center;
-        font-size: 30 upx;
+        font-size: 30upx;
         color: #a7b6d0;
     }
 
@@ -153,8 +290,8 @@
         background: #ff65a3;
         color: #fff;
         border: 0;
-        border-radius: 100 upx;
-        font-size: 36 upx;
+        border-radius: 100upx;
+        font-size: 36upx;
     }
 
     .zai-btn:after {
@@ -163,7 +300,7 @@
 
     /*按钮点击效果*/
     .zai-btn.button-hover {
-        transform: translate(1 upx, 1 upx);
+        transform: translate(1upx, 1upx);
     }
 
 </style>

+ 117 - 0
pages/user/location.vue

@@ -0,0 +1,117 @@
+<template>
+    <view>
+		<cu-custom :bgColor="NavBarColor" :isBack="true">
+			<block slot="backText">返回</block>
+			<block slot="content">定位</block>
+		</cu-custom>
+		<map 
+		style="width: 100%; height:500px;" 
+		:latitude="latitude" 
+		:longitude="longitude" 
+		:markers="marker"
+		:scale="scale"
+		>
+		</map>
+    </view>
+</template>
+
+<script>
+	export default {
+	    data() {
+	        return {
+				NavBarColor:this.NavBarColor,
+	            id:0, // 使用 marker点击事件 需要填写id
+	            title: 'map',
+	           latitude: 40.009704,  //纬度
+		      longitude: 116.374999,  //经度
+		     marker: [{
+			     id:0,
+			     latitude: 40.009704,//纬度
+			     longitude: 116.374999,//经度
+			     iconPath: '/static/location.png',    //显示的图标        
+			     rotate:0,   // 旋转度数
+			     width:20,   //宽
+			     height:20,   //高
+			     title:'你在哪了',//标注点名
+			     alpha:0.5,   //透明度
+			     /* label:{//为标记点旁边增加标签   //因背景颜色H5不支持
+				     content:'北京国炬公司',//文本
+				    color:'red',//文本颜色
+					  fontSize:24,//文字大小
+					   x:5,//label的坐标,原点是 marker 对应的经纬度
+					   y:1,//label的坐标,原点是 marker 对应的经纬度 
+					   borderWidth:12,//边框宽度
+					   borderColor:'pink',//边框颜色    
+					  borderRadius:20,//边框圆角                        
+					  bgColor:'black',//背景色
+					  padding:5,//文本边缘留白
+					   textAlign:'right'//文本对齐方式。
+			   }, */
+			   callout:{//自定义标记点上方的气泡窗口 点击有效  
+			     content:'北京国炬公司',//文本
+			     color:'#ffffff',//文字颜色
+			     fontSize:14,//文本大小
+			     borderRadius:2,//边框圆角
+			     bgColor:'#00c16f',//背景颜色
+			     display:'ALWAYS'//常显
+			   }
+			   // anchor:{//经纬度在标注图标的锚点,默认底边中点
+			   //     x:0,    原点为给出的经纬度
+			   //     y:0,
+			   // }
+						
+		   }],
+		   scale:16,//地图缩放程度
+	      controls:[{//在地图上显示控件,控件不随着地图移动
+		    id:1,//控件id
+		    iconPath:'/static/login3.png',//显示的图标
+		      clickable:true,
+		    position:{//控件在地图的位置
+			   left:15,
+			   top:15,
+			   width:50,
+			   height:50
+		     },    
+	     }], 
+	      circles:[{//在地图上显示圆
+		    latitude: 40.009704,
+		    longitude: 116.374999,
+		    radius:50,//半径
+		      fillColor:"#ffffffAA",//填充颜色
+		    color:"#55aaffAA",//描边的颜色
+		    strokeWidth:1//描边的宽度
+		  }], 
+	    /*  polyline:[{//指定一系列坐标点,从数组第一项连线至最后一项
+	       points:[
+			    {latitude: 40.009153,longitude: 116.374935},
+			    {latitude: 40.009704,longitude: 116.374999},
+	       ],
+	       color:"#0000AA",//线的颜色
+	       width:2,//线的宽度
+	       dottedLine:true,//是否虚线
+	       arrowLine:true,    //带箭头的线 开发者工具暂不支持该属性
+	     }], */
+	     }
+	    },
+		onLoad() {
+			this.getLocation()
+		},
+	    methods: {
+			getLocation(){
+				uni.getLocation({
+				    type: 'gcj02',
+				    success: function (res) {
+				        console.log('当前位置的经度:' + res.longitude);
+				        console.log('当前位置的纬度:' + res.latitude);
+				    },
+					fail: function (res) {
+						 console.log('当前位置的经度');
+					}	
+				});
+			}
+	    }
+	}
+</script>
+
+<style>
+</style>

+ 70 - 34
pages/user/people.vue

@@ -2,25 +2,10 @@
 	<view>
 		<scroll-view scroll-y class="page">
 			  <!-- 头部logo-->
-		  <view class="UCenter-bg" @click="remove">
-		    <image :src="personalList.avatar" round class="png animation-slide-right margin-bottom-sm" mode="widthFix" :style="[{animationDelay: '0.1s'}]"></image>
-		    <view class="text-xl animation-slide-left" :style="[{animationDelay: '0.2s'}]">
-		       {{personalList.depart}}
-		    </view>
-		    <image src="/static/wave.gif" mode="scaleToFill" class="gif-wave"></image>
+		  <view class="UCenter-bg">
+		    <image :src="personalList.avatar" class="png round animation-slide-right margin-bottom-sm" mode="scaleToFill" :style="[{animationDelay: '0.1s'}]"></image>
+		    <image src="https://static.jeecg.com/upload/test/wave_1595818053612.gif" mode="scaleToFill" class="gif-wave"></image>
 		  </view>
-		   <!-- 个人信息卡片-->
-		  <!-- <view class="cu-list menu-avatar">
-		   	<view class="cu-item">
-		   		<view class="cu-avatar round lg" style="background-image:url(https://ossweb-img.qq.com/images/lol/web201310/skin/big10006.jpg);"></view>
-		   		<view class="content flex-sub">
-		   			<view class="text-grey">{{personalList.avatar}}</view>
-		   			<view class="text-gray text-sm flex justify-between">
-		   				经理
-		   			</view>
-		   		</view>
-		   	</view>
-		   </view> -->
 		  <view class="padding flex text-center text-grey bg-white shadow-warp">
 		    <view class="flex flex-sub flex-direction solid-right animation-slide-top" :style="[{animationDelay: '0.2s'}]">
 		      <view class="text-xl text-orange">{{personalList.username}}</view>
@@ -40,24 +25,37 @@
 		        <text class="text-grey">收藏</text>
 		      </view>
 		    </view>
-		    <view class="cu-item arrow animation-slide-bottom" :style="[{animationDelay: '0.3s'}]">
+		    <view class="cu-item arrow animation-slide-bottom" :style="[{animationDelay: '0.2s'}]">
 		      <view class="content">
 		        <text class="cuIcon-redpacket_fill text-red"></text>
 		        <text class="text-grey">红包</text>
 		      </view>
 		    </view>
-		    <view class="cu-item arrow animation-slide-bottom" :style="[{animationDelay: '0.5s'}]">
-				<navigator class="content" url="/pages/user/userdetail" hover-class="none">
+			<view class="cu-item arrow animation-slide-bottom" :style="[{animationDelay: '0.3s'}]" @tap="scan">
+			  <view class="content">
+			    <text class="cuIcon-scan text-red"></text>
+			    <text class="text-grey">扫码</text>
+			  </view>
+			</view>
+			<navigator class="cu-item arrow animation-slide-bottom" :style="[{animationDelay: '0.4s'}]" url="/pages/user/location" hover-class="none">
+				<view class="content" >
+				    <text class="cuIcon-location text-cyan"></text>
+					<text class="text-grey">定位</text>
+				</view>
+			</navigator>
+			<navigator class="cu-item arrow animation-slide-bottom" url="/pages/user/userdetail" :style="[{animationDelay: '0.6s'}]">
+			     <view class="content">
 				    <text class="cuIcon-settingsfill text-cyan"></text>
 					<text class="text-grey">设置</text>
-				</navigator>
-		    </view>
-			<view class="cu-item arrow animation-slide-bottom" :style="[{animationDelay: '0.7s'}]">
-				<navigator class="content" url="/pages/user/userexit" hover-class="none">
+			    </view>
+			</navigator>
+		   
+			<navigator class="cu-item arrow animation-slide-bottom" :style="[{animationDelay: '0.7s'}]" url="/pages/user/userexit" hover-class="none">
+				<view class="content" >
 				    <text class="cuIcon-exit text-cyan"></text>
 					<text class="text-grey">退出</text>
-				</navigator>
-			</view>
+				</view>
+			</navigator>
 		  </view>
 		  <view class="cu-tabbar-height"></view>
 		</scroll-view>
@@ -79,6 +77,7 @@
 				  positionUrl:'/sys/position/list',
 				  departUrl:'/sys/user/userDepartList',
 				  userUrl:'/sys/user/queryById',
+				  postUrl:'/sys/position/queryByCode',
 				  userId:'',
 				  id:''
 			};
@@ -88,16 +87,33 @@
 				immediate: true,
 				handler() {
 					console.log('watch',this.cur)
-				    this.load()
+				    this.userId=this.$store.getters.userid;
+					this.load()
 				},
 			},
 		},
 		methods: {
-			remove(){
-				 uni.removeStorageSync('Access-Token')
+			scan(){
+				 console.log("进来了")
+				// #ifndef H5
+				uni.scanCode({
+				    success: function (res) {
+						console.log('条码res:' + res);
+				        console.log('条码类型:' + res.scanType);
+				        console.log('条码内容:' + res.result);
+				    }
+				});
+				// #endif
+				// #ifdef H5
+				this.$tip.alert("暂不支持")
+				// #endif
 			},
 			load(){
-				this.$http.get(this.userUrl,{params:{id:this.$store.getters.userid}}).then(res=>{
+				if(!this.userId){
+					
+					return;
+				}
+				this.$http.get(this.userUrl,{params:{id:this.userId}}).then(res=>{
 					console.log("res",res)
 					 if (res.data.success) {
 						let perArr = res.data.result
@@ -105,21 +121,41 @@
 						this.personalList.avatar =avatar
 						this.personalList.realname = perArr.realname
 						this.personalList.username = perArr.username
-						this.personalList.post = perArr.post
 						this.personalList.depart = perArr.departIds
+					    this.getpost(perArr.post)
+					}
+				}).catch(err => {
+					console.log(err);
+				});
+				
+			},
+			getpost(code){
+				if(!code||code.length==0){
+					this.personalList.post='员工'
+					return false;
+				}
+				this.$http.get(this.postUrl,{params:{code:code}}).then(res=>{
+					console.log("postUrl",res)
+					 if (res.data.success) {
+						this.personalList.post=res.data.result.name
 					}
 				}).catch(err => {
 					console.log(err);
 				});
 				
-			}		
+			}
 		}
 	}
 </script>
 
 <style>
 .UCenter-bg {
-  background-image: url(https://image.weilanwl.com/color2.0/index.jpg);
+	/* #ifdef MP-WEIXIN */
+	background-image: url('https://static.jeecg.com/upload/test/blue_1595818030310.png');
+	/* #endif */
+	/* #ifndef MP-WEIXIN */
+	background-image: url('/static/blue.png');
+	/* #endif */
   background-size: cover;
   height: 400rpx;
   display: flex;

+ 2 - 52
pages/user/userdetail.vue

@@ -54,32 +54,7 @@
 				</view>
 			</view>
 			
-			<view class="cu-list menu">
-				<view class="cu-item animation-slide-bottom" :style="[{animationDelay: '0.6s'}]">
-					<view class="content">
-						<text class="text-grey">所在部门</text>
-					</view>
-					<view class="action">
-						<text class="text-grey">{{personalMsg.orgCode}}</text>
-					</view>
-				</view>
-				<view class="cu-item animation-slide-bottom" :style="[{animationDelay: '0.7s'}]">
-					<view class="content">
-						<text class="text-grey">工号</text>
-					</view>
-					<view class="action">
-						<text class="text-grey">{{personalMsg.workNo}}</text>
-					</view>
-				</view>
-				<view class="cu-item animation-slide-bottom" :style="[{animationDelay: '0.8s'}]">
-					<view class="content">
-						<text class="text-grey">状态</text>
-					</view>
-					<view class="action">
-						<text class="text-grey">{{personalMsg.status}}</text>
-					</view>
-				</view>
-			</view>
+			
 			
 			<view class="cu-list menu">
 				<view class="cu-item animation-slide-bottom" :style="[{animationDelay: '0.9s'}]">
@@ -100,32 +75,7 @@
 				</view>
 			</view>
 			
-			<view class="cu-list menu">
-				<view class="cu-item animation-slide-bottom" :style="[{animationDelay: '1.1s'}]">
-					<view class="content">
-						<text class="text-grey">职务</text>
-					</view>
-					<view class="action">
-						<text class="text-grey">{{personalMsg.post}}</text>
-					</view>
-				</view>
-				<view class="cu-item animation-slide-bottom" :style="[{animationDelay: '1.2s'}]">
-					<view class="content">
-						<text class="text-grey">身份</text>
-					</view>
-					<view class="action">
-						<text class="text-grey">{{personalMsg.identity}}</text>
-					</view>
-				</view>
-				<view class="cu-item animation-slide-bottom" v-if="personalMsg.identity =='上级'" >
-					<view class="content">
-						<text class="text-grey">负责部门</text>
-					</view>
-					<view class="action">
-						<text class="text-grey">{{personalMsg.departIds}}</text>
-					</view>
-				</view>
-			</view>
+	
 
 		</scroll-view>
 	</view>

+ 1 - 16
pages/user/useredit.vue

@@ -32,23 +32,8 @@
 				<view class="title">性别</view>
 				<switch class='switch-sex' @change="SwitchC" :class="switchC?'checked':''" :checked="switchC?true:false"></switch>
 			</view>
-			<view class="cu-form-group">
-				<view class="title">生日</view>
-				<picker mode="date" :value="myFormData.birthday" @change="DateChange">
-					<view class="picker">
-						{{myFormData.birthday}}
-					</view>
-				</picker>
-			</view>
 			
-			<view class="cu-form-group margin-top">
-				<view class="title">所在部门</view>
-				<input placeholder="所在部门" name="input" v-model="myFormData.orgCode" disabled></input>
-			</view>
-			<view class="cu-form-group">
-				<view class="title">工号</view>
-				<input placeholder="工号" name="input"  v-model="myFormData.workNo" disabled></input>
-			</view>
+			
 			
 			<view class="cu-form-group margin-top">
 				<view class="title">手机号码</view>

BIN
static/blue.png


BIN
static/folder.png


BIN
static/home/128/chuchai.png


BIN
static/home/128/duanxin.png


BIN
static/home/128/gongwen.png


BIN
static/home/128/hetong.png


BIN
static/home/128/huiyi.png


BIN
static/home/128/kaoqin.png


BIN
static/home/128/kehu.png


BIN
static/home/128/liucheng.png


BIN
static/home/128/more.png


BIN
static/home/128/qingjia.png


BIN
static/home/128/qingjia1.png


BIN
static/home/128/renwu.png


BIN
static/home/128/richang.png


BIN
static/home/128/richeng.png


BIN
static/home/128/tongxun.png


BIN
static/home/128/tongzhi.png


BIN
static/home/128/toupiao.png


BIN
static/home/128/wendang.png


BIN
static/home/128/xinwen.png


BIN
static/home/128/youjian.png


BIN
static/home/128/zhoubao.png


BIN
static/home/fresher_ticket_back.png


BIN
static/home/icon_message_back.png


BIN
static/home/line2_icon1.png


BIN
static/home/line2_icon2.png


BIN
static/home/line2_icon3.png


BIN
static/home/line2_icon4.png


BIN
static/home/line4_icon1.png


BIN
static/home/line4_icon2.png


BIN
static/home/line4_icon3.png


BIN
static/home/line4_icon4.png


+ 4 - 3
store/index.js

@@ -31,7 +31,6 @@ export default new Vuex.Store({
   actions: {
     // 登录
     mLogin({ commit }, userInfo) {
-      console.log("mLogin",userInfo)
       return new Promise((resolve, reject) => {
        api.login(userInfo).then(response => {
           if(response.data.code ==200){ 
@@ -56,9 +55,11 @@ export default new Vuex.Store({
     PhoneLogin({ commit }, userInfo) {
       return new Promise((resolve, reject) => {
         api.phoneNoLogin(userInfo).then(response => {
-          if(response.code =='200'){
-            const result = response.result
+          if(response.data.code ==200){
+            const result = response.data.result
             const userInfo = result.userInfo
+			uni.setStorageSync(ACCESS_TOKEN,result.token);
+			uni.setStorageSync(USER_INFO,userInfo);
             commit('SET_TOKEN', result.token)
             commit('SET_NAME', { username: userInfo.username,realname: userInfo.realname})
             commit('SET_AVATAR', userInfo.avatar)

+ 79 - 0
uni.scss

@@ -0,0 +1,79 @@
+/**
+ * 这里是uni-app内置的常用样式变量
+ *
+ * uni-app 官方扩展插件及插件市场(https://ext.dcloud.net.cn)上很多三方插件均使用了这些样式变量
+ * 如果你是插件开发者,建议你使用scss预处理,并在插件代码中直接使用这些变量(无需 import 这个文件),方便用户通过搭积木的方式开发整体风格一致的App
+ *
+ */
+
+/**
+ * 如果你是App开发者(插件使用者),你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能
+ *
+ * 如果你的项目同样使用了scss预处理,你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件
+ */
+
+/* 颜色变量 */
+
+/* 行为相关颜色 */
+$uni-color-primary: #007aff;
+$uni-color-success: #4cd964;
+$uni-color-warning: #f0ad4e;
+$uni-color-error: #dd524d;
+
+/* 文字基本颜色 */
+$uni-text-color:#333;//基本色
+$uni-text-color-inverse:#fff;//反色
+$uni-text-color-grey:#999;//辅助灰色,如加载更多的提示信息
+$uni-text-color-placeholder: #808080;
+$uni-text-color-disable:#c0c0c0;
+
+/* 背景颜色 */
+$uni-bg-color:#ffffff;
+$uni-bg-color-grey:#f8f8f8;
+$uni-bg-color-hover:#f1f1f1;//点击状态颜色
+$uni-bg-color-mask:rgba(0, 0, 0, 0.4);//遮罩颜色
+
+/* 边框颜色 */
+$uni-border-color:#c8c7cc;
+$uni-border-color-gray:#f1f1f1;
+
+/* 尺寸变量 */
+
+/* 文字尺寸 */
+$uni-font-size-sm:24upx;
+$uni-font-size-base:28upx;
+$uni-font-size-lg:32upx;
+$uni-font-size-xl:48upx;
+
+/* 图片尺寸 */
+$uni-img-size-ssm:35upx;
+$uni-img-size-sm:40upx;
+$uni-img-size-base:52upx;
+$uni-img-size-lg:80upx;
+
+/* Border Radius */
+$uni-border-radius-sm: 4upx;
+$uni-border-radius-base: 6upx;
+$uni-border-radius-lg: 12upx;
+$uni-border-radius-circle: 50%;
+
+/* 水平间距 */
+$uni-spacing-row-sm: 10px;
+$uni-spacing-row-base: 20upx;
+$uni-spacing-row-lg: 30upx;
+
+/* 垂直间距 */
+$uni-spacing-col-sm: 8upx;
+$uni-spacing-col-base: 16upx;
+$uni-spacing-col-lg: 24upx;
+
+/* 透明度 */
+$uni-opacity-disabled: 0.3; // 组件禁用态的透明度
+
+/* 文章场景相关 */
+$uni-color-title: #2C405A; // 文章标题颜色
+$uni-font-size-title:40upx;
+$uni-color-subtitle: #555555; // 二级标题颜色
+$uni-font-size-subtitle:36upx;
+$uni-color-paragraph: #3F536E; // 文章段落颜色
+$uni-font-size-paragraph:30upx;

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