qxj 6 mēneši atpakaļ
revīzija
8613dc74e1
100 mainītis faili ar 8721 papildinājumiem un 0 dzēšanām
  1. 4 0
      .gitignore
  2. 16 0
      .hbuilderx/launch.json
  3. 179 0
      App.vue
  4. 3 0
      androidPrivacy.json
  5. 9 0
      api/common.js
  6. 8 0
      api/companyWxUser.js
  7. 24 0
      api/course.js
  8. 25 0
      api/crm.js
  9. 28 0
      api/qw.js
  10. 27 0
      api/user.js
  11. 156 0
      assets/css/common.less
  12. 2 0
      assets/iconfont/iconfont.css
  13. 9 0
      common/config.js
  14. 74 0
      common/request.js
  15. 23 0
      index.html
  16. 35 0
      main.js
  17. 145 0
      manifest.json
  18. 0 0
      note.txt
  19. 74 0
      package-lock.json
  20. 24 0
      package.json
  21. 298 0
      pages.json
  22. 216 0
      pages/auth/login.vue
  23. 123 0
      pages/common/launch.vue
  24. 193 0
      pages/customer/index.vue
  25. 22 0
      pages/index/index.vue
  26. 22 0
      pages/msg/index.vue
  27. 116 0
      pages/user/about.vue
  28. 43 0
      pages/user/ai/index.vue
  29. 168 0
      pages/user/courseSop/sop.vue
  30. 89 0
      pages/user/courseSop/sopLogsDetails.vue
  31. 302 0
      pages/user/courseSop/sopLosList.vue
  32. 131 0
      pages/user/crm/components/customerInfo.vue
  33. 225 0
      pages/user/crm/customerDetails.vue
  34. 235 0
      pages/user/crm/importWxUser.vue
  35. 193 0
      pages/user/crm/lineCustomer.vue
  36. 193 0
      pages/user/crm/myCustomer.vue
  37. 179 0
      pages/user/editPwd.vue
  38. 293 0
      pages/user/editUser.vue
  39. 398 0
      pages/user/index.vue
  40. 168 0
      pages/user/qwSop/sop.vue
  41. 90 0
      pages/user/qwSop/sopLogsDetails.vue
  42. 302 0
      pages/user/qwSop/sopLosList.vue
  43. 230 0
      pages/user/userInfo.vue
  44. 257 0
      pages/user/users/userInfo.vue
  45. 256 0
      pages/user/users/users.vue
  46. 46 0
      router/router.js
  47. BIN
      static/account.png
  48. BIN
      static/images/customer.png
  49. BIN
      static/images/default.png
  50. BIN
      static/images/empty.png
  51. BIN
      static/images/home.png
  52. BIN
      static/images/home_select.png
  53. BIN
      static/images/icon_about_us.png
  54. BIN
      static/images/icon_ai.png
  55. BIN
      static/images/icon_comp.png
  56. BIN
      static/images/icon_comp_white.png
  57. BIN
      static/images/icon_cust.png
  58. BIN
      static/images/icon_edit.png
  59. BIN
      static/images/icon_phone.png
  60. BIN
      static/images/icon_set.png
  61. BIN
      static/images/menu_info.png
  62. BIN
      static/images/my.png
  63. BIN
      static/images/my_select.png
  64. BIN
      static/images/office.png
  65. BIN
      static/images/order.png
  66. BIN
      static/images/order_select.png
  67. BIN
      static/images/phone.png
  68. BIN
      static/images/report.png
  69. BIN
      static/images/report_select.png
  70. BIN
      static/images/right_arrow.png
  71. BIN
      static/images/send.png
  72. BIN
      static/images/sms.png
  73. BIN
      static/logo.png
  74. BIN
      static/password.png
  75. 77 0
      uni.scss
  76. 6 0
      uni_modules/mescroll-uni/changelog.md
  77. 19 0
      uni_modules/mescroll-uni/components/mescroll-body/mescroll-body.css
  78. 400 0
      uni_modules/mescroll-uni/components/mescroll-body/mescroll-body.vue
  79. 47 0
      uni_modules/mescroll-uni/components/mescroll-diy/beibei/components/mescroll-down.css
  80. 39 0
      uni_modules/mescroll-uni/components/mescroll-diy/beibei/components/mescroll-down.vue
  81. 360 0
      uni_modules/mescroll-uni/components/mescroll-diy/beibei/mescroll-body.vue
  82. 49 0
      uni_modules/mescroll-uni/components/mescroll-diy/beibei/mescroll-uni-option.js
  83. 437 0
      uni_modules/mescroll-uni/components/mescroll-diy/beibei/mescroll-uni.vue
  84. 44 0
      uni_modules/mescroll-uni/components/mescroll-diy/xinlang/components/mescroll-down.css
  85. 53 0
      uni_modules/mescroll-uni/components/mescroll-diy/xinlang/components/mescroll-down.vue
  86. 32 0
      uni_modules/mescroll-uni/components/mescroll-diy/xinlang/components/mescroll-up.css
  87. 40 0
      uni_modules/mescroll-uni/components/mescroll-diy/xinlang/components/mescroll-up.vue
  88. 380 0
      uni_modules/mescroll-uni/components/mescroll-diy/xinlang/mescroll-body.vue
  89. 64 0
      uni_modules/mescroll-uni/components/mescroll-diy/xinlang/mescroll-uni-option.js
  90. 462 0
      uni_modules/mescroll-uni/components/mescroll-diy/xinlang/mescroll-uni.vue
  91. 116 0
      uni_modules/mescroll-uni/components/mescroll-empty/mescroll-empty.vue
  92. 55 0
      uni_modules/mescroll-uni/components/mescroll-uni/components/mescroll-down.css
  93. 47 0
      uni_modules/mescroll-uni/components/mescroll-uni/components/mescroll-down.vue
  94. 83 0
      uni_modules/mescroll-uni/components/mescroll-uni/components/mescroll-top.vue
  95. 47 0
      uni_modules/mescroll-uni/components/mescroll-uni/components/mescroll-up.css
  96. 39 0
      uni_modules/mescroll-uni/components/mescroll-uni/components/mescroll-up.vue
  97. 15 0
      uni_modules/mescroll-uni/components/mescroll-uni/mescroll-i18n.js
  98. 57 0
      uni_modules/mescroll-uni/components/mescroll-uni/mescroll-mixins.js
  99. 64 0
      uni_modules/mescroll-uni/components/mescroll-uni/mescroll-uni-option.js
  100. 36 0
      uni_modules/mescroll-uni/components/mescroll-uni/mescroll-uni.css

+ 4 - 0
.gitignore

@@ -0,0 +1,4 @@
+node_modules/
+.project
+unpackage/
+.DS_Store

+ 16 - 0
.hbuilderx/launch.json

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

+ 179 - 0
App.vue

@@ -0,0 +1,179 @@
+<script>
+	var wsUrl="ws://42.194.245.189:8008/app/webSocket/w-";
+	var pingpangTimes=null;
+	var isSocketOpen=false;
+	var socket=null;
+	export default {
+		data() {
+			return {
+			};
+		},
+		onLaunch: function() {
+			console.log('App Launch')
+			var that=this;
+			uni.$on('initSocket', () => {
+				that.initSocket()
+			})
+			uni.$on('sendMsg', (item) => {
+				that.sendMsg(item)
+			})
+			uni.$on('closeWebSocket', () => {
+				that.closeWebSocket()
+			})
+			
+		},
+		onShow: function() {
+			console.log('App Show')
+		},
+		onHide: function() {
+			console.log('App Hide')
+		},
+		onUnload() {
+			
+			console.log(pingpangTimes)
+			clearInterval(pingpangTimes)
+			
+		},
+		methods:{
+			
+			closeWebSocket(){
+				if(socket!=null){
+					uni.closeSocket();
+				}
+				clearInterval(pingpangTimes)
+			},
+			initSocket(){
+				console.log("initSocket")
+				//创建一个socket连接
+				var userId=uni.getStorageSync('companyUserId') ;
+				var that=this;
+				console.log(wsUrl)
+				socket=uni.connectSocket({
+					url:wsUrl+userId,
+					multiple:true,
+					success: res=>{
+						console.log('WebSocket连接已打开!');
+						isSocketOpen=true
+						//先确保清除了之前的心跳定时器
+						clearInterval(pingpangTimes)
+						uni.onSocketMessage((res)=>{
+							console.log("收到消息")
+							const redata = JSON.parse(res.data);
+							console.log(redata);
+							if(redata.cmd=="heartbeat"){
+								  //心跳
+								  console.log("heartbeat")
+							}
+							else if(redata.cmd=="getWeixinId"){
+								uni.$emit('getWeixinId',redata); 
+							}
+							else if(redata.cmd=="getWeixinList"){
+								uni.$emit('getWeixinList',redata); 
+							}
+							else if(redata.cmd=="sendSop"){
+								uni.$emit('sendSop',redata); 
+							}
+							  
+						})
+						pingpangTimes=setInterval(()=>{
+							var userId=uni.getStorageSync('userId') ;
+							var data={cmd:"heartbeat",userId: "w-"+userId};
+							console.log(data)
+							uni.sendSocketMessage({
+								data:JSON.stringify(data),
+								success:()=>{
+									console.log('WebSocket发送心条数据!');
+								},
+								fail:()=>{
+									isSocketOpen=false
+								}
+							});
+						},5000)
+					},
+					error: res=>{
+						console.log(res)
+					},
+				})
+				//监听socket打开
+				uni.onSocketOpen(()=>{
+					isSocketOpen=true
+					console.log('WebSocket连接已打开!!');
+				})
+				//监听socket关闭
+				uni.onSocketClose(()=>{
+					isSocketOpen=false
+					console.log('WebSocket连接已关闭!');
+				})
+				//监听socket错误
+				uni.onSocketError(()=>{
+					isSocketOpen=false
+					console.log('WebSocket连接打开失败');
+				})
+				
+			},
+			sendMsg(data){
+				if(isSocketOpen){
+					var userId=uni.getStorageSync('companyUserId') ;
+					uni.sendSocketMessage({
+						data: JSON.stringify(data),
+						success:()=>{
+							
+							console.log("发送成功")
+						},
+						fail:()=>{
+							console.log("发送失败")
+						}
+					});
+					
+				}
+				
+			},
+		
+		}
+	}
+</script>
+
+<style lang="scss">
+	/*每个页面公共css */
+	@import "@/uni_modules/uview-ui/index.scss";
+	view{
+		box-sizing: border-box;
+	}
+	.ellipsis{
+		overflow: hidden;
+		text-overflow: ellipsis;
+		white-space: nowrap;
+	}
+	.ellipsis2{
+		overflow:hidden; 
+		text-overflow:ellipsis;
+		display:-webkit-box; 
+		-webkit-box-orient:vertical;
+		-webkit-line-clamp:2; 
+	}
+	 
+	
+	.no-data-box{
+		height:100%;
+		width: 100%;
+		display: flex;
+		justify-content: center;
+		align-items: center;
+		flex-direction: column;
+		image{
+			width: 264upx;
+			height: 212upx;
+		}
+		.empty-title{
+			margin-top: 20rpx;
+			font-size: 28rpx;
+			color: gray;
+			 
+		}
+	}
+</style>
+<style lang="less">
+/*每个页面公共css */
+@import './assets/iconfont/iconfont.css';
+@import './assets/css/common.less';
+</style>

+ 3 - 0
androidPrivacy.json

@@ -0,0 +1,3 @@
+{
+    "prompt" : "template"
+}

+ 9 - 0
api/common.js

@@ -0,0 +1,9 @@
+import Request from '../common/request.js';
+let request = new Request().http
+
+ export function getDicts(data) {
+	 return request('/app/common/getDicts',data,'GET');
+ }
+ export function uploadOSS(data) {
+ 	 return request('/app/common/uploadOSS',data,'POST','application/json;charset=UTF-8');
+ }

+ 8 - 0
api/companyWxUser.js

@@ -0,0 +1,8 @@
+import Request from '../common/request.js';
+let request = new Request().http
+ 
+ 
+ export function addCompanyWxUser(data) {
+ 	 return request('/app/companyWxUser/addCompanyWxUser',data,'POST','application/json;charset=UTF-8');
+ }
+ 

+ 24 - 0
api/course.js

@@ -0,0 +1,24 @@
+import Request from '../common/request.js';
+let request = new Request().http
+
+ export function getCourseSopList(data) {
+ 	 return request('/app/courseSop/getCourseSopList',data,'GET');
+ }
+ 
+ 
+ 
+ export function getCourseSopLogsList(data) {
+ 	 return request('/app/courseSop/getCourseSopLogsList',data,'GET');
+ }
+ 
+ 
+ export function getCourseSopLogsDetail(data) {
+ 	 return request('/app/qwSop/getCourseSopLogsDetail',data,'GET');
+ }
+ 
+ 
+ 
+ 
+ export function updateCourseSopLogs(data) {
+ 	 return request('/app/qwSop/updateCourseSopLogs',data,'POST','application/json;charset=UTF-8');
+ }

+ 25 - 0
api/crm.js

@@ -0,0 +1,25 @@
+import Request from '../common/request.js';
+let request = new Request().http
+
+
+ export function getLineCustomerList(data) {
+ 	 return request('/app/crmCustomer/getLineCustomerList',data,'GET');
+ }
+ 
+ 
+ export function getMyCustomerList(data) {
+ 	 return request('/app/crmCustomer/getMyCustomerList',data,'GET');
+ }
+ 
+ export function getCustomerDetails(data) {
+ 	 return request('/app/crmCustomer/getCustomerDetails',data,'GET');
+ }
+ 
+ export function editCrmCustomer(data) {
+ 	 return request('/app/crmCustomer/editCrmCustomer',data,'POST','application/json;charset=UTF-8');
+ }
+ 
+ export function addCrmCustomer(data) {
+ 	 return request('/app/crmCustomer/addCrmCustomer',data,'POST','application/json;charset=UTF-8');
+ }
+ 

+ 28 - 0
api/qw.js

@@ -0,0 +1,28 @@
+import Request from '../common/request.js';
+let request = new Request().http
+
+
+ export function getConfigSignature(data) {
+ 	 return request('/qw/qwJsApi/getConfigSignature',data,'GET');
+ }
+ 
+ 
+ export function getQwSopList(data) {
+ 	 return request('/app/qwSop/getQwSopList',data,'GET');
+ }
+ 
+ 
+ export function getQwSopLogsList(data) {
+ 	 return request('/app/qwSop/getQwSopLogsList',data,'GET');
+ }
+ 
+ 
+ export function getQwSopLogsDetail(data) {
+ 	 return request('/app/qwSop/getQwSopLogsDetail',data,'GET');
+ }
+ 
+ 
+ 
+ export function updateQwSopLogs(data) {
+ 	 return request('/app/qwSop/updateQwSopLogs',data,'POST','application/json;charset=UTF-8');
+ }

+ 27 - 0
api/user.js

@@ -0,0 +1,27 @@
+import Request from '../common/request.js';
+let request = new Request().http
+
+ 
+ export function login(data) {
+ 	 return request('/app/user/login',data,'POST','application/json;charset=UTF-8');
+ }
+ export function checkLogin(data) {
+ 	 return request('/app/user/checkLogin',data,'POST','application/json;charset=UTF-8');
+ }
+ 
+ export function getCompanyUser(data) {
+ 	 return request('/app/user/getCompanyUser',data,'GET');
+ }
+ 
+ export function getUserInfoByUserId(data) {
+ 	 return request('/app/user/getUserInfoByUserId',data,'GET');
+ }
+ export function getAllUsers(data) {
+ 	 return request('/app/user/getAllUsers',data,'GET');
+ }
+ export function getUserInfo(data) {
+ 	 return request('/app/user/getUserInfo',data,'GET');
+ }
+ 
+ 
+ 

+ 156 - 0
assets/css/common.less

@@ -0,0 +1,156 @@
+  
+ page {
+   background: #f5f5f5;
+   height: 100%;
+ }
+ .container{
+   height: 100%;
+ } 
+  
+  /*单行文本溢出省略号*/
+  .one-t {
+    overflow: hidden;
+    white-space: nowrap;
+    text-overflow: ellipsis;
+    transition: all linear 0.2s;
+  }
+  
+  /*多行文本溢出省略号*/
+  .more-t {
+    overflow: hidden;
+    text-overflow: ellipsis;
+    display: -webkit-box;
+    -webkit-line-clamp: 2;
+    -webkit-box-orient: vertical;
+    transition: all linear 0.2s;
+  }
+  
+  /* ==================
+            flex布局(colorui里面也有相关基础样式)
+   ==================== */
+  /* x水平排列*/
+  .x-f {
+    display: flex;
+    align-items: center;
+  }
+  
+  /*x两端且水平居中*/
+  .x-bc {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+  }
+  
+  /*x平分且水平居中*/
+  .x-ac {
+    display: flex;
+    justify-content: space-around;
+    align-items: center;
+  }
+  
+  /*x水平靠上对齐*/
+  .x-start {
+    display: flex;
+    align-items: flex-start;
+  }
+  
+  /*x水平靠下对齐*/
+  .x-end {
+    display: flex;
+    align-items: flex-end;
+  }
+  
+  /*上下左右居中*/
+  .x-c {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+  }
+  
+  /*y竖直靠左*/
+  .y-start {
+    display: flex;
+    flex-direction: column;
+    align-items: flex-start;
+  }
+  
+  /*y竖直靠右*/
+  .y-end {
+    display: flex;
+    flex-direction: column;
+    align-items: flex-end;
+  }
+  
+  /*y竖直居中*/
+  .y-f {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+  }
+  
+  // y竖直两端
+  .y-b {
+    display: flex;
+    flex-direction: column;
+    justify-content: space-between;
+  }
+  
+  /*y竖直两端居中*/
+  .y-bc {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: space-between;
+  }
+ /* layout */
+ .acea-row {
+   display: flex;
+   flex-wrap: wrap;
+   /* 辅助类 */
+ }
+ .acea-row.row-middle {
+   align-items: center;
+ }
+ .acea-row.row-top {
+   align-items: flex-start;
+ }
+ .acea-row.row-bottom {
+   align-items: flex-end;
+ }
+ .acea-row.row-center {
+   justify-content: center;
+ }
+ .acea-row.row-right {
+   justify-content: flex-end;
+ }
+ .acea-row.row-left {
+   justify-content: flex-start;
+ }
+ .acea-row.row-between {
+   justify-content: space-between;
+ }
+ .acea-row.row-around {
+   justify-content: space-around;
+ }
+ .acea-row.row-column-around {
+   flex-direction: column;
+   justify-content: space-around;
+ }
+ .acea-row.row-column {
+   flex-direction: column;
+ }
+ .acea-row.row-column-between {
+   flex-direction: column;
+   justify-content: space-between;
+ }
+ /* 上下左右垂直居中 */
+ .acea-row.row-center-wrapper {
+   align-items: center;
+   justify-content: center;
+ }
+ /* 上下两边居中对齐 */
+ .acea-row.row-between-wrapper {
+   align-items: center;
+   justify-content: space-between;
+ }
+  

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 2 - 0
assets/iconfont/iconfont.css


+ 9 - 0
common/config.js

@@ -0,0 +1,9 @@
+/**
+ * 本项目只有这里修改相应配置信息
+ */
+
+const config={};
+export default{
+	config:config,
+ 
+}

+ 74 - 0
common/request.js

@@ -0,0 +1,74 @@
+// uni-app请求封装
+export default class Request {
+	http(router, data = {}, method,contentType) {
+		let that = this;
+		let path = 'http://42.194.245.189:8007';
+		uni.setStorageSync('requestPath',path)
+		// uni.showLoading({
+		// 	title: '加载中'
+		// });
+		return new Promise((resolve, reject) => {
+			let token = uni.getStorageSync('AppToken');
+			console.log(1111)
+			console.log(token)
+			var httpContentType='application/x-www-form-urlencoded';
+			if(contentType!=undefined){
+				//application/json;charset=UTF-8
+				httpContentType=contentType;
+			}
+			var routers=router;
+			// 请求
+			uni.request({
+				header: {
+					// 'Content-Type': 'application/x-www-form-urlencoded',
+					'Content-Type': httpContentType,
+					'AppToken': token
+				},
+				url: `${path}${router}`,
+				data: data,
+				method: method,
+				success: (res) => {
+					//收到开发者服务器成功返回的回调函数
+					if(res.data.code==401){//没有权限直接退出到登录界面
+						let pages = getCurrentPages();
+						let url = pages[ pages.length - 1]; //当前页页面实例
+						console.log(url)
+						//如果登录界面已打开,自动关闭
+						if(url!=undefined&&url.route=="pages/auth/login"){
+							resolve(res.data)
+							return;
+						}
+						uni.reLaunch({
+							url:'/pages/auth/login',
+							success: () => {
+								uni.hideLoading();
+								
+							},
+							fail: () => {
+								uni.hideLoading();
+							}
+						})
+						return;
+					}
+					if (res.token) {
+						uni.setStorageSync('AppToken',res.token)
+					}
+					resolve(res.data)
+				},
+				fail:(res) =>{
+					//接口调用失败的回调函数
+				},
+				complete:(res) =>{
+					//接口调用结束的回调函数(调用成功、失败都会执行)
+					if (res.data.code == 401) {
+						return false
+					}
+					uni.hideLoading();
+				}
+				
+			})
+		})
+		 
+	}
+	
+}

+ 23 - 0
index.html

@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <script>
+      var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||
+        CSS.supports('top: constant(a)'))
+      document.write(
+        '<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
+        (coverSupport ? ', viewport-fit=cover' : '') + '" />')
+    </script>
+	<script src="https://res.wx.qq.com/open/js/jweixin-1.2.0.js"></script>
+	<script src="https://open.work.weixin.qq.com/wwopen/js/jwxwork-1.0.0.js"></script>
+    <title></title>
+    <!--preload-links-->
+    <!--app-context-->
+	<link rel="stylesheet" href="<%= BASE_URL %>static/index.css" />
+  </head>
+  <body>
+    <div id="app"><!--app-html--></div>
+    <script type="module" src="/main.js"></script>
+  </body>
+</html>

+ 35 - 0
main.js

@@ -0,0 +1,35 @@
+import App from './App'
+
+// #ifndef VUE3
+import Vue from 'vue'
+Vue.config.productionTip = false
+import uView from '@/uni_modules/uview-ui'
+Vue.use(uView)
+// uni.$u.config.unit = 'rpx'
+
+import utils from './utils/common.js'
+Vue.prototype.utils = utils;
+import {setData} from './utils/common.js'
+Vue.prototype.setData = setData;
+
+
+//import router from '@/router/router.js'  
+//import {RouterMount} from 'uni-simple-router'
+
+App.mpType = 'app'
+const app = new Vue({
+    ...App
+})
+//RouterMount(app,router,'#app')
+app.$mount()
+// #endif
+
+// #ifdef VUE3
+import { createSSRApp } from 'vue'
+export function createApp() {
+  const app = createSSRApp(App)
+  return {
+    app
+  }
+}
+// #endif

+ 145 - 0
manifest.json

@@ -0,0 +1,145 @@
+{
+    "name" : "AI客服",
+    "appid" : "__UNI__8A0233D",
+    "description" : "",
+    "versionName" : "1.0.8",
+    "versionCode" : 108,
+    "transformPx" : false,
+    /* 5+App特有相关 */
+    "app-plus" : {
+        "usingComponents" : true,
+        "nvueStyleCompiler" : "uni-app",
+        "compilerVersion" : 3,
+        "splashscreen" : {
+            "alwaysShowBeforeRender" : false,
+            "waiting" : false,
+            "autoclose" : true,
+            "delay" : 0
+        },
+        "compatible" : {
+            "ignoreVersion" : true //true表示忽略版本检查提示框,HBuilderX1.9.0及以上版本支持  
+        },
+        /* 模块配置 */
+        "modules" : {
+            "Geolocation" : {},
+            "Maps" : {},
+            "Contacts" : {}
+        },
+        /* 应用发布信息 */
+        "distribute" : {
+            /* android打包配置 */
+            "android" : {
+                "permissions" : [
+                    "<uses-feature android:name=\"android.hardware.camera\"/>",
+                    "<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
+                    "<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.CALL_PHONE\"/>",
+                    "<uses-permission android:name=\"android.permission.CAMERA\"/>",
+                    "<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
+                    "<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
+                    "<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
+                    "<uses-permission android:name=\"android.permission.READ_CONTACTS\"/>",
+                    "<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
+                    "<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.VIBRATE\"/>",
+                    "<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
+                    "<uses-permission android:name=\"android.permission.WRITE_CONTACTS\"/>",
+                    "<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
+                ],
+                "abiFilters" : [ "armeabi-v7a", "arm64-v8a" ]
+            },
+            /* ios打包配置 */
+            "ios" : {
+                "dSYMs" : false
+            },
+            /* SDK配置 */
+            "sdkConfigs" : {
+                "maps" : {
+                    "amap" : {
+                        "appkey_ios" : "A",
+                        "appkey_android" : "ba8f07ed6ffdb1cacda26c0545a49d9a"
+                    }
+                },
+                "ad" : {},
+                "geolocation" : {}
+            },
+            "splashscreen" : {
+                "androidStyle" : "default",
+                "useOriginalMsgbox" : true
+            },
+            "icons" : {
+                "android" : {
+                    "hdpi" : "unpackage/res/icons/72x72.png",
+                    "xhdpi" : "unpackage/res/icons/96x96.png",
+                    "xxhdpi" : "unpackage/res/icons/144x144.png",
+                    "xxxhdpi" : "unpackage/res/icons/192x192.png"
+                },
+                "ios" : {
+                    "appstore" : "unpackage/res/icons/1024x1024.png",
+                    "ipad" : {
+                        "app" : "unpackage/res/icons/76x76.png",
+                        "app@2x" : "unpackage/res/icons/152x152.png",
+                        "notification" : "unpackage/res/icons/20x20.png",
+                        "notification@2x" : "unpackage/res/icons/40x40.png",
+                        "proapp@2x" : "unpackage/res/icons/167x167.png",
+                        "settings" : "unpackage/res/icons/29x29.png",
+                        "settings@2x" : "unpackage/res/icons/58x58.png",
+                        "spotlight" : "unpackage/res/icons/40x40.png",
+                        "spotlight@2x" : "unpackage/res/icons/80x80.png"
+                    },
+                    "iphone" : {
+                        "app@2x" : "unpackage/res/icons/120x120.png",
+                        "app@3x" : "unpackage/res/icons/180x180.png",
+                        "notification@2x" : "unpackage/res/icons/40x40.png",
+                        "notification@3x" : "unpackage/res/icons/60x60.png",
+                        "settings@2x" : "unpackage/res/icons/58x58.png",
+                        "settings@3x" : "unpackage/res/icons/87x87.png",
+                        "spotlight@2x" : "unpackage/res/icons/80x80.png",
+                        "spotlight@3x" : "unpackage/res/icons/120x120.png"
+                    }
+                }
+            }
+        },
+        "nativePlugins" : {}
+    },
+    /* 快应用特有相关 */
+    "quickapp" : {},
+    /* 小程序特有相关 */
+    "mp-weixin" : {
+        "appid" : "wxece08028880b35e1",
+        "setting" : {
+            "urlCheck" : true
+        },
+        "usingComponents" : true,
+        "permission" : {
+            "scope.userLocation" : {
+                "desc" : "获取您的位置信息为了更好的推荐服务"
+            }
+        },
+        "requiredPrivateInfos" : [ "getLocation", "chooseLocation" ]
+    },
+    "mp-alipay" : {
+        "usingComponents" : true
+    },
+    "mp-baidu" : {
+        "usingComponents" : true
+    },
+    "mp-toutiao" : {
+        "usingComponents" : true
+    },
+    "uniStatistics" : {
+        "enable" : false
+    },
+    "vueVersion" : "2",
+    "locale" : "zh-Hans",
+    "fallbackLocale" : "zh-Hans",
+    "h5" : {
+        "router" : {
+            "mode" : "hash",
+            "base" : "./"
+        }
+    }
+}

+ 0 - 0
note.txt


+ 74 - 0
package-lock.json

@@ -0,0 +1,74 @@
+{
+  "name": "stoer",
+  "version": "1.0.0",
+  "lockfileVersion": 1,
+  "requires": true,
+  "dependencies": {
+    "decode-uri-component": {
+      "version": "0.2.2",
+      "resolved": "https://registry.npmmirror.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz",
+      "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ=="
+    },
+    "filter-obj": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmmirror.com/filter-obj/-/filter-obj-1.1.0.tgz",
+      "integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ=="
+    },
+    "klona": {
+      "version": "2.0.6",
+      "resolved": "https://registry.npmmirror.com/klona/-/klona-2.0.6.tgz",
+      "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==",
+      "dev": true
+    },
+    "neo-async": {
+      "version": "2.6.2",
+      "resolved": "https://registry.npmmirror.com/neo-async/-/neo-async-2.6.2.tgz",
+      "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
+      "dev": true
+    },
+    "query-string": {
+      "version": "6.14.1",
+      "resolved": "https://registry.npmmirror.com/query-string/-/query-string-6.14.1.tgz",
+      "integrity": "sha512-XDxAeVmpfu1/6IjyT/gXHOl+S0vQ9owggJ30hhWKdHAsNPOcasn5o9BW0eejZqL2e4vMjhAxoW3jVHcD6mbcYw==",
+      "requires": {
+        "decode-uri-component": "^0.2.0",
+        "filter-obj": "^1.1.0",
+        "split-on-first": "^1.0.0",
+        "strict-uri-encode": "^2.0.0"
+      }
+    },
+    "sass-loader": {
+      "version": "13.2.0",
+      "resolved": "https://registry.npmmirror.com/sass-loader/-/sass-loader-13.2.0.tgz",
+      "integrity": "sha512-JWEp48djQA4nbZxmgC02/Wh0eroSUutulROUusYJO9P9zltRbNN80JCBHqRGzjd4cmZCa/r88xgfkjGD0TXsHg==",
+      "dev": true,
+      "requires": {
+        "klona": "^2.0.4",
+        "neo-async": "^2.6.2"
+      }
+    },
+    "split-on-first": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmmirror.com/split-on-first/-/split-on-first-1.1.0.tgz",
+      "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw=="
+    },
+    "strict-uri-encode": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmmirror.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz",
+      "integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ=="
+    },
+    "uni-read-pages": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmmirror.com/uni-read-pages/-/uni-read-pages-1.0.5.tgz",
+      "integrity": "sha512-GkrrZ0LX0vn9R5k6RKEi0Ez3Q3e2vUpjXQ8Z6/K/d28KudI9ajqgt8WEjQFlG5EPm1K6uTArN8LlqmZTEixDUA=="
+    },
+    "uni-simple-router": {
+      "version": "1.5.5",
+      "resolved": "https://registry.npmmirror.com/uni-simple-router/-/uni-simple-router-1.5.5.tgz",
+      "integrity": "sha512-VjBnwhvmWYHVNsj2zcPjYBwb9TqG7miR87qLBBLI4gHOnJVYmCyjZK/bj06f9slvTMbWXrze7LJ9/Hi/8DB0ag==",
+      "requires": {
+        "query-string": "^6.12.1"
+      }
+    }
+  }
+}

+ 24 - 0
package.json

@@ -0,0 +1,24 @@
+{
+  "name": "stoer",
+  "version": "1.0.0",
+  "description": " ",
+  "main": " ",
+  "scripts": {
+    "test": " "
+  },
+  "repository": {
+    "type": "",
+    "url": " "
+  },
+  "author": "",
+  "license": "",
+  "keywords": [],
+  "dependencies": {
+    "version": "1.11.7",
+    "uni-read-pages": "^1.0.5",
+    "uni-simple-router": "^1.5.5"
+  },
+  "devDependencies": {
+    "sass-loader": "^13.2.0"
+  }
+}

+ 298 - 0
pages.json

@@ -0,0 +1,298 @@
+{
+	"easycom": {
+		"^u-(.*)": "@/uni_modules/uview-ui/components/u-$1/u-$1.vue"
+	},
+	"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
+		{
+			"path": "pages/common/launch",
+			"style": {
+				"navigationBarBackgroundColor":"#ffffff",
+				"navigationBarTitleText": ""
+			}
+		},
+		{
+			"path": "pages/auth/login",
+			"style": {
+				"navigationBarBackgroundColor": "#ffffff",
+				"navigationBarTitleText": "",
+				"app-plus":{
+					"titleNView":{
+						"autoBackButton":false 
+					}
+					
+				}
+				
+			}
+		},
+		{
+			"path": "pages/index/index",
+			"style": {
+				"navigationBarBackgroundColor": "#ffffff",
+				"navigationBarTextStyle": "black",
+				"navigationBarTitleText": "首页",
+                "enablePullDownRefresh": false
+			}
+		}
+        ,{
+            "path" : "pages/user/index",
+            "style" :                                                                                    
+            {
+				"navigationBarTextStyle": "white",
+                "navigationBarTitleText": "我的",
+                "enablePullDownRefresh": false
+            }
+            
+        }
+        ,{
+            "path" : "pages/customer/index",
+            "style" :                                                                                    
+            {
+				"navigationBarBackgroundColor": "#ffffff",
+				"navigationBarTextStyle": "black",
+                "navigationBarTitleText": "客户",
+                "enablePullDownRefresh": false
+            }
+            
+        }
+		,{
+		    "path" : "pages/msg/index",
+		    "style" :                                                                                    
+		    {
+				"navigationBarBackgroundColor": "#ffffff",
+				"navigationBarTextStyle": "black",
+		        "navigationBarTitleText": "消息",
+		        "enablePullDownRefresh": false
+		    }
+		    
+		}
+		,{
+		    "path" : "pages/user/ai/index",
+		    "style" :                                                                                    
+		    {
+				"navigationBarBackgroundColor": "#ffffff",
+				"navigationBarTextStyle": "black",
+		        "navigationBarTitleText": "AI智能客服",
+		        "enablePullDownRefresh": false
+		    }
+		    
+		}
+		,{
+		    "path" : "pages/user/about",
+		    "style" :                                                                                    
+		    {
+				"navigationBarBackgroundColor": "#ffffff",
+				"navigationBarTextStyle": "black",
+		        "navigationBarTitleText": "关于我们",
+		        "enablePullDownRefresh": false
+		    }
+		    
+		}
+		,{
+		    "path" : "pages/user/editPwd",
+		    "style" :                                                                                    
+		    {
+				"navigationBarBackgroundColor": "#ffffff",
+				"navigationBarTextStyle": "black",
+		        "navigationBarTitleText": "修改密码",
+		        "enablePullDownRefresh": false
+		    }
+		    
+		}
+		,{
+		    "path" : "pages/user/editUser",
+		    "style" :                                                                                    
+		    {
+				"navigationBarBackgroundColor": "#ffffff",
+				"navigationBarTextStyle": "black",
+		        "navigationBarTitleText": "用户信息",
+		        "enablePullDownRefresh": false
+		    }
+		    
+		}
+		,{
+		    "path" : "pages/user/userInfo",
+		    "style" :                                                                                    
+		    {
+				"navigationBarBackgroundColor": "#ffffff",
+				"navigationBarTextStyle": "black",
+		        "navigationBarTitleText": "用户信息",
+		        "enablePullDownRefresh": false
+		    }
+		    
+		}
+		,{
+		    "path" : "pages/user/qwSop/sop",
+		    "style" :                                                                                    
+		    {
+				"navigationBarBackgroundColor": "#ffffff",
+				"navigationBarTextStyle": "black",
+		        "navigationBarTitleText": "群发列表",
+		        "enablePullDownRefresh": false
+		    }
+		    
+		}
+		,{
+		    "path" : "pages/user/qwSop/sopLosList",
+		    "style" :                                                                                    
+		    {
+				"navigationBarBackgroundColor": "#ffffff",
+				"navigationBarTextStyle": "black",
+		        "navigationBarTitleText": "群发记录",
+		        "enablePullDownRefresh": false
+		    }
+		    
+		}
+		,{
+		    "path" : "pages/user/qwSop/sopLogsDetails",
+		    "style" :                                                                                    
+		    {
+				"navigationBarBackgroundColor": "#ffffff",
+				"navigationBarTextStyle": "black",
+		        "navigationBarTitleText": "记录详情",
+		        "enablePullDownRefresh": false
+		    }
+		    
+		}
+		,{
+		    "path" : "pages/user/courseSop/sop",
+		    "style" :                                                                                    
+		    {
+				"navigationBarBackgroundColor": "#ffffff",
+				"navigationBarTextStyle": "black",
+		        "navigationBarTitleText": "群发列表",
+		        "enablePullDownRefresh": false
+		    }
+		    
+		}
+		,{
+		    "path" : "pages/user/courseSop/sopLosList",
+		    "style" :                                                                                    
+		    {
+				"navigationBarBackgroundColor": "#ffffff",
+				"navigationBarTextStyle": "black",
+		        "navigationBarTitleText": "群发记录",
+		        "enablePullDownRefresh": false
+		    }
+		    
+		}
+		,{
+		    "path" : "pages/user/courseSop/sopLogsDetails",
+		    "style" :                                                                                    
+		    {
+				"navigationBarBackgroundColor": "#ffffff",
+				"navigationBarTextStyle": "black",
+		        "navigationBarTitleText": "记录详情",
+		        "enablePullDownRefresh": false
+		    }
+		    
+		}
+		,{
+		    "path" : "pages/user/users/users",
+		    "style" :                                                                                    
+		    {
+				"navigationBarBackgroundColor": "#ffffff",
+				"navigationBarTextStyle": "black",
+		        "navigationBarTitleText": "员工列表",
+		        "enablePullDownRefresh": false
+		    }
+		    
+		}
+		,{
+		    "path" : "pages/user/users/userInfo",
+		    "style" :                                                                                    
+		    {
+				"navigationBarBackgroundColor": "#ffffff",
+				"navigationBarTextStyle": "black",
+		        "navigationBarTitleText": "员工详情",
+		        "enablePullDownRefresh": false
+		    }
+		    
+		}
+		,{
+		    "path" : "pages/user/crm/lineCustomer",
+		    "style" :                                                                                    
+		    {
+				"navigationBarBackgroundColor": "#ffffff",
+				"navigationBarTextStyle": "black",
+		        "navigationBarTitleText": "线索客户",
+		        "enablePullDownRefresh": false
+		    }
+		    
+		}
+		,{
+		    "path" : "pages/user/crm/myCustomer",
+		    "style" :                                                                                    
+		    {
+				"navigationBarBackgroundColor": "#ffffff",
+				"navigationBarTextStyle": "black",
+		        "navigationBarTitleText": "我的客户",
+		        "enablePullDownRefresh": false
+		    }
+		    
+		}
+		,{
+		    "path" : "pages/user/crm/customerDetails",
+		    "style" :                                                                                    
+		    {
+				"navigationBarBackgroundColor": "#ffffff",
+				"navigationBarTextStyle": "black",
+		        "navigationBarTitleText": "客户详情",
+		        "enablePullDownRefresh": false
+		    }
+		    
+		}
+		,{
+		    "path" : "pages/user/crm/importWxUser",
+		    "style" :                                                                                    
+		    {
+				"navigationBarBackgroundColor": "#ffffff",
+				"navigationBarTextStyle": "black",
+		        "navigationBarTitleText": "导入客户",
+		        "enablePullDownRefresh": false
+		    }
+		    
+		}
+		 
+    ],
+	"globalStyle": {
+		"navigationBarTitleText": "销售管理端",
+		"navigationBarBackgroundColor": "#115296",
+		"backgroundColor": "#ffffff"
+	},
+	"tabBar": {
+		"color": "#7e7e7e",
+		"selectedColor": "#115296",
+		"borderStyle": "white",
+		"backgroundColor": "#ffffff",
+		"height": "64px",
+		"fontSize":"12px",
+		"iconWidth":"18px",
+		"spacing": "4px",
+		"list": [
+			// {
+			// 	"pagePath": "pages/index/index",
+			// 	"iconPath": "/static/images/home.png",
+			// 	"selectedIconPath": "/static/images/home_select.png",
+			// 	"text": "首页"
+			// },
+			{
+				"pagePath": "pages/customer/index",
+				"iconPath": "/static/images/order.png",
+				"selectedIconPath": "/static/images/order_select.png",
+				"text": "客户"
+			},
+			{
+				"pagePath": "pages/msg/index",
+				"iconPath": "/static/images/report.png",
+				"selectedIconPath": "/static/images/report_select.png",
+				"text": "消息"
+			},
+			{
+				"pagePath": "pages/user/index",
+				"iconPath": "/static/images/my.png",
+				"selectedIconPath": "/static/images/my_select.png",
+				"text": "我的"
+			}
+		]
+	}
+}

+ 216 - 0
pages/auth/login.vue

@@ -0,0 +1,216 @@
+<template>
+	<view>
+		 <view class="content">
+			 <view class="login">
+				<view class="head">
+					 <image src="/static/logo.png" ></image>
+					 <p class="title">销售管理端</p>
+					 <p class="desc">客户沟通更智能</p>
+				</view>
+				<view class="login">
+					 <view class="login-item">
+						 <!-- <text>帐号</text> -->
+						 <view class="input-account">
+							 <input v-model="account" placeholder="请输入帐号" type="text" ></input>
+						 </view>
+					 </view> 
+					 <view class="login-item">
+						 <!-- <text>密码</text> -->
+						 <view class="input-pwd">
+							 <input v-model="password"  placeholder="请输入密码" type="password" ></input>
+						 </view>
+					 </view> 
+					 <view class="btns">
+						 <view class="login-btn" @click="login">登录</view>
+					 </view>
+				</view>
+			 </view>
+		 </view>
+		 <view class="footer">
+			 <p>版权所有 @2024</p>
+		 </view>
+		 
+	</view>
+</template>
+
+<script>
+
+import { login } from '@/api/user.js'
+export default {
+	data() {
+		return {
+			account:"",
+			password:"",
+		}
+	},
+	onLoad(option) 
+	{
+	},
+	onUnload() {
+	},
+	mounted() {
+
+	},
+	methods: {
+	    login(){
+			if (this.utils.isEmpty(this.account)) {
+				uni.showToast({
+					title: "请输入帐号",
+					icon: 'none',
+				});
+				return
+			}
+			if (this.utils.isEmpty(this.password)) {
+				uni.showToast({
+					title: "请输入密码",
+					icon: 'none',
+				});
+				return
+			}
+			var data = {
+				account:this.account,
+				password: this.password,
+			};
+			var that=this;
+			uni.showLoading({
+				title:"处理中..."
+			})
+			login(data).then(
+				res => {
+					uni.hideLoading()
+					if(res.code==200){
+						uni.setStorageSync('AppToken',res.data.token);
+						uni.setStorageSync('companyUserId',res.data.user.userId);
+						uni.$emit('initSocket');
+						uni.reLaunch({
+							url: '../user/index',
+							animationType: 'pop-in',
+							animationDuration: 100
+						})
+					}
+					else{
+						uni.showToast({
+							title: res.msg,
+							icon: 'none'
+						})
+					}
+					
+				},
+				rej => {}
+			);
+	   }
+	 
+	},
+  
+}
+</script>
+
+<style lang="scss" scoped>
+page{
+ 	background-color: #ffffff;
+}
+.content{
+	 background-color: #ffffff;
+	 display: flex;
+	 flex-direction: column;
+	 align-items: center;
+	 justify-content: center;
+	 height: calc(100vh);
+	 width: 100%;
+	 .login{
+		 width: 100%;
+		 padding-bottom: 50rpx;
+		.head{
+			text-align: center;
+			image{
+				padding: 20rpx;
+				width: 120rpx;
+				height: 120rpx;
+				border-radius: 10rpx;
+				box-shadow:0px 0px 20rpx rgba(0,0,0,0.2);
+			}
+			.title{
+			 	color: #141414;
+			 	margin:50upx 0upx 30upx 0rpx;
+			 	font-size: 38rpx;
+				font-weight: 500;
+			 }
+			 .desc{
+			 	color: #686866;
+			 	padding:0 0 30rpx 0rpx;
+			 	font-size: 28rpx;
+			 }
+		}
+		.login{
+			width: 100%;
+		 	padding: 30rpx 50rpx;
+			.login-item{
+				margin-bottom: 30rpx;
+				text-align: left;
+				.input-account{
+					margin-top: 20rpx;
+					margin-bottom: 20rpx;
+					border-radius:40rpx;
+					border:solid 1rpx #e4e4e4;
+					height: 80rpx;	
+					width: 100%;
+					background:url(../../static/account.png) no-repeat 0 center;
+					background-size: 30rpx 30rpx;
+					background-position: 30rpx;
+					input{
+						margin-left: 80rpx;
+						height: 80rpx;
+						line-height: 80rpx
+					}
+				 
+				}
+				.input-pwd{
+					margin-top: 20rpx;
+					margin-bottom: 20rpx;
+					border-radius:40rpx;
+					border:solid 1rpx #e4e4e4;
+					height: 80rpx;	
+					width: 100%;
+					background:url(../../static/password.png) no-repeat 0 center;
+					background-size: 30rpx 30rpx;
+					background-position: 30rpx;
+					input{
+						margin-left: 80rpx;
+						height: 80rpx;
+						line-height: 80rpx
+					}
+				}
+			}
+			.btns{
+				margin: 60rpx 0rpx;
+				.login-btn {
+					display: flex;
+					align-items: center;
+					justify-content: center;
+					width: 100%;
+					height: 80rpx;
+					background: linear-gradient(to right, #115296 0%, #115296 100%);
+					background: -moz-linear-gradient(to right, #115296 0%, #115296 100%);
+					box-shadow: 0px 7rpx 6rpx 0px rgba(229, 138, 0, 0.22);
+					border-radius: 40rpx;
+					font-size: 30rpx;
+					font-family: PingFang SC;
+					font-weight: 500;
+					color: rgba(255, 255, 255, 1);
+				}
+			}
+			
+		}
+		 
+	}
+}
+.footer{
+	color: #686866;
+	width: 100%;
+	position: fixed;
+	text-align: center;
+	bottom: 60upx;
+	font-size: 28rpx;
+}
+
+</style>

+ 123 - 0
pages/common/launch.vue

@@ -0,0 +1,123 @@
+<template>
+	<view class="content">
+		 
+	</view>
+</template>
+
+<script>
+	import { getConfigSignature } from '@/api/qw.js'
+	export default {
+		data() {
+			return {
+				loading: null,
+				outUserId: null,
+				qwUserId: null,
+				currentPath: null,
+				baseUrl:uni.getStorageSync('requestPath')
+				
+			};
+		},
+		onShow() {
+			// this.getUserId();
+			this.navigatHandler();
+		},
+		methods: {
+			navigatHandler() {
+				if (!this.utils.isLogin()) {
+					uni.reLaunch({
+						url: '../auth/login',
+					})
+				} else {
+					uni.reLaunch({
+						url: '../user/index',
+					})
+				}
+			},
+			getUserId() {
+				const urlParams = new URLSearchParams(window.location.search);
+				const currentUrl = window.location.href.split('#')[0];
+				const companyId = urlParams.get('companyId');
+				const code = urlParams.get('code');
+				var data={
+					companyId: companyId,
+					url: currentUrl,
+					code: code
+				}
+				getConfigSignature(data).then(
+					res => {
+						uni.hideLoading()
+						if(res.code==200){
+							const agentConfigSignature = res.config.agentConfigSignature;
+							const configSignature = res.config.configSignature;
+							const corpId = res.config.corpId;
+							const nonceStr = res.config.nonceStr;
+							const timestamp = res.config.timestamp;
+							const agentId = res.config.agentId;
+							this.qwUserId = res.config.userid;
+							wx.config({
+								beta: true,
+								debug: false,
+								appId: corpId,
+								timestamp: timestamp,
+								nonceStr: nonceStr,
+								signature: configSignature,
+								jsApiList: ['getCurExternalContact']
+							});
+							wx.ready(() => {
+								wx.agentConfig({
+									corpid: corpId,
+									agentid: agentId,
+									timestamp: timestamp,
+									nonceStr: nonceStr,
+									signature: agentConfigSignature,
+									jsApiList: ['getCurExternalContact'],
+									success: (res) => {
+										wx.invoke('getCurExternalContact', {}, async (res) => {
+											if (res.err_msg === "getCurExternalContact:ok") {
+												this.outUserId = res.userId;
+												uni.showToast({
+													title: res.userId,
+													icon: 'none'
+												})
+											} else {
+												// 错误处理
+											}
+										});
+									},
+									fail: (res) => {
+										console.error('agentConfig fail:', res);
+									}
+								});
+							});
+							wx.error((res) => {
+								console.error('wx.error:', res);
+							});
+						}
+						else{
+							uni.showToast({
+								title: res.msg,
+								icon: 'none'
+							})
+						}
+						
+					},
+					rej => {}
+				);
+				 
+			}
+		}
+ 
+	};
+ 
+</script>
+<style>
+	page{
+		background-color: #fff;
+	}
+	.content {
+		background-color: #fff;
+		height: 100%;
+		width: 100%;
+		position: relative;
+	}
+</style>

+ 193 - 0
pages/customer/index.vue

@@ -0,0 +1,193 @@
+<template>
+	<view class="content">
+		<view class="top">
+			<u-search placeholder="输入名称搜索" v-model="searchKey" @custom="search()" @search="search()"></u-search>
+		</view>
+		<mescroll-body top="88rpx" bottom="0" ref="mescrollRef" @init="mescrollInit"    :down="downOption" :up="upOption" @down="downCallback" @up="upCallback">
+		<view class="customer-list">
+			<view class="customer-item"  v-for="(item) in dataList"  @click="navTo('/pages/user/crm/customerDetails?customerId='+item.customerId)" >
+				<view class="name-box">
+					<view class="name">{{item.customerName}}</view>
+					<view class="btns">
+						<image class="btn" src="/static/images/sms.png"></image>
+						<image class="btn" src="/static/images/phone.png"></image>
+					</view>
+				</view>
+				<view class="desc-box">
+					<view class="label">最后跟进:</view>
+					<view class="value">{{item.startTime}}</view>
+				</view>
+				<view class="desc-box">
+					<view class="label">所在地区:</view>
+					<view class="value">{{item.address}}</view>
+				</view>
+				<view class="desc-box">
+					<view class="label">备&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;注:</view>
+					<view class="value">{{item.remark}}</view>
+				</view>
+				 
+			</view>
+		</view>
+		</mescroll-body>
+	</view>
+</template>
+
+<script>
+	import MescrollMixin from "@/uni_modules/mescroll-uni/components/mescroll-uni/mescroll-mixins.js";
+	
+	import {getMyCustomerList} from '@/api/crm.js'
+	export default {
+		mixins: [MescrollMixin], // 使用mixin
+		data() {
+			return {
+				searchKey:"",
+				mescroll:null,
+				downOption: {
+					auto:false//不要自动加载
+				},
+				upOption: {
+					onScroll:false,
+					use: true, // 是否启用上拉加载; 默认true
+					page: {
+						num: 0, // 当前页码,默认0,回调之前会加1,即callback(page)会从1开始
+						size: 10 // 每页数据的数量,默认10
+					},
+					noMoreSize: 10, // 配置列表的总数量要大于等于5条才显示'-- END --'的提示
+					textNoMore:"已经到底了",
+					empty: {
+						icon:'/static/images/empty.png',
+						tip: '暂无数据'
+					}
+				},
+				dataList: []
+			}
+		},
+		onShow() {
+		},
+		methods: {
+			search(){
+				this.mescroll.resetUpScroll()
+			}, 
+			navTo(url){
+				uni.navigateTo({
+					url: url
+				})
+			},
+			mescrollInit(mescroll) {
+				this.mescroll = mescroll;
+			},
+			/*下拉刷新的回调 */
+			downCallback(mescroll) {
+				mescroll.resetUpScroll()
+			},
+			upCallback(page) {
+				//联网加载数据
+				var that = this;
+				var data={
+					customerName:this.searchKey,
+					pageNum: page.num,
+					pageSize: page.size
+				};
+				uni.showLoading({
+					title:"加载中..."
+				})
+				getMyCustomerList(data).then(res => {
+					uni.hideLoading()
+					if(res.code==200){
+						//设置列表数据
+						if (page.num == 1) {
+							that.dataList = res.data.list; 
+							
+						} else {
+							that.dataList = that.dataList.concat(res.data.list);
+							 
+						}
+						that.mescroll.endBySize(res.data.list.length, res.data.total);
+						
+					}else{
+						uni.showToast({
+							icon:'none',
+							title: "请求失败",
+						});
+						that.dataList = null;
+						that.mescroll.endErr();
+					}
+				});
+			},
+		}
+	}
+</script>
+
+<style lang="scss">
+	page{
+		
+		height: 100%;
+		background: #f6f6f6;
+	}
+</style>
+<style scoped lang="scss">
+	.content{
+		height: 100%;
+		padding: 0rpx;
+		position: relative;
+		.top{
+			z-index: 10000;
+			width: 100%;
+			position: absolute;
+			top:0rpx;
+			left:0rpx;
+			height: 88rpx;
+			background-color: #fff;
+			padding: 0rpx 15rpx;
+		}
+		.customer-list{
+			display: flex;
+			flex-direction: column;
+			padding: 15rpx;
+			.customer-item{
+				padding: 15rpx;
+				border-radius: 15rpx;
+				background-color: #fff;
+				margin-bottom: 15rpx;
+				width: 100%;
+				.name-box{
+					width: 100%;
+					display: flex;
+					align-items: center;
+					justify-content: flex-start;
+					.name{
+						flex: 1;
+						font-size: 38rpx;
+						color:#111;
+					}
+					.btns{
+						.btn{
+							margin-left: 10rpx;
+							width: 30rpx;
+							height:30rpx;
+						}
+					}
+				}
+				.desc-box{
+					margin-top: 15rpx;
+					width: 100%;
+					display: flex;
+					align-items: center;
+					justify-content: flex-start;
+					.label{
+						font-size: 28rpx;
+						color: #a8a8a8;
+					}
+					.value{
+						font-size: 28rpx;
+						color: #a8a8a8;
+					}
+					
+					
+				}
+			}
+		}
+		
+	}
+	 
+</style>

+ 22 - 0
pages/index/index.vue

@@ -0,0 +1,22 @@
+<template>
+	<view>
+		
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				
+			}
+		},
+		methods: {
+			
+		}
+	}
+</script>
+
+<style>
+
+</style>

+ 22 - 0
pages/msg/index.vue

@@ -0,0 +1,22 @@
+<template>
+	<view>
+		
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				
+			}
+		},
+		methods: {
+			
+		}
+	}
+</script>
+
+<style>
+
+</style>

+ 116 - 0
pages/user/about.vue

@@ -0,0 +1,116 @@
+<template>
+	<view class="content">
+		<view class="logo">
+			<image src="/static/logo.png"></image>
+			<p>企微聊天工具</p>
+		</view>
+		<view class="set-box">
+			<view class="item" @click="callPhone()">
+				<view class="left">
+					<text class="text">联系我们</text>
+				</view>
+				<image class="right" src="/static/images/right_arrow.png" mode="aspectFill"></image>
+			</view>
+			<view style="height: 1px;background-color: #F7F7F7;"></view>
+			<view class="item">
+				<view class="left">
+					<text class="text">版本号</text>
+				</view>
+				<view class="right-text">
+					<text class="text">v{{version}}</text>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+ 
+ export default {
+ 	data() {
+ 		return {
+			tel:undefined,
+			version:"1.0"
+ 		}
+ 	},
+	onLoad() {
+		var that=this;
+		// that.version = widgetinfo.version
+		
+	},
+ 	methods: {
+		callPhone(){
+			uni.makePhoneCall({
+				phoneNumber: "4000717770"
+			})
+		},
+ 		submit(){
+ 			
+ 		}
+ 	}
+ }
+ 
+ 
+</script>
+
+
+<style scoped lang="scss">
+page{
+	height: 100%;
+	background-color: #f5f5f5;
+}
+.content{
+	height: 100%;
+}
+.logo{
+	padding-top: 15%;
+	text-align: center;
+	
+	image{
+		padding: 15rpx;
+		width: 120rpx;
+		height: 120rpx;
+		border-radius: 10rpx;
+		box-shadow:0px 0px 20rpx rgba(0,0,0,0.2);
+	}
+	p{
+		margin: 10px 0px;
+		font-size: 14px;
+	}
+}
+.set-box{
+	margin-top: 30upx;
+	background: #fff;
+	padding: 0 40upx;
+	.item{
+		position: relative;
+		display: flex;
+		align-items: center;
+		justify-content: space-between;
+		padding: 25upx 0;
+		.left{
+			display: flex;
+			align-items: center;
+			.text{
+				font-size: 28upx;
+				color: #666;
+			}
+		}
+		.right{
+			width: 10upx;
+			height: 20upx;
+		 
+		}
+		.right-text{
+			 
+		}
+	}
+}
+.contact-btn {
+	position: absolute;
+	width: 100%;
+	display: flex;
+	opacity: 0;
+}
+
+</style>

+ 43 - 0
pages/user/ai/index.vue

@@ -0,0 +1,43 @@
+<template>
+	<view class="content">
+		<web-view  v-if="webUrl!=null" :src="webUrl"></web-view>
+	</view>
+</template>
+<script>
+import {getConfigByKey} from '@/api/common'
+export default {
+	components: {
	},
+ 	data() {
+ 		return {
+			webUrl:null,
+ 		}
+ 	},
+	onLoad(options) {
+		var companyUserId=uni.getStorageSync('companyUserId');
+		var that=this;
+		setTimeout(function(){
+			that.webUrl="https://ai.cdwjyyh.com/#/pages/ai/conversation?userType=3&userId="+companyUserId+"&companyUserId="+companyUserId
+			console.log(that.webUrl);
+		},500)
+		
+	},
+	methods:{
+		 
+	}
+ 	 
+ }
+ 
+ 
+</script>
+
+
+<style scoped lang="scss">
+page{
+	height: 100%;
+}
+.content{
+	height: 100%;
+	padding: 30rpx;
+}
+ 
+</style>

+ 168 - 0
pages/user/courseSop/sop.vue

@@ -0,0 +1,168 @@
+<template>
+	<view class="content">
+		<mescroll-body top="0rpx" bottom="0" ref="mescrollRef" @init="mescrollInit"    :down="downOption" :up="upOption" @down="downCallback" @up="upCallback">
+		<view class="sop-list">
+			<view class="sop-item"  v-for="(item) in dataList" @click.stop="navTo('/pages/user/courseSop/sopLosList?sopId='+item.id)"   >
+				<view class="name-box">
+					<view class="name">{{item.name}}</view>
+				</view>
+				<view class="desc-box">
+					<view class="label">创建时间:</view>
+					<view class="value">{{item.createTime}}</view>
+				</view>
+			</view>
+		</view>
+		</mescroll-body>
+	</view>
+</template>
+
+<script>
+	import MescrollMixin from "@/uni_modules/mescroll-uni/components/mescroll-uni/mescroll-mixins.js";
+	
+	import {getCourseSopList} from '@/api/course.js'
+	export default {
+		mixins: [MescrollMixin], // 使用mixin
+		data() {
+			return {
+				mescroll:null,
+				downOption: {
+					auto:false//不要自动加载
+				},
+				upOption: {
+					onScroll:false,
+					use: true, // 是否启用上拉加载; 默认true
+					page: {
+						num: 0, // 当前页码,默认0,回调之前会加1,即callback(page)会从1开始
+						size: 10 // 每页数据的数量,默认10
+					},
+					noMoreSize: 10, // 配置列表的总数量要大于等于5条才显示'-- END --'的提示
+					textNoMore:"已经到底了",
+					empty: {
+						icon:'/static/images/empty.png',
+						tip: '暂无数据'
+					}
+				},
+				dataList: []
+			}
+		},
+		onShow() {
+		},
+		methods: {
+			mescrollInit(mescroll) {
+				this.mescroll = mescroll;
+			},
+			/*下拉刷新的回调 */
+			downCallback(mescroll) {
+				mescroll.resetUpScroll()
+			},
+			upCallback(page) {
+				//联网加载数据
+				var that = this;
+				var data={
+					pageNum: page.num,
+					pageSize: page.size
+				};
+				uni.showLoading({
+					title:"加载中..."
+				})
+				getCourseSopList(data).then(res => {
+					uni.hideLoading()
+					if(res.code==200){
+						//设置列表数据
+						if (page.num == 1) {
+							that.dataList = res.data.list; 
+							
+						} else {
+							that.dataList = that.dataList.concat(res.data.list);
+							 
+						}
+						that.mescroll.endBySize(res.data.list.length, res.data.total);
+						
+					}else{
+						uni.showToast({
+							icon:'none',
+							title: "请求失败",
+						});
+						that.dataList = null;
+						that.mescroll.endErr();
+					}
+				});
+			},
+			navTo(url){
+				uni.navigateTo({
+					url: url
+				})
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	page{
+		
+		height: 100%;
+		background: #f6f6f6;
+	}
+</style>
+<style scoped lang="scss">
+	.content{
+		height: 100%;
+		padding: 0rpx;
+		.tabs{
+			z-index: 10000;
+			position: absolute;
+			top:0rpx;
+			left:0rpx;
+			height: 88rpx;
+			background-color: #fff;
+			width: 100%;
+		}
+		.sop-list{
+			display: flex;
+			flex-direction: column;
+			padding: 15rpx;
+			.sop-item{
+				padding: 15rpx;
+				border-radius: 15rpx;
+				background-color: #fff;
+				margin-bottom: 15rpx;
+				width: 100%;
+				.name-box{
+					width: 100%;
+					display: flex;
+					align-items: center;
+					justify-content: flex-start;
+					.name{
+						flex: 1;
+						font-size: 32rpx;
+						color:#111;
+					}
+					.btns{
+						.btn{
+							margin-left: 10rpx;
+							width: 40rpx;
+							height:40rpx;
+						}
+					}
+				}
+				.desc-box{
+					margin-top: 15rpx;
+					width: 100%;
+					display: flex;
+					align-items: center;
+					justify-content: flex-start;
+					.label{
+						font-size: 28rpx;
+						color: #a8a8a8;
+					}
+					.value{
+						font-size: 28rpx;
+						color: #a8a8a8;
+					}
+					
+					
+				}
+			}
+		}
+	}
+</style>

+ 89 - 0
pages/user/courseSop/sopLogsDetails.vue

@@ -0,0 +1,89 @@
+<template>
+	<view class="content">
+		<view class="info-detail" v-if="item!=null">
+			<view class="item">
+				<text class="label">接收者:</text>
+				<text class="text">{{item.externalUserName}}</text>
+			</view>
+			<view class="item">
+				<text class="label">发送时间:</text>
+				<text class="text">{{item.sendTime}}</text>
+			</view>
+			<view class="item">
+				<text class="label">实际发送时间:</text>
+				<text class="text">{{item.realSendTime}}</text>
+			</view>
+			<view class="item" v-for="(cont,index ) in contents">
+				<text class="label">发送内容({{index+1}}):</text>
+				<text class="text" v-html="cont.value"> </text>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	import {getCourseSopLogsDetail} from '@/api/course.js'
+	export default {
+		data() {
+			return {
+				id:null,
+				item:null,
+				contents:[],
+			}
+		},
+		onLoad(option) {
+			// this.id = option.id;
+			this.item=JSON.parse(uni.getStorageSync("sop"));
+			console.log(this.item)
+			this.contents=JSON.parse(this.item.contentJson);
+		},
+		onShow() {
+			 
+		},
+		methods: {
+			getUserInfoByUserId(userId){
+				var data = {userId:userId};
+				var that=this;
+				getUserInfoByUserId(data).then(
+					res => {
+						that.bindUser(res);
+						
+					},
+					rej => {}
+				);
+			},
+			 
+		}
+	}
+</script>
+
+<style lang="scss">
+	page{
+		
+		height: 100%;
+		background: #f6f6f6;
+	}
+</style>
+<style scoped lang="scss">
+	.content{
+		// 详细信息
+		.info-detail{
+			padding: 0 24upx;
+			background-color: #fff;
+			.item{
+				padding: 20upx 0;
+				border-bottom: 1px solid #f7f7f7;
+				.label{
+					font-size: 30upx;
+					color: #999;
+					margin-right: 20upx;
+				}
+				.text{
+					font-size: 30upx;
+					color: #333;
+				}
+				 
+			}
+		}
+	}
+</style>

+ 302 - 0
pages/user/courseSop/sopLosList.vue

@@ -0,0 +1,302 @@
+<template>
+	<view class="content">
+		<view class="top-box">
+			<view class="tabs">
+			 	<u-tabs
+			 	 :scrollable="false"
+			 	 :list="tabs"  
+			 	  lineColor="#115296"
+			 	@change="tagChange">
+			 	</u-tabs>
+			</view>
+			
+		</view>
+		
+		<mescroll-body top="88rpx" bottom="0" ref="mescrollRef" @init="mescrollInit"    :down="downOption" :up="upOption" @down="downCallback" @up="upCallback">
+		<view class="sop-list">
+			<view class="sop-item"  v-for="(item) in dataList" @click.stop="openDetails(item)"   >
+				<view class="name-box">
+					<view class="name">{{item.externalUserName}}</view>
+					
+				</view>
+				<view class="desc-box">
+					<view class="label">发送时间:</view>
+					<view class="value">{{item.sendTime}}</view>
+				</view>
+				<view class="desc-box">
+					<view class="label">所属销售:</view>
+					<view class="value">{{item.userName}}</view>
+				</view>
+			</view>
+		</view>
+		</mescroll-body>
+		<view class="footer-btns">
+			<u-button :disabled="!sendFlag" type="success" wid @click="sendMsg()" text="启动群发"></u-button>
+		</view>
+	</view>
+</template>
+
+<script>
+	import MescrollMixin from "@/uni_modules/mescroll-uni/components/mescroll-uni/mescroll-mixins.js";
+	import { getCourseSopLogsList} from '@/api/course.js'
+	export default {
+		mixins: [MescrollMixin], // 使用mixin
+		data() {
+			return {
+				sendFlag:true,
+				hasNextPage:false,
+				totalCount:0,
+				sopId:null,
+				status:3,
+				tabs:[
+					{
+						id:3,
+						name:'待发送'
+					},
+					{
+						id:1,
+						name:'发送成功'
+					},
+					{
+						id:0,
+						name:'发送失败'
+					}
+				],
+				mescroll:null,
+				downOption: {
+					auto:false//不要自动加载
+				},
+				upOption: {
+					onScroll:false,
+					use: true, // 是否启用上拉加载; 默认true
+					page: {
+						num: 0, // 当前页码,默认0,回调之前会加1,即callback(page)会从1开始
+						size: 10 // 每页数据的数量,默认10
+					},
+					noMoreSize: 10, // 配置列表的总数量要大于等于5条才显示'-- END --'的提示
+					textNoMore:"已经到底了",
+					empty: {
+						icon:'/static/images/empty.png',
+						tip: '暂无数据'
+					}
+				},
+				dataList: []
+			}
+		},
+		onLoad(option) {
+			this.sopId=option.sopId;
+			var that=this;
+			uni.$on('sendSop', (item) => {
+				console.log(JSON.parse(item.data))
+				var data=JSON.parse(item.data);
+				var param={
+					sendStatus:1,
+					id: data.id,
+					receivingStatus: data.receivingStatus,
+					remark:item.remark
+				};
+				updateCourseSopLogs(param).then(res => {
+					
+				});
+				that.sendMsgOp();
+			})
+		},
+		onShow() {
+		},
+		methods: {
+			sendMsgOp(){
+				if(this.sendFlag){
+					return;
+				}
+				//循环发送 先获取一条,更新一条这样发送
+				this.mescroll.resetUpScroll()
+				var userId=uni.getStorageSync('companyUserId') ;
+				var that=this;
+				setTimeout(function(){
+					if(that.dataList.length>0){
+						var data={cmd:"sendSop", data:that.dataList[0],userId:"p-"+userId};
+						uni.$emit('sendMsg',data);  
+					}
+					else{
+						that.sendFlag=true;
+						uni.showToast({
+							icon:'none',
+							title: "已完成",
+						});
+					}
+				},5000);
+				
+			},
+			sendMsg(){
+				if(!this.sendFlag){
+					uni.showToast({
+						icon:'none',
+						title: "运行中...",
+					});
+					return;
+				}
+				var that=this;
+				uni.showModal({
+					title:"提示",
+					content:"确认启动自动发送吗?",
+					showCancel:true,
+					cancelText:'取消',
+					confirmText:'确定',
+					success:res=>{
+						if(res.confirm){
+							this.sendFlag=false;
+							that.sendMsgOp()
+							 
+						}else{
+							// 否则点击了取消  
+						}
+					}
+				})
+				
+			},
+			openDetails(item){
+				uni.setStorageSync("sop",JSON.stringify(item))
+				uni.navigateTo({
+					url:"sopLogsDetails"
+				})
+			},
+			tagChange(item){
+				this.status=item.id
+				this.mescroll.resetUpScroll()
+			},
+			mescrollInit(mescroll) {
+				this.mescroll = mescroll;
+			},
+			/*下拉刷新的回调 */
+			downCallback(mescroll) {
+				mescroll.resetUpScroll()
+			},
+			upCallback(page) {
+				//联网加载数据
+				var that = this;
+				var data={
+					sopId:this.sopId,
+					sendStatus:this.status,
+					pageNum: page.num,
+					pageSize: page.size
+				};
+				uni.showLoading({
+					title:"加载中..."
+				})
+				getCourseSopLogsList(data).then(res => {
+					uni.hideLoading()
+					if(res.code==200){
+						//设置列表数据
+						if (page.num == 1) {
+							that.dataList = res.data.list; 
+							
+						} else {
+							that.dataList = that.dataList.concat(res.data.list);
+							 
+						}
+						that.hasNextPage=res.data.hasNextPage
+						that.mescroll.endBySize(res.data.list.length, res.data.total);
+						
+					}else{
+						uni.showToast({
+							icon:'none',
+							title: "请求失败",
+						});
+						that.dataList = null;
+						that.mescroll.endErr();
+					}
+				});
+			},
+			navTo(url){
+				uni.navigateTo({
+					url: url
+				})
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	page{
+		
+		height: 100%;
+		background: #f6f6f6;
+	}
+</style>
+<style scoped lang="scss">
+	.content{
+		height: 100%;
+		padding: 0rpx;
+		.top-box{
+			width: 100%;
+			z-index: 10000;
+			position: absolute;
+			top:0rpx;
+			left:0rpx;
+			
+			.tabs{
+				height: 88rpx;
+				background-color: #fff;
+				width: 100%;
+			}
+			
+		}
+		
+		.footer-btns{
+			padding:15rpx;
+			width: 100%;
+			z-index: 10000;
+			position: absolute;
+			bottom:30rpx;
+			left:0rpx;
+		}
+		
+		.sop-list{
+			display: flex;
+			flex-direction: column;
+			padding: 15rpx;
+			.sop-item{
+				padding: 15rpx;
+				border-radius: 15rpx;
+				background-color: #fff;
+				margin-bottom: 15rpx;
+				width: 100%;
+				.name-box{
+					width: 100%;
+					display: flex;
+					align-items: center;
+					justify-content: flex-start;
+					.name{
+						flex: 1;
+						font-size: 38rpx;
+						color:#111;
+					}
+					.btns{
+						.btn{
+							margin-left: 10rpx;
+							width: 40rpx;
+							height:40rpx;
+						}
+					}
+				}
+				.desc-box{
+					margin-top: 15rpx;
+					width: 100%;
+					display: flex;
+					align-items: center;
+					justify-content: flex-start;
+					.label{
+						font-size: 28rpx;
+						color: #a8a8a8;
+					}
+					.value{
+						font-size: 28rpx;
+						color: #a8a8a8;
+					}
+					
+					
+				}
+			}
+		}
+	}
+</style>

+ 131 - 0
pages/user/crm/components/customerInfo.vue

@@ -0,0 +1,131 @@
+<template>
+	<view class="content">
+		<view class="info-detail" v-if="item!=null" >
+			<view class="item">
+				<text class="label">客户姓名</text>
+				<text class="text">{{item.customerName}}</text>
+			</view>
+			<view class="item">
+				<text class="label">创建时间</text>
+				<text class="text">{{item.createTime}}</text>
+			</view>
+		 <view class="item">
+		 	<text class="label">客户微信</text>
+		 	<view class="right">
+				<view class="text ellipsis" >{{item.weixin}}</view>
+				<view class="btn" @click="sendMsg()">	
+					自动获取
+				</view>
+			</view>
+		 </view>
+		</view>
+	</view>
+</template>
+
+<script>
+	import {getCustomerDetails} from '@/api/crm.js'
+	export default {
+		data() {
+			return {
+				id:null,
+				item:null,
+			}
+		},
+		onLoad(option) {
+			
+		},
+		onShow() {
+			 
+		},
+		methods: {
+			sendMsg(){
+				var that=this;
+				var userId=uni.getStorageSync('companyUserId') ;
+				var data={cmd:"getWeixinId", data:{},userId:"p-"+userId};
+				uni.$emit('sendMsg',data); 
+				
+			},
+			getCustomerDetails(customerId) {
+				//联网加载数据
+				var that = this;
+				var data = {
+					customerId: customerId
+				};
+				getCustomerDetails(data).then(res => {
+					if(res.code==200){
+						this.item=res.customer;
+						 
+					}else{
+						uni.showToast({
+							icon:'none',
+							title: "请求失败",
+						});
+					}
+				});
+			},
+			 
+		}
+	}
+</script>
+
+<style lang="scss">
+	page{
+		
+		height: 100%;
+		background: #f6f6f6;
+	}
+</style>
+<style scoped lang="scss">
+	.content{
+		// 详细信息
+		.info-detail{
+			background-color: #fff;
+			.item{
+				height: 88rpx;
+				border-bottom: 1rpx solid #f3f3f3;
+				width: 100%;
+				display: flex;
+				align-items: center;
+				justify-content: flex-start;
+				padding: 20upx 0;
+				.label{
+					min-width: 150rpx;
+					flex: 1;
+					font-size: 28upx;
+					color: #999;
+					margin-right: 20upx;
+				}
+				.right{
+					
+					flex: 1;
+					display: flex;
+					align-items: center;
+					justify-content: flex-end;
+					.text{
+						max-width: 200rpx;
+						font-size: 28upx;
+						color: #333;
+					}
+					.btn{
+						min-width: 150rpx;
+						height: 66rpx;
+						border-radius: 8rpx;
+						padding: 5rpx 15rpx;
+						display: flex;
+						align-items: center;
+						justify-content: center;
+						border: 1rpx solid #e9e9e9;
+						font-size: 28rpx;
+						color: #909399;
+						
+					}
+				}
+				.text{
+					font-size: 28upx;
+					color: #333;
+				}
+				 
+			}
+		}
+	}
+</style>

+ 225 - 0
pages/user/crm/customerDetails.vue

@@ -0,0 +1,225 @@
+<template>
+	<view class="content">
+		<view class="top-box" v-if="item!=null">
+			<view class="customer-box">
+				<view class="left">
+					<view class="name">{{item.customerName}}</view>
+					<view class="desc-box">
+						<view class="label">创建时间:</view>
+						<view class="value"> {{item.createTime}}</view>
+					</view>
+					<view class="desc-box">
+						<view class="label">地址:</view>
+						<view class="value">{{item.address}} </view>
+					</view>
+				</view>
+				<view class="btns">
+					<image class="btn" src="../../../static/images/sms.png"></image>
+					<image class="btn" src="../../../static/images/phone.png"></image>
+				</view>
+			</view>
+			<view class="tabs">
+				<u-tabs
+				 :scrollable="true"
+				 :list="tabs"  
+				  lineColor="#115296"
+				@change="tagChange">
+				</u-tabs>
+			</view>
+			<view class="cont-box">
+				<customer-info ref="customerInfo"  v-if="tabId==1"></customer-info>
+			</view>
+		</view>
+		<u-modal :show="show"   ref="uModal" :asyncClose="true" :showCancelButton="true" title="提示" @cancel="close" @confirm="confirm" :content='content'></u-modal>
+	</view>
+</template>
+<script>
+	import customerInfo from './components/customerInfo.vue'
+	import {getCustomerDetails,editCrmCustomer} from '@/api/crm.js'
+	export default {
+		components:{
+			customerInfo, // 基本信息
+		},
+		data() {
+			return {
+				wxNickName:null,
+				show:false,
+				content:"",
+				customerId:null,
+				tabId:1,
+				item:null,
+				tabs:[
+					{
+						id:1,
+						name:'用户资料'
+					},
+					{
+						id:2,
+						name:'联系人'
+					},
+					{
+						id:3,
+						name:'跟进记录'
+					},
+					{
+						id:4,
+						name:'通话记录'
+					},
+					{
+						id:5,
+						name:'短信记录'
+					}
+				],
+			}
+		},
+		onLoad(option) {
+			var that=this;
+			uni.$on('getWeixinId', (item) => {
+				console.log(item)
+				
+				that.content="用户昵称:"+item.data+",是否更新";
+				that.wxNickName=item.data;
+				that.show=true;
+			})
+			this.customerId=option.customerId
+			console.log(this.customerId)
+			this.getCustomerDetails()
+		},
+		onShow() {
+			
+		},
+		methods: {
+			close(){
+				this.show=false;
+			},
+			confirm(){
+				this.show=false;
+				var data = {
+					customerId:this.customerId,
+					weixin:this.wxNickName
+					
+				};
+				//更新用户昵称
+				var that=this;
+				editCrmCustomer(data).then(res => {
+					if(res.code==200){
+						uni.showToast({
+							icon:'none',
+							title: "操作成功",
+						});
+						if(this.tabId==1){
+							setTimeout(function(){
+								that.$refs.customerInfo.getCustomerDetails(that.customerId)
+							},200);
+						}
+					}else{
+						uni.showToast({
+							icon:'none',
+							title: res.msg,
+						});
+					}
+				});
+				
+			},
+			tagChange(item){
+				this.tabId=item.id
+				var that = this;
+				if(this.tabId==1){
+					setTimeout(function(){
+						that.$refs.customerInfo.getCustomerDetails(that.customerId)
+					},200);
+				}
+			},
+			getCustomerDetails() {
+				//联网加载数据
+				var that = this;
+				var data = {
+					customerId:this.customerId
+				};
+				getCustomerDetails(data).then(res => {
+					if(res.code==200){
+						this.item=res.customer;
+						setTimeout(function(){
+							that.$refs.customerInfo.getCustomerDetails(that.customerId)
+						},200);
+						
+						 
+					}else{
+						uni.showToast({
+							icon:'none',
+							title: res.msg,
+						});
+						setTimeout(function(){
+							uni.navigateBack()
+						},2000);
+						
+					}
+				});
+			},
+		}
+	}
+</script>
+
+<style lang="scss">
+	page{
+		
+		height: 100%;
+		background: #f6f6f6;
+	}
+</style>
+<style scoped lang="scss">
+	.content{
+		height: 100%;
+		padding: 20rpx;
+		.top-box{
+			padding: 15rpx;
+			background-color: #fff;
+			margin-bottom: 15rpx;
+			border-radius: 15rpx;
+			width: 100%;
+			.customer-box{
+				
+				width: 100%;
+				display: flex;
+				align-items: flex-start;
+				justify-content: flex-start;
+				.left{
+					flex: 1;
+					font-size: 38rpx;
+					color:#111;
+					.name{
+						flex: 1;
+						font-size: 42rpx;
+						color:#111;
+					}
+					.desc-box{
+						margin-top: 15rpx;
+						width: 100%;
+						display: flex;
+						align-items: center;
+						justify-content: flex-start;
+						.label{
+							font-size: 28rpx;
+							color: #a8a8a8;
+						}
+						.value{
+							font-size: 28rpx;
+							color: #a8a8a8;
+						}
+						
+						
+					}
+				}
+				.btns{
+					.btn{
+						margin-left: 10rpx;
+						width: 45rpx;
+						height:45rpx;
+					}
+				}
+			}
+		}
+		
+	}
+	 
+</style>

+ 235 - 0
pages/user/crm/importWxUser.vue

@@ -0,0 +1,235 @@
+<template>
+	<view class="content">
+		<view class="customer-list">
+			<view class="customer-item"  v-for="(item,index) in dataList"  >
+				<view class="name-box">
+					<view class="name">昵称:{{item.nickName}}</view>
+					<view class="btns">
+						<view class="btn"  @click="delItem(index)" >删除</view>
+					</view>
+				</view>
+				<view class="desc-box">
+					<view class="label">微信号:</view>
+					<view class="value">{{item.id}}</view>
+				</view>
+				 
+			</view>
+		</view>
+		<view class="bottom-btns">
+			<view class="btn1" @click="getWxUsers()">获取客户</view>
+			<view class="btn2" @click="addWxUsers()">导入客户</view>
+		</view>
+		 
+	</view>
+</template>
+
+<script>
+	
+	import {addCompanyWxUser} from '@/api/companyWxUser.js'
+	export default {
+		data() {
+			return {
+				flag:true,
+				dataList: []
+			}
+		},
+		onLoad() {
+			var that=this;
+			uni.$on('getWeixinList', (items) => {
+				console.log(JSON.parse(items.data))
+				that.dataList=JSON.parse(items.data);
+				that.flag=true;
+				uni.showToast({
+					icon:'none',
+					title: "获取微信任务已完成,共获取"+that.dataList.length+"个",
+				});	
+				 
+			})
+		},
+		onShow() {
+		},
+		methods: {
+			delItem(index){
+				this.dataList.splice(index,1);
+			},
+			importUsers(){
+				var that=this;
+				var index=0;
+				that.dataList.forEach(item => {
+					//更新用户昵称
+					index++;
+					var data={nickName:item.nickName,weixinId:item.id};
+					addCompanyWxUser(data).then(res => {
+						if(index>=that.dataList.length){
+							that.dataList=[];
+							uni.showToast({
+								icon:'none',
+								title: "导入成功",
+							});	
+						}
+						else{
+							uni.showToast({
+								icon:'none',
+								title: "导入"+item.nickName,
+							});	
+						}
+					});
+					
+				});
+				
+			},
+			addWxUsers(){
+				if(!this.flag){
+					uni.showToast({
+						icon:'none',
+						title: "正在同步获取微信用户...",
+					});
+					return;
+				}
+				if(this.dataList.length==0){
+					uni.showToast({
+						icon:'none',
+						title: "没有可导入的数据",
+					});
+					return;
+				}
+				var that=this;
+				uni.showModal({
+					title:"提示",
+					content:"确认导入用户列表吗?",
+					showCancel:true,
+					cancelText:'取消',
+					confirmText:'确定',
+					success:res=>{
+						if(res.confirm){
+							that.importUsers();
+													
+						}else{
+						}
+					}
+				})
+			},
+			getWxUsers(){
+				if(!this.flag){
+					return;
+				}
+				var that=this;
+				uni.showModal({
+					title:"提示",
+					content:"确认开始获取微信用户?",
+					showCancel:true,
+					cancelText:'取消',
+					confirmText:'确定',
+					success:res=>{
+						if(res.confirm){
+							that.flag=false;
+							var userId=uni.getStorageSync('companyUserId') ;
+							var data={cmd:"getWeixinList", data:{},userId:"p-"+userId};
+							uni.$emit('sendMsg',data); 
+							uni.showToast({
+							 	icon:'none',
+							 	title: "开始获取微信用户...",
+							});
+						}else{
+						}
+					}
+				})
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	page{
+		
+		height: 100%;
+		background: #f6f6f6;
+	}
+</style>
+<style scoped lang="scss">
+	.content{
+		height: 100%;
+		padding: 0rpx;
+		position: relative;
+		.bottom-btns{
+			position: fixed;
+			bottom: 0;
+			left: 0;
+			width: 100%;
+			height: 50px;
+			display: flex;
+			align-items: center;
+			justify-content: center;
+			.btn1{
+				display: flex;
+				align-items: center;
+				justify-content: center;
+				height: 50px;
+				flex:1;
+				background-color: red;
+				color: #fff;
+				font-size: 28rpx;
+			}
+			.btn2{
+				display: flex;
+				align-items: center;
+				justify-content: center;
+				height: 50px;
+				flex:1;
+				background-color: green;
+				color: #fff;
+				font-size: 28rpx;
+			}
+		}
+		.customer-list{
+			display: flex;
+			flex-direction: column;
+			padding: 15rpx 15rpx 100rpx;
+			.customer-item{
+				padding: 15rpx;
+				border-radius: 15rpx;
+				background-color: #fff;
+				margin-bottom: 15rpx;
+				width: 100%;
+				.name-box{
+					width: 100%;
+					display: flex;
+					align-items: center;
+					justify-content: flex-start;
+					.name{
+						flex: 1;
+						font-size: 38rpx;
+						color:#111;
+					}
+					.btns{
+						.btn{
+							margin-left: 10rpx;
+							color: #a8a8a8;
+							font-size: 28rpx;
+							 
+						}
+					}
+				}
+				.desc-box{
+					margin-top: 15rpx;
+					width: 100%;
+					display: flex;
+					align-items: center;
+					justify-content: flex-start;
+					.label{
+						font-size: 28rpx;
+						color: #a8a8a8;
+					}
+					.value{
+						font-size: 28rpx;
+						color: #a8a8a8;
+					}
+					
+					
+				}
+			}
+		}
+		
+	}
+	 
+</style>

+ 193 - 0
pages/user/crm/lineCustomer.vue

@@ -0,0 +1,193 @@
+<template>
+	<view class="content">
+		<view class="top">
+			<u-search placeholder="输入名称搜索" v-model="searchKey" @custom="search()" @search="search()"></u-search>
+		</view>
+		<mescroll-body top="88rpx" bottom="0" ref="mescrollRef" @init="mescrollInit"    :down="downOption" :up="upOption" @down="downCallback" @up="upCallback">
+		<view class="customer-list">
+			<view class="customer-item"  v-for="(item) in dataList"  @click="navTo('/pages/user/crm/customerDetails?customerId='+item.customerId)" >
+				<view class="name-box">
+					<view class="name">{{item.customerName}}</view>
+					<view class="btns">
+						<image class="btn" src="../../../static/images/sms.png"></image>
+						<image class="btn" src="../../../static/images/phone.png"></image>
+					</view>
+				</view>
+				<view class="desc-box">
+					<view class="label">创建时间:</view>
+					<view class="value">{{item.createTime}}</view>
+				</view>
+				<view class="desc-box">
+					<view class="label">所在地区:</view>
+					<view class="value">{{item.address}}</view>
+				</view>
+				<view class="desc-box">
+					<view class="label">备&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;注:</view>
+					<view class="value">{{item.remark}}</view>
+				</view>
+				 
+			</view>
+		</view>
+		</mescroll-body>
+	</view>
+</template>
+
+<script>
+	import MescrollMixin from "@/uni_modules/mescroll-uni/components/mescroll-uni/mescroll-mixins.js";
+	
+	import {getLineCustomerList} from '@/api/crm.js'
+	export default {
+		mixins: [MescrollMixin], // 使用mixin
+		data() {
+			return {
+				searchKey:"",
+				mescroll:null,
+				downOption: {
+					auto:false//不要自动加载
+				},
+				upOption: {
+					onScroll:false,
+					use: true, // 是否启用上拉加载; 默认true
+					page: {
+						num: 0, // 当前页码,默认0,回调之前会加1,即callback(page)会从1开始
+						size: 10 // 每页数据的数量,默认10
+					},
+					noMoreSize: 10, // 配置列表的总数量要大于等于5条才显示'-- END --'的提示
+					textNoMore:"已经到底了",
+					empty: {
+						icon:'/static/images/empty.png',
+						tip: '暂无数据'
+					}
+				},
+				dataList: []
+			}
+		},
+		onShow() {
+		},
+		methods: {
+			search(){
+				this.mescroll.resetUpScroll()
+			}, 
+			navTo(url){
+				uni.navigateTo({
+					url: url
+				})
+			},
+			mescrollInit(mescroll) {
+				this.mescroll = mescroll;
+			},
+			/*下拉刷新的回调 */
+			downCallback(mescroll) {
+				mescroll.resetUpScroll()
+			},
+			upCallback(page) {
+				//联网加载数据
+				var that = this;
+				var data={
+					customerName:this.searchKey,
+					pageNum: page.num,
+					pageSize: page.size
+				};
+				uni.showLoading({
+					title:"加载中..."
+				})
+				getLineCustomerList(data).then(res => {
+					uni.hideLoading()
+					if(res.code==200){
+						//设置列表数据
+						if (page.num == 1) {
+							that.dataList = res.data.list; 
+							
+						} else {
+							that.dataList = that.dataList.concat(res.data.list);
+							 
+						}
+						that.mescroll.endBySize(res.data.list.length, res.data.total);
+						
+					}else{
+						uni.showToast({
+							icon:'none',
+							title: "请求失败",
+						});
+						that.dataList = null;
+						that.mescroll.endErr();
+					}
+				});
+			},
+		}
+	}
+</script>
+
+<style lang="scss">
+	page{
+		
+		height: 100%;
+		background: #f6f6f6;
+	}
+</style>
+<style scoped lang="scss">
+	.content{
+		height: 100%;
+		padding: 0rpx;
+		position: relative;
+		.top{
+			z-index: 10000;
+			width: 100%;
+			position: absolute;
+			top:0rpx;
+			left:0rpx;
+			height: 88rpx;
+			background-color: #fff;
+			padding: 0rpx 15rpx;
+		}
+		.customer-list{
+			display: flex;
+			flex-direction: column;
+			padding: 15rpx;
+			.customer-item{
+				padding: 15rpx;
+				border-radius: 15rpx;
+				background-color: #fff;
+				margin-bottom: 15rpx;
+				width: 100%;
+				.name-box{
+					width: 100%;
+					display: flex;
+					align-items: center;
+					justify-content: flex-start;
+					.name{
+						flex: 1;
+						font-size: 38rpx;
+						color:#111;
+					}
+					.btns{
+						.btn{
+							margin-left: 10rpx;
+							width: 45rpx;
+							height:45rpx;
+						}
+					}
+				}
+				.desc-box{
+					margin-top: 15rpx;
+					width: 100%;
+					display: flex;
+					align-items: center;
+					justify-content: flex-start;
+					.label{
+						font-size: 28rpx;
+						color: #a8a8a8;
+					}
+					.value{
+						font-size: 28rpx;
+						color: #a8a8a8;
+					}
+					
+					
+				}
+			}
+		}
+		
+	}
+	 
+</style>

+ 193 - 0
pages/user/crm/myCustomer.vue

@@ -0,0 +1,193 @@
+<template>
+	<view class="content">
+		<view class="top">
+			<u-search placeholder="输入名称搜索" v-model="searchKey" @custom="search()" @search="search()"></u-search>
+		</view>
+		<mescroll-body top="88rpx" bottom="0" ref="mescrollRef" @init="mescrollInit"    :down="downOption" :up="upOption" @down="downCallback" @up="upCallback">
+		<view class="customer-list">
+			<view class="customer-item"  v-for="(item) in dataList"  @click="navTo('/pages/user/crm/customerDetails?customerId='+item.customerId)" >
+				<view class="name-box">
+					<view class="name">{{item.customerName}}</view>
+					<view class="btns">
+						<image class="btn" src="../../../static/images/sms.png"></image>
+						<image class="btn" src="../../../static/images/phone.png"></image>
+					</view>
+				</view>
+				<view class="desc-box">
+					<view class="label">最后跟进:</view>
+					<view class="value">{{item.startTime}}</view>
+				</view>
+				<view class="desc-box">
+					<view class="label">所在地区:</view>
+					<view class="value">{{item.address}}</view>
+				</view>
+				<view class="desc-box">
+					<view class="label">备&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;注:</view>
+					<view class="value">{{item.remark}}</view>
+				</view>
+				 
+			</view>
+		</view>
+		</mescroll-body>
+	</view>
+</template>
+
+<script>
+	import MescrollMixin from "@/uni_modules/mescroll-uni/components/mescroll-uni/mescroll-mixins.js";
+	
+	import {getMyCustomerList} from '@/api/crm.js'
+	export default {
+		mixins: [MescrollMixin], // 使用mixin
+		data() {
+			return {
+				searchKey:"",
+				mescroll:null,
+				downOption: {
+					auto:false//不要自动加载
+				},
+				upOption: {
+					onScroll:false,
+					use: true, // 是否启用上拉加载; 默认true
+					page: {
+						num: 0, // 当前页码,默认0,回调之前会加1,即callback(page)会从1开始
+						size: 10 // 每页数据的数量,默认10
+					},
+					noMoreSize: 10, // 配置列表的总数量要大于等于5条才显示'-- END --'的提示
+					textNoMore:"已经到底了",
+					empty: {
+						icon:'/static/images/empty.png',
+						tip: '暂无数据'
+					}
+				},
+				dataList: []
+			}
+		},
+		onShow() {
+		},
+		methods: {
+			search(){
+				this.mescroll.resetUpScroll()
+			}, 
+			navTo(url){
+				uni.navigateTo({
+					url: url
+				})
+			},
+			mescrollInit(mescroll) {
+				this.mescroll = mescroll;
+			},
+			/*下拉刷新的回调 */
+			downCallback(mescroll) {
+				mescroll.resetUpScroll()
+			},
+			upCallback(page) {
+				//联网加载数据
+				var that = this;
+				var data={
+					customerName:this.searchKey,
+					pageNum: page.num,
+					pageSize: page.size
+				};
+				uni.showLoading({
+					title:"加载中..."
+				})
+				getMyCustomerList(data).then(res => {
+					uni.hideLoading()
+					if(res.code==200){
+						//设置列表数据
+						if (page.num == 1) {
+							that.dataList = res.data.list; 
+							
+						} else {
+							that.dataList = that.dataList.concat(res.data.list);
+							 
+						}
+						that.mescroll.endBySize(res.data.list.length, res.data.total);
+						
+					}else{
+						uni.showToast({
+							icon:'none',
+							title: "请求失败",
+						});
+						that.dataList = null;
+						that.mescroll.endErr();
+					}
+				});
+			},
+		}
+	}
+</script>
+
+<style lang="scss">
+	page{
+		
+		height: 100%;
+		background: #f6f6f6;
+	}
+</style>
+<style scoped lang="scss">
+	.content{
+		height: 100%;
+		padding: 0rpx;
+		position: relative;
+		.top{
+			z-index: 10000;
+			width: 100%;
+			position: absolute;
+			top:0rpx;
+			left:0rpx;
+			height: 88rpx;
+			background-color: #fff;
+			padding: 0rpx 15rpx;
+		}
+		.customer-list{
+			display: flex;
+			flex-direction: column;
+			padding: 15rpx;
+			.customer-item{
+				padding: 15rpx;
+				border-radius: 15rpx;
+				background-color: #fff;
+				margin-bottom: 15rpx;
+				width: 100%;
+				.name-box{
+					width: 100%;
+					display: flex;
+					align-items: center;
+					justify-content: flex-start;
+					.name{
+						flex: 1;
+						font-size: 38rpx;
+						color:#111;
+					}
+					.btns{
+						.btn{
+							margin-left: 10rpx;
+							width: 45rpx;
+							height:45rpx;
+						}
+					}
+				}
+				.desc-box{
+					margin-top: 15rpx;
+					width: 100%;
+					display: flex;
+					align-items: center;
+					justify-content: flex-start;
+					.label{
+						font-size: 28rpx;
+						color: #a8a8a8;
+					}
+					.value{
+						font-size: 28rpx;
+						color: #a8a8a8;
+					}
+					
+					
+				}
+			}
+		}
+		
+	}
+	 
+</style>

+ 179 - 0
pages/user/editPwd.vue

@@ -0,0 +1,179 @@
+<template>
+	<view>
+		<view class="content">
+			
+			<view class="info-item">
+				<view class="label">旧密码</view>
+				<view class="right">
+					<input type="safe-password" v-model="oldPassword" placeholder="请输入旧密码" class="input"></text>
+				</view>
+			</view>
+			
+			<view class="info-item">
+				<view class="label">新密码</view>
+				<view class="right">
+					<input type="safe-password" v-model="password" placeholder="请输入新密码" class="input"></text>
+				</view>
+			</view>
+			<view class="info-item">
+				<view class="label">确认密码</view>
+				<view class="right">
+					<input type="safe-password" v-model="password1" placeholder="请输入确认密码" class="input"></text>
+				</view>
+			</view>
+		</view>
+		<view class="btn-box">
+			<view class="sub-btn" @click="submit()">保存修改</view>
+		</view>
+	</view>
+	
+
+</template>
+
+<script>
+	import {setPwd,setHeadImg,setUserInfo,getUserInfo} from '@/api/user.js';
+	export default {
+		data() {
+			return {
+				oldPassword:"",
+				password:"",
+				password1:"",
+			}
+		},
+		onLoad() {
+		},
+		methods: {
+			submit(){
+				if (this.utils.isEmpty(this.oldPassword)) {
+					uni.showToast({
+						title: "请输入旧密码",
+						icon: 'none',
+					});
+					return
+				}
+				if (this.utils.isEmpty(this.password)) {
+					uni.showToast({
+						title: "请输入新密码",
+						icon: 'none',
+					});
+					return
+				}
+				if (this.password!=this.password1) {
+					uni.showToast({
+						title: "两次密码输入不正确",
+						icon: 'none',
+					});
+					return
+				}
+				console.log(this.password);
+				var patrn=/^(\w){6,20}$/;  
+				if (!patrn.exec(this.password)) {
+					uni.showToast({
+						title: "密码只能输入6-20个字母、数字、下划线",
+						icon: 'none',
+					});
+					return
+				}
+				
+				var data = {oldPassword:this.oldPassword,password:this.password};
+				setPwd(data).then(
+					res => {
+						if(res.code==200){
+							uni.showToast({
+							    title: '密码修改成功',
+							    duration: 2000
+							});
+							setTimeout(function() {
+								uni.navigateBack({
+								  success: () => {
+								  }
+								})
+							}, 500);
+						}
+						else{
+							uni.showToast({
+								title: res.msg,
+								icon: 'none',
+							});
+						}
+						
+					},
+					rej => {}
+				);
+				
+			}
+			
+		}
+	}
+</script>
+
+
+<style scoped lang="scss">
+	.content{
+		padding-top: 20rpx;
+		border-top: 1px solid #F5F6FA;
+	}
+	.info-item{
+		height: 104upx;
+		background: #FFFFFF;
+		padding: 0 30upx;
+		display: flex;
+		align-items: center;
+		justify-content: flex-start;
+		border-bottom: 1px solid #F5F6FA;
+		&:last-child{
+			border-bottom: none;
+		}
+		.label{
+			width: 150rpx;
+			font-size: 30upx;
+			font-family: PingFang SC;
+			font-weight: 400;
+			color: #0F1826;
+		}
+		.right{
+			display: flex;
+			align-items: center;
+			justify-content: center;
+			.text{
+				font-size: 30upx;
+				font-family: PingFang SC;
+				font-weight: 400;
+				color: #0F1826;
+			}
+			.image{
+				margin-left: 10upx;
+				width: 30upx;
+				height: 30upx;
+			}
+			.input{
+				text-align: left;
+				font-size: 30upx;
+				font-family: PingFang SC;
+				font-weight: 400;
+				color: #0F1826;
+			}
+		}
+		 
+	}
+	.btn-box{
+		margin-top: 15rpx;
+		height: 120upx;
+		padding: 0 30upx;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		.sub-btn{
+			width: 100%;
+			height: 88upx;
+			line-height: 88upx;
+			text-align: center;
+			font-size: 30upx;
+			font-family: PingFang SC;
+			font-weight: bold;
+			color: #FFFFFF;
+			background: #4BC9B1;
+			border-radius: 44upx;
+		}
+	}
+</style>

+ 293 - 0
pages/user/editUser.vue

@@ -0,0 +1,293 @@
+<template>
+	<view>
+		<view class="content">
+			<view class="info-item">
+				<view class="label">头像</view>
+				<view class="right">
+					<image  class="head" @tap="chooseImage()" :src="headImg" mode=""></image>
+				</view>
+			</view>
+			<view class="info-item">
+				<view class="label">姓名</view>
+				<view class="right">
+					<text class="text">{{nickName}}</text>
+				</view>
+			</view>
+			<view class="info-item">
+				<view class="label">部门</view>
+				<view class="right">
+					<text class="text">{{deptName}}</text>
+				</view>
+			</view>
+			<view class="info-item">
+				<view class="label">岗位</view>
+				<view class="right">
+					<text class="text">{{postNames}}</text>
+				</view>
+			</view>
+			<view class="info-item">
+				<view class="label">性别</view>
+				<view class="right">
+					<picker @change="pickerSex" :value="sex" :range="sexPicker">
+						<view class="picker">
+							{{sex>-1?sexPicker[sex]:''}}
+						</view>
+					</picker>
+				</view>
+			</view>
+			 <view class="info-item">
+			 	<view class="label">邮箱</view>
+			 	<view class="right">
+					<input type="text" v-model="email" placeholder="请输入邮箱" class="input"></text>
+			 	</view>
+			 </view>
+			<view class="info-item">
+				<view class="label">手机号</view>
+				<view class="right">
+					<input type="text" v-model="phonenumber" placeholder="请输入手机号" class="input"></text>
+				</view>
+			</view>
+		</view>
+		<view class="btn-box">
+			<view class="sub-btn" @click="submit()">保存修改</view>
+		</view>
+	</view>
+	
+
+</template>
+
+<script>
+	import {setPwd,setHeadImg,setUserInfo,getCompanyUser} from '@/api/user.js';
+	export default {
+		data() {
+			return {
+				nickName:"",
+				deptName:"",
+				postNames:"",
+				phonenumber:"",
+				email:"",
+				sexPicker: ['男', '女','未知'],
+				sex: 0, // 性别
+				headImg: '/static/images/detault_head.jpg', // 头像,
+				headImgUrl:'',
+			}
+		},
+		onLoad() {
+			this.getCompanyUser();
+		},
+		methods: {
+			bindUser(data){
+				var that=this;
+				that.nickName=data.nickName;
+				that.deptName=data.deptName;
+				// that.posts=data.post;
+				that.phonenumber=data.phonenumber;
+				that.email=data.email;
+				that.sex=data.sex;
+				if(!that.utils.isEmpty(data.avatar)){
+					that.headImg=uni.getStorageSync('requestPath')+data.avatar;
+					this.headImgUrl=data.avatar;
+				}
+			},
+			getCompanyUser(){
+				var data = {};
+				var that=this;
+				getCompanyUser(data).then(
+					res => {
+						that.bindUser(res.data);
+						
+					},
+					rej => {}
+				);
+			},
+			// 性别选择
+			pickerSex(e) {
+				this.sex = e.detail.value
+			},
+			// 选择照片
+			chooseImage() {
+				uni.chooseImage({
+					count: 1, //默认9
+					sizeType: ['original', 'compressed'], //可以指定是原图还是压缩图,默认二者都有
+					sourceType: ['album', 'camera'], //从相册选择
+					success: (res) => {
+						//this.headImg = res.tempFilePaths[0]
+						uni.uploadFile({
+							url: uni.getStorageSync('requestPath')+'/app/common/upload', //仅为示例,非真实的接口地址
+							filePath: res.tempFilePaths[0],
+							name: 'file',
+							formData: {
+								'user': 'test'  // 上传附带参数
+							},
+							success: (uploadFileRes) => {
+								console.log(uploadFileRes)
+								// 根据接口具体返回格式   赋值具体对应url
+								var data=JSON.parse(uploadFileRes.data)
+								this.headImg=uni.getStorageSync('requestPath')+data.fileName;
+								this.headImgUrl=data.fileName;
+							}
+						});
+						
+					}
+				});
+			},
+			// 预览头像
+			viewImage() {
+				uni.previewImage({
+					urls: this.headImg,
+					current: this.headImg
+				});
+			},
+			submit(){
+				if (this.utils.isEmpty(this.email)) {
+					uni.showToast({
+						title: "请输入邮箱",
+						icon: 'none',
+					});
+					return
+				}
+				if (this.utils.isEmpty(this.phonenumber)) {
+					uni.showToast({
+						title: "请输入手机号",
+						icon: 'none',
+					});
+					return
+				}
+				var data = {mobile:this.phonenumber,email:this.email,sex:this.sex.toString(),headImg:this.headImgUrl};
+				console.log(data);
+				setUserInfo(data).then(
+					res => {
+						if(res.code==200){
+							uni.showToast({
+								title: '修改成功',
+								duration: 2000
+							});
+							setTimeout(function() {
+								uni.navigateBack({
+								  success: () => {
+								  }
+								})
+							}, 500);
+						}
+						else{
+							uni.showToast({
+								title: res.msg,
+								icon: 'none',
+							});
+						}
+						
+					},
+					rej => {}
+				);
+				
+			}
+			
+		}
+	}
+</script>
+
+
+<style scoped lang="scss">
+	.content{
+		padding-top: 20rpx;
+		border-top: 1px solid #F5F6FA;
+	}
+	.info-item{
+		height: 104upx;
+		background: #FFFFFF;
+		padding: 0 30upx;
+		display: flex;
+		align-items: center;
+		justify-content: space-between;
+		border-bottom: 1px solid #F5F6FA;
+		&:last-child{
+			border-bottom: none;
+		}
+		.label{
+			
+			font-size: 30upx;
+			font-family: PingFang SC;
+			font-weight: 400;
+			color: #0F1826;
+		}
+		.right{
+			display: flex;
+			align-items: center;
+			justify-content: center;
+			.text{
+				font-size: 30upx;
+				font-family: PingFang SC;
+				font-weight: 400;
+				color: #0F1826;
+			}
+			.image{
+				margin-left: 10upx;
+				width: 30upx;
+				height: 30upx;
+			}
+			.input{
+				text-align: right;
+				font-size: 30upx;
+				font-family: PingFang SC;
+				font-weight: 400;
+				color: #0F1826;
+			}
+		}
+		.head{
+			border-radius: 50%;
+			width: 80upx;
+			height: 80upx;
+		}
+		.arrow{
+			width: 30upx;
+			height: 30upx;
+		}
+		.text{
+			font-size: 30upx;
+			font-family: PingFang SC;
+			font-weight: 400;
+			color: #0F1826;
+		}
+		.input{
+			text-align: right;
+			font-size: 30upx;
+			font-family: PingFang SC;
+			font-weight: 400;
+			color: #0F1826;
+		}
+		&.password{
+			margin-top: 20upx;
+			.right{
+				display: flex;
+				align-items: center;
+				.text{
+					font-size: 30upx;
+					font-family: PingFang SC;
+					font-weight: 400;
+					color: #0F1826;
+					margin-right: 15upx;
+				}
+			}
+		}
+	}
+	.btn-box{
+		margin-top: 15rpx;
+		height: 120upx;
+		padding: 0 30upx;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		.sub-btn{
+			width: 100%;
+			height: 88upx;
+			line-height: 88upx;
+			text-align: center;
+			font-size: 30upx;
+			font-family: PingFang SC;
+			font-weight: bold;
+			color: #FFFFFF;
+			background: #115296;
+			border-radius: 44upx;
+		}
+	}
+</style>

+ 398 - 0
pages/user/index.vue

@@ -0,0 +1,398 @@
+<template>
+	<view class="content">
+		<view class="bg"></view>
+		<view class="cont-box">
+			<view class="user-cont">
+				<view class="user-box" v-if="user!=null">
+					<view class="left">
+						<image src="../../static/images/default.png"></image>
+						<view class="user" >
+							<view class="username">{{user.nickName}}</view>
+							<view class="account">{{user.deptName}}</view>
+						</view>
+					</view>
+					<view class="right" @click="navTo('/pages/user/userInfo')">
+						<image src="../../static/images/right_arrow.png"></image>
+					</view>
+				</view>
+				<view class="company" v-if="user!=null">
+					<image src="../../static/images/icon_comp.png"></image>
+					<view class="name">{{user.deptName}}</view>
+				</view>
+			</view>
+			
+			<view class="menu-box">
+				<view class="title-box">
+					<image class="icon" src="../../static/images/office.png"></image>
+					<view class="title">常用功能</view>
+				</view>
+				<view class="line"></view>
+				<view class="menus">
+					<view class="menu-item" @click="navTo('/pages/user/ai/index')">
+						<image src="../../static/images/menu_info.png"></image>
+						<view class="m-name">AI客服</view>
+					</view>
+					<view class="menu-item" @click="navTo('/pages/user/qwSop/sop')">
+						<image src="../../static/images/menu_info.png"></image>
+						<view class="m-name">群发SOP</view>
+					</view>
+					<!-- <view class="menu-item" @click="navTo('/pages/user/courseSop/sop')">
+						<image src="../../static/images/menu_info.png"></image>
+						<view class="m-name">课程群发SOP</view>
+					</view> -->
+				</view>
+				
+			</view>
+			<view class="menu-box">
+				<view class="title-box">
+					<image class="icon" src="../../static/images/office.png"></image>
+					<view class="title">客户管理</view>
+				</view>
+				<view class="line"></view>
+				<view class="menus">
+					<view class="menu-item" @click="navTo('/pages/user/crm/lineCustomer')">
+						<image src="../../static/images/menu_info.png"></image>
+						<view class="m-name">线索客户</view>
+					</view>
+					<view class="menu-item" @click="navTo('/pages/user/crm/myCustomer')">
+						<image src="../../static/images/menu_info.png"></image>
+						<view class="m-name">我的客户</view>
+					</view>
+					<view class="menu-item" @click="navTo('/pages/user/crm/importWxUser')">
+						<image src="../../static/images/menu_info.png"></image>
+						<view class="m-name">导入微信用户</view>
+					</view>
+				</view>
+			</view>
+			<!-- <view class="menu-box">
+				<view class="title-box">
+					<image class="icon" src="../../static/images/office.png"></image>
+					<view class="title">常用工具</view>
+				</view>
+				<view class="line"></view>
+				<view class="menus">
+					<view class="menu-item"  >
+						<image src="../../static/images/menu_info.png"></image>
+						<view class="m-name">制单</view>
+					</view>
+					<view class="menu-item"  >
+						<image src="../../static/images/menu_info.png"></image>
+						<view class="m-name">微信收款</view>
+					</view>
+					<view class="menu-item"  >
+						<image src="../../static/images/menu_info.png"></image>
+						<view class="m-name">支付宝收款</view>
+					</view>
+					<view class="menu-item"  >
+						<image src="../../static/images/menu_info.png"></image>
+						<view class="m-name">订单管理</view>
+					</view>
+					<view class="menu-item"  >
+						<image src="../../static/images/menu_info.png"></image>
+						<view class="m-name">商品套餐包</view>
+					</view>
+					<view class="menu-item"  >
+						<image src="../../static/images/menu_info.png"></image>
+						<view class="m-name">优惠券</view>
+					</view>
+				</view>
+			</view> -->
+			<view class="menu-box">
+				<view class="title-box">
+					<image class="icon" src="../../static/images/office.png"></image>
+					<view class="title">其它工具</view>
+				</view>
+				<view class="line"></view>
+				<view class="menus">
+					<view class="menu-item" @click="navTo('/pages/user/users/users')">
+						<image src="../../static/images/menu_info.png"></image>
+						<view class="m-name">通讯录</view>
+					</view>
+				</view>
+			</view>
+			<view class="menu-box1">
+				<view class="menu-item" @click="navTo('/pages/user/about')">
+					<view class="left">
+						<image src="../../static/images/icon_about_us.png"></image>
+						<view class="label">关于我们</view>
+					</view>
+					<view class="right">
+						<image src="../../static/images/right_arrow.png"></image>
+					</view>
+				</view>
+				<view class="menu-item" @click="navTo('/pages/user/editUser')">
+					<view class="left">
+						<image src="../../static/images/icon_set.png"></image>
+						<view class="label">设置</view>
+					</view>
+					<view class="right">
+						<image src="../../static/images/right_arrow.png"></image>
+					</view>
+				</view>
+			</view>
+			<view class="btn-box" >
+				<view class="sub-btn" @click="showLogout()">退出登录</view>
+			</view>
+		</view>
+		<u-modal :show="show" title="提示" :showCancelButton="true"  @cancel="hideLogout()" @confirm="logout()" content='确认退出吗?'></u-modal>
+	</view>
+</template>
+
+<script>
+	
+	import { getCompanyUser } from '@/api/user.js'
+ 
+	export default {
+		data() {
+			return {
+				show:false,
+				user:null,
+			}
+		},
+		onShow() {
+			this.getCompanyUser()
+		
+		},
+		methods: {
+			
+			showLogout(){
+				this.show=true;
+			},
+			hideLogout(){
+				this.show=false;	
+			},
+			logout(){
+				this.utils.logout();
+				uni.$emit('closeWebSocket');
+				uni.reLaunch({
+					url: '/pages/auth/login',
+					animationType: 'pop-in',
+					animationDuration: 100
+				})
+			},
+			navTo(url){
+				console.log(url)
+				uni.navigateTo({
+					url
+				})
+			},
+			getCompanyUser(){
+				var data = {
+				};
+				getCompanyUser(data).then(res => {
+					if(res.code==200){
+						this.user=res.data;
+					}else{
+						
+					}
+				});
+			},
+		}
+	}
+</script>
+
+<style lang="scss">
+	page{
+		height: 100%;
+		background: #f6f6f6;
+	}
+</style>
+<style scoped lang="scss">
+	.content{
+		position: relative;
+		height: 100%;
+		width: 100%;
+		.bg{
+			width: 100%;
+			height: 200upx;
+			position: absolute;
+			top: 0;
+			left: 0;
+			z-index: 1;
+			background: linear-gradient(to bottom,#115296, #b4d8ff);
+			// border-radius: 0rpx 0rpx 60rpx 60rpx;
+		}
+		.cont-box{
+			position: relative;
+			z-index: 2;
+			padding: 30rpx 30rpx 200rpx;
+			.user-cont{
+				box-shadow: 0px 0px 5px 2px rgba(0,0,0,0.05);
+				padding: 30rpx;
+				background-color: #fff;
+				border-radius: 15rpx;
+				.user-box{
+					width: 100%;
+					display: flex;
+					align-items: center;
+					justify-content: flex-start;
+					.left{
+						flex:1;
+						display: flex;
+						align-items: center;
+						justify-content: flex-start;
+						image{
+							border-radius: 50%;
+							width:120rpx;
+							height:120rpx;
+						}
+						.user{
+							margin-left: 20rpx;
+							width: calc(100% - 140rpx);
+							display: flex;
+							flex-direction: column;
+							align-items: flex-start;
+							justify-content: flex-start;
+							.username{
+								font-size: 38rpx;
+								font-family: PingFang SC;
+								color: #111;
+								font-weight: bold;
+							}
+							.account{
+								padding: 5rpx 20rpx;
+								border-radius: 30rpx;
+								background-color: #115296;
+								margin-top: 20rpx;
+								font-size: 24rpx;
+								font-family: PingFang SC;
+								color: #fff;
+							}
+						}
+					}
+					.right{
+						image{
+							width: 15rpx;
+							height:30rpx;
+						}
+						
+					}
+					
+				}
+				.company{
+					margin-top: 30rpx;
+					display: flex;
+					align-items: center;
+					justify-content: flex-start;
+					image{
+						width:40rpx;
+						height:40rpx;
+					}
+					.name{
+						margin-left: 15rpx;
+						font-size: 28rpx;
+						color: #111;
+					}
+				}
+			}
+			
+			.menu-box{
+				box-shadow: 0px 0px 5px 2px rgba(0,0,0,0.05);
+				width: 100%;
+				margin-top: 30rpx;
+				padding: 30rpx;
+				background-color: #fff;
+				border-radius: 15rpx;
+				.title-box{
+					display: flex;
+					align-items: center;
+					justify-content: flex-start;
+					.icon{
+						width: 30rpx;
+						height:30rpx;
+					}
+					.title{
+						margin-left: 10rpx;
+						font-size: 28rpx;
+						font-family: PingFang SC;
+						color: #111;
+					}
+				}
+				.line{
+					margin-top: 15rpx;
+					height: 0.5rpx;
+					width: 100%;
+					background-color: #efefef;
+				}
+				.menus{
+					margin-top: 30rpx;
+					display: flex;
+					align-items: center;
+					justify-content: flex-start;
+					flex-wrap: wrap;
+				}
+				.menu-item{
+					display: flex;
+					flex-direction: column;
+					align-items: center;
+					justify-content: center;
+					width: 25%;
+					margin-bottom: 20rpx;
+					image{
+						width:60rpx;
+						height:60rpx;
+					}
+					.m-name{
+						margin-top: 10rpx;
+						font-size: 24rpx;
+						font-family: PingFang SC;
+						color: #111;
+					}
+				}
+				
+			}
+			.menu-box1{
+				width: 100%;
+				margin-top: 30rpx;
+				padding: 0rpx 30rpx;
+				background-color: #fff;
+				border-radius: 15rpx;
+				.menu-item{
+					width: 100%;
+					display: flex;
+					align-items: center;
+					justify-content: flex-start;
+					line-height: 100rpx;
+					.left{
+						flex:1;
+						display: flex;
+						align-items: center;
+						justify-content: flex-start;
+						image{
+							width: 30rpx;
+							height:30rpx;
+						}
+						.label{
+							margin-left: 10rpx;
+						}
+					}
+					.right{
+						image{
+							width: 10rpx;
+							height:15rpx;
+						}
+						
+					}
+				}
+			}
+		}
+	}
+	.btn-box{
+		margin: 60rpx 0rpx 30rpx;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		.sub-btn{
+			// box-shadow: 0px 0px 5px 2px rgba(0,0,0,0.05);
+			border: 1rpx solid #f8f8f8;
+			background: #FFFFFF;
+			width: 100%;
+			height: 88upx;
+			line-height: 88upx;
+			text-align: center;
+			font-size: 30upx;
+			font-family: PingFang SC;
+			color: #111;
+		}
+	}
+</style>

+ 168 - 0
pages/user/qwSop/sop.vue

@@ -0,0 +1,168 @@
+<template>
+	<view class="content">
+		<mescroll-body top="0rpx" bottom="0" ref="mescrollRef" @init="mescrollInit"    :down="downOption" :up="upOption" @down="downCallback" @up="upCallback">
+		<view class="sop-list">
+			<view class="sop-item"  v-for="(item) in dataList" @click.stop="navTo('/pages/user/qwSop/sopLosList?sopId='+item.id)"   >
+				<view class="name-box">
+					<view class="name">{{item.name}}</view>
+				</view>
+				<view class="desc-box">
+					<view class="label">创建时间:</view>
+					<view class="value">{{item.createTime}}</view>
+				</view>
+			</view>
+		</view>
+		</mescroll-body>
+	</view>
+</template>
+
+<script>
+	import MescrollMixin from "@/uni_modules/mescroll-uni/components/mescroll-uni/mescroll-mixins.js";
+	
+	import {getQwSopList} from '@/api/qw.js'
+	export default {
+		mixins: [MescrollMixin], // 使用mixin
+		data() {
+			return {
+				mescroll:null,
+				downOption: {
+					auto:false//不要自动加载
+				},
+				upOption: {
+					onScroll:false,
+					use: true, // 是否启用上拉加载; 默认true
+					page: {
+						num: 0, // 当前页码,默认0,回调之前会加1,即callback(page)会从1开始
+						size: 10 // 每页数据的数量,默认10
+					},
+					noMoreSize: 10, // 配置列表的总数量要大于等于5条才显示'-- END --'的提示
+					textNoMore:"已经到底了",
+					empty: {
+						icon:'/static/images/empty.png',
+						tip: '暂无数据'
+					}
+				},
+				dataList: []
+			}
+		},
+		onShow() {
+		},
+		methods: {
+			mescrollInit(mescroll) {
+				this.mescroll = mescroll;
+			},
+			/*下拉刷新的回调 */
+			downCallback(mescroll) {
+				mescroll.resetUpScroll()
+			},
+			upCallback(page) {
+				//联网加载数据
+				var that = this;
+				var data={
+					pageNum: page.num,
+					pageSize: page.size
+				};
+				uni.showLoading({
+					title:"加载中..."
+				})
+				getQwSopList(data).then(res => {
+					uni.hideLoading()
+					if(res.code==200){
+						//设置列表数据
+						if (page.num == 1) {
+							that.dataList = res.data.list; 
+							
+						} else {
+							that.dataList = that.dataList.concat(res.data.list);
+							 
+						}
+						that.mescroll.endBySize(res.data.list.length, res.data.total);
+						
+					}else{
+						uni.showToast({
+							icon:'none',
+							title: "请求失败",
+						});
+						that.dataList = null;
+						that.mescroll.endErr();
+					}
+				});
+			},
+			navTo(url){
+				uni.navigateTo({
+					url: url
+				})
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	page{
+		
+		height: 100%;
+		background: #f6f6f6;
+	}
+</style>
+<style scoped lang="scss">
+	.content{
+		height: 100%;
+		padding: 0rpx;
+		.tabs{
+			z-index: 10000;
+			position: absolute;
+			top:0rpx;
+			left:0rpx;
+			height: 88rpx;
+			background-color: #fff;
+			width: 100%;
+		}
+		.sop-list{
+			display: flex;
+			flex-direction: column;
+			padding: 15rpx;
+			.sop-item{
+				padding: 15rpx;
+				border-radius: 15rpx;
+				background-color: #fff;
+				margin-bottom: 15rpx;
+				width: 100%;
+				.name-box{
+					width: 100%;
+					display: flex;
+					align-items: center;
+					justify-content: flex-start;
+					.name{
+						flex: 1;
+						font-size: 32rpx;
+						color:#111;
+					}
+					.btns{
+						.btn{
+							margin-left: 10rpx;
+							width: 40rpx;
+							height:40rpx;
+						}
+					}
+				}
+				.desc-box{
+					margin-top: 15rpx;
+					width: 100%;
+					display: flex;
+					align-items: center;
+					justify-content: flex-start;
+					.label{
+						font-size: 28rpx;
+						color: #a8a8a8;
+					}
+					.value{
+						font-size: 28rpx;
+						color: #a8a8a8;
+					}
+					
+					
+				}
+			}
+		}
+	}
+</style>

+ 90 - 0
pages/user/qwSop/sopLogsDetails.vue

@@ -0,0 +1,90 @@
+<template>
+	<view class="content">
+		<view class="info-detail" v-if="item!=null">
+			<view class="item">
+				<text class="label">接收者:</text>
+				<text class="text">{{item.externalUserName}}</text>
+			</view>
+			<view class="item">
+				<text class="label">发送时间:</text>
+				<text class="text">{{item.sendTime}}</text>
+			</view>
+			<view class="item">
+				<text class="label">实际发送时间:</text>
+				<text class="text">{{item.realSendTime}}</text>
+			</view>
+			<view class="item" v-for="(cont,index ) in contents">
+				<text class="label">发送内容({{index+1}}):</text>
+				<text class="text" v-html="cont.value"> </text>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	import {getQwSopSendCustomerList} from '@/api/qw.js'
+	export default {
+		data() {
+			return {
+				id:null,
+				item:null,
+				contents:[],
+			}
+		},
+		onLoad(option) {
+			// this.id = option.id;
+			this.item=JSON.parse(uni.getStorageSync("sop"));
+			console.log(this.item)
+			this.contents=JSON.parse(this.item.contentJson);
+		},
+		onShow() {
+			 
+		},
+		methods: {
+			 
+			getUserInfoByUserId(userId){
+				var data = {userId:userId};
+				var that=this;
+				getUserInfoByUserId(data).then(
+					res => {
+						that.bindUser(res);
+						
+					},
+					rej => {}
+				);
+			},
+			 
+		}
+	}
+</script>
+
+<style lang="scss">
+	page{
+		
+		height: 100%;
+		background: #f6f6f6;
+	}
+</style>
+<style scoped lang="scss">
+	.content{
+		// 详细信息
+		.info-detail{
+			padding: 0 24upx;
+			background-color: #fff;
+			.item{
+				padding: 20upx 0;
+				border-bottom: 1px solid #f7f7f7;
+				.label{
+					font-size: 30upx;
+					color: #999;
+					margin-right: 20upx;
+				}
+				.text{
+					font-size: 30upx;
+					color: #333;
+				}
+				 
+			}
+		}
+	}
+</style>

+ 302 - 0
pages/user/qwSop/sopLosList.vue

@@ -0,0 +1,302 @@
+<template>
+	<view class="content">
+		<view class="top-box">
+			<view class="tabs">
+			 	<u-tabs
+			 	 :scrollable="false"
+			 	 :list="tabs"  
+			 	  lineColor="#115296"
+			 	@change="tagChange">
+			 	</u-tabs>
+			</view>
+			
+		</view>
+		
+		<mescroll-body top="88rpx" bottom="0" ref="mescrollRef" @init="mescrollInit"    :down="downOption" :up="upOption" @down="downCallback" @up="upCallback">
+		<view class="sop-list">
+			<view class="sop-item"  v-for="(item) in dataList" @click.stop="openDetails(item)"   >
+				<view class="name-box">
+					<view class="name">{{item.externalUserName}}</view>
+					
+				</view>
+				<view class="desc-box">
+					<view class="label">发送时间:</view>
+					<view class="value">{{item.sendTime}}</view>
+				</view>
+				<view class="desc-box">
+					<view class="label">所属销售:</view>
+					<view class="value">{{item.userName}}</view>
+				</view>
+			</view>
+		</view>
+		</mescroll-body>
+		<view class="footer-btns">
+			<u-button :disabled="!sendFlag" type="success" wid @click="sendMsg()" text="启动群发"></u-button>
+		</view>
+	</view>
+</template>
+
+<script>
+	import MescrollMixin from "@/uni_modules/mescroll-uni/components/mescroll-uni/mescroll-mixins.js";
+	import {updateQwSopLogs,getQwSopLogsList} from '@/api/qw.js'
+	export default {
+		mixins: [MescrollMixin], // 使用mixin
+		data() {
+			return {
+				sendFlag:true,
+				hasNextPage:false,
+				totalCount:0,
+				sopId:null,
+				status:3,
+				tabs:[
+					{
+						id:3,
+						name:'待发送'
+					},
+					{
+						id:1,
+						name:'发送成功'
+					},
+					{
+						id:0,
+						name:'发送失败'
+					}
+				],
+				mescroll:null,
+				downOption: {
+					auto:false//不要自动加载
+				},
+				upOption: {
+					onScroll:false,
+					use: true, // 是否启用上拉加载; 默认true
+					page: {
+						num: 0, // 当前页码,默认0,回调之前会加1,即callback(page)会从1开始
+						size: 10 // 每页数据的数量,默认10
+					},
+					noMoreSize: 10, // 配置列表的总数量要大于等于5条才显示'-- END --'的提示
+					textNoMore:"已经到底了",
+					empty: {
+						icon:'/static/images/empty.png',
+						tip: '暂无数据'
+					}
+				},
+				dataList: []
+			}
+		},
+		onLoad(option) {
+			this.sopId=option.sopId;
+			var that=this;
+			uni.$on('sendSop', (item) => {
+				console.log(JSON.parse(item.data))
+				var data=JSON.parse(item.data);
+				var param={
+					sendStatus:1,
+					id: data.id,
+					receivingStatus: data.receivingStatus,
+					remark:item.remark
+				};
+				updateQwSopLogs(param).then(res => {
+					
+				});
+				that.sendMsgOp();
+			})
+		},
+		onShow() {
+		},
+		methods: {
+			sendMsgOp(){
+				if(this.sendFlag){
+					return;
+				}
+				//循环发送 先获取一条,更新一条这样发送
+				this.mescroll.resetUpScroll()
+				var userId=uni.getStorageSync('companyUserId') ;
+				var that=this;
+				setTimeout(function(){
+					if(that.dataList.length>0){
+						var data={cmd:"sendSop", data:that.dataList[0],userId:"p-"+userId};
+						uni.$emit('sendMsg',data);  
+					}
+					else{
+						that.sendFlag=true;
+						uni.showToast({
+							icon:'none',
+							title: "已完成",
+						});
+					}
+				},5000);
+				
+			},
+			sendMsg(){
+				if(!this.sendFlag){
+					uni.showToast({
+						icon:'none',
+						title: "运行中...",
+					});
+					return;
+				}
+				var that=this;
+				uni.showModal({
+					title:"提示",
+					content:"确认启动自动发送吗?",
+					showCancel:true,
+					cancelText:'取消',
+					confirmText:'确定',
+					success:res=>{
+						if(res.confirm){
+							this.sendFlag=false;
+							that.sendMsgOp()
+							 
+						}else{
+							// 否则点击了取消  
+						}
+					}
+				})
+				
+			},
+			openDetails(item){
+				uni.setStorageSync("sop",JSON.stringify(item))
+				uni.navigateTo({
+					url:"sopLogsDetails"
+				})
+			},
+			tagChange(item){
+				this.status=item.id
+				this.mescroll.resetUpScroll()
+			},
+			mescrollInit(mescroll) {
+				this.mescroll = mescroll;
+			},
+			/*下拉刷新的回调 */
+			downCallback(mescroll) {
+				mescroll.resetUpScroll()
+			},
+			upCallback(page) {
+				//联网加载数据
+				var that = this;
+				var data={
+					sopId:this.sopId,
+					sendStatus:this.status,
+					pageNum: page.num,
+					pageSize: page.size
+				};
+				uni.showLoading({
+					title:"加载中..."
+				})
+				getQwSopLogsList(data).then(res => {
+					uni.hideLoading()
+					if(res.code==200){
+						//设置列表数据
+						if (page.num == 1) {
+							that.dataList = res.data.list; 
+							
+						} else {
+							that.dataList = that.dataList.concat(res.data.list);
+							 
+						}
+						that.hasNextPage=res.data.hasNextPage
+						that.mescroll.endBySize(res.data.list.length, res.data.total);
+						
+					}else{
+						uni.showToast({
+							icon:'none',
+							title: "请求失败",
+						});
+						that.dataList = null;
+						that.mescroll.endErr();
+					}
+				});
+			},
+			navTo(url){
+				uni.navigateTo({
+					url: url
+				})
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	page{
+		
+		height: 100%;
+		background: #f6f6f6;
+	}
+</style>
+<style scoped lang="scss">
+	.content{
+		height: 100%;
+		padding: 0rpx;
+		.top-box{
+			width: 100%;
+			z-index: 10000;
+			position: absolute;
+			top:0rpx;
+			left:0rpx;
+			
+			.tabs{
+				height: 88rpx;
+				background-color: #fff;
+				width: 100%;
+			}
+			
+		}
+		
+		.footer-btns{
+			padding:15rpx;
+			width: 100%;
+			z-index: 10000;
+			position: absolute;
+			bottom:30rpx;
+			left:0rpx;
+		}
+		
+		.sop-list{
+			display: flex;
+			flex-direction: column;
+			padding: 15rpx;
+			.sop-item{
+				padding: 15rpx;
+				border-radius: 15rpx;
+				background-color: #fff;
+				margin-bottom: 15rpx;
+				width: 100%;
+				.name-box{
+					width: 100%;
+					display: flex;
+					align-items: center;
+					justify-content: flex-start;
+					.name{
+						flex: 1;
+						font-size: 38rpx;
+						color:#111;
+					}
+					.btns{
+						.btn{
+							margin-left: 10rpx;
+							width: 40rpx;
+							height:40rpx;
+						}
+					}
+				}
+				.desc-box{
+					margin-top: 15rpx;
+					width: 100%;
+					display: flex;
+					align-items: center;
+					justify-content: flex-start;
+					.label{
+						font-size: 28rpx;
+						color: #a8a8a8;
+					}
+					.value{
+						font-size: 28rpx;
+						color: #a8a8a8;
+					}
+					
+					
+				}
+			}
+		}
+	}
+</style>

+ 230 - 0
pages/user/userInfo.vue

@@ -0,0 +1,230 @@
+<template>
+	<view class="content">
+		<!-- 个人信息 -->
+		<view class="user-info">
+			<view class="info-box">
+				<view class="left">
+					<view class="head-box">
+						<image class="img" :src="avatar" mode="aspectFill"></image>
+					</view>
+					<view class="info">
+						<text class="name">{{nickName}}</text>
+						<text class="title">{{phonenumber}}</text>
+					</view>
+				</view>
+				<image   class="right"  src="../../static/images/icon_edit.png" mode="aspectFill" @click="editInfo"></image>
+			</view>
+			<!-- 公司 -->
+			<view class="comp-info">
+				<image class="img" src="../../static/images/icon_comp_white.png" mode="aspectFill"></image>
+				<text class="text">{{deptName}}</text>
+			</view>
+		</view>
+		<!-- 详细信息 -->
+		<view class="info-detail">
+			<view class="item">
+				<text class="label">姓名</text>
+				<text class="text">{{nickName}}</text>
+			</view>
+			<view class="item">
+				<text class="label">性别</text>
+				<text class="text">{{sex}}</text>
+			</view>
+			<view class="item column" @click="callPhone(phonenumber)">
+				<view class="left">
+					<text class="label">手机</text>
+					<text class="text">{{phonenumber}}</text>
+				</view>
+				<image class="img" src="/static/images/icon_phone.png" mode="aspectFill"  ></image>
+			</view>
+			<view class="item">
+				<text class="label">邮箱</text>
+				<text class="text">{{email}}</text>
+			</view>
+			<view class="item">
+				<text class="label">部门</text>
+				<text class="text">{{deptName}}</text>
+			</view>
+			<view class="item">
+				<text class="label">岗位</text>
+				<text class="text">{{postNames}}</text>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	import {getCompanyUser} from '@/api/user.js';
+	export default {
+		data() {
+			return {
+				avatar:"/static/images/detault_head.jpg", 
+				nickName:"",
+				deptName:"",
+				postNames:"",
+				phonenumber:"",
+				email:"",
+				sex:"",
+				isShow:false,
+				userId:undefined,
+			}
+		},
+		onLoad(option) {
+			// 修改顶部导航背景色
+			// uni.setNavigationBarColor({
+			// 	frontColor: '#ffffff',
+			// 	backgroundColor: '#4BC9B1',
+			// 	animation: {
+			// 		duration: 400,
+			// 		timingFunc: 'easeIn'
+			// 	}
+			// })
+			
+		},
+		onShow() {
+			this.getCompanyUser();
+		},
+		methods: {
+			bindUser(data){
+				var that=this;
+				that.nickName=data.nickName;
+				that.deptName=data.dept.deptName;
+				that.phonenumber=data.phonenumber;
+				that.email=data.email;
+				if(data.sex==0){
+					that.sex="男";
+				}
+				else if(data.sex==1){
+					that.sex="女";
+				}
+				else if(data.sex==2){
+					that.sex="未知";
+				}
+				if(!that.utils.isEmpty(data.avatar)){
+					that.avatar=uni.getStorageSync('requestPath')+data.avatar;
+				}
+			},
+			getCompanyUser(){
+				var data = {};
+				var that=this;
+				getCompanyUser(data).then(
+					res => {
+						that.bindUser(res.data);
+						
+					},
+					rej => {}
+				);
+			},
+			// 拨打电话
+			callPhone(tel){
+				uni.makePhoneCall({
+					phoneNumber: tel
+				})
+			},
+			// 个人信息编辑
+			editInfo() {
+				uni.navigateTo({
+					url: 'editUser'
+				})
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	page{
+		background: #fff;
+	}
+</style>
+<style scoped lang="scss">
+	.content{
+		// 个人信息
+		.user-info{
+			background: #115296;
+			padding: 0 30upx;
+			.info-box{
+				display: flex;
+				align-items: center;
+				justify-content: space-between;
+				.left{
+					display: flex;
+					align-items: center;
+					justify-content: center;
+					padding: 20upx 0;
+					.head-box{
+						width: 120upx;
+						height: 120upx;
+						line-height: 100upx;
+						font-size: 30upx;
+						color: #fff;
+						text-align: center;
+						margin-right: 20upx;
+						.img{
+							border-radius: 50%;
+							width: 100%;
+							height: 100%;
+						}
+					}
+					.info{
+						height: 80upx;
+						display: flex;
+						flex-direction: column;
+						justify-content: space-between;
+						.name{
+							font-size: 30upx;
+							color: #fff;
+						}
+						.title{
+							font-size: 28upx;
+							color: #fff;
+						}
+					}
+				}
+				.right{
+					width: 40upx;
+					height: 40upx;
+				}
+			}
+			.comp-info{
+				padding: 20upx 0 40upx;
+				display: flex;
+				align-items: center;
+				.img{
+					width: 30upx;
+					height: 30upx;
+					margin-right: 20upx;
+				}
+				.text{
+					font-size: 30upx;
+					color: #fff;
+				}
+			}
+		}
+		// 详细信息
+		.info-detail{
+			padding: 0 24upx;
+			.item{
+				padding: 20upx 0;
+				border-bottom: 1px solid #f7f7f7;
+				.label{
+					font-size: 30upx;
+					color: #999;
+					margin-right: 20upx;
+				}
+				.text{
+					font-size: 30upx;
+					color: #333;
+				}
+				&.column{
+					display: flex;
+					align-items: center;
+					justify-content: space-between;
+					.img{
+						width: 50upx;
+						height: 50upx;
+					}
+				}
+			}
+		}
+	}
+</style>

+ 257 - 0
pages/user/users/userInfo.vue

@@ -0,0 +1,257 @@
+<template>
+	<view class="content">
+		<!-- 个人信息 -->
+		<view class="user-info">
+			<view class="info-box">
+				<view class="left">
+					<view class="head-box">
+						<image class="img" :src="avatar" mode="aspectFill"></image>
+					</view>
+					<view class="info">
+						<text class="name">{{nickName}}</text>
+						<text class="title">{{postNames}}</text>
+					</view>
+				</view>
+				<image v-if="!isShow" class="right" src="/static/images/icon_edit.png" mode="aspectFill" @click="editInfo"></image>
+			</view>
+			<!-- 公司 -->
+			<view class="comp-info">
+				<image class="img" src="/static/images/icon_comp_white.png" mode="aspectFill"></image>
+				<text class="text">{{deptName}}</text>
+			</view>
+		</view>
+		<!-- 详细信息 -->
+		<view class="info-detail">
+			<view class="item">
+				<text class="label">姓名</text>
+				<text class="text">{{nickName}}</text>
+			</view>
+			<view class="item">
+				<text class="label">性别</text>
+				<text class="text">{{sex}}</text>
+			</view>
+			<view class="item column" @click="callPhone(phonenumber)">
+				<view class="left">
+					<text class="label">手机</text>
+					<text class="text">{{phonenumber}}</text>
+				</view>
+				<image class="img" src="/static/images/icon_phone_left.png" mode="aspectFill"  ></image>
+			</view>
+			<view class="item">
+				<text class="label">邮箱</text>
+				<text class="text">{{email}}</text>
+			</view>
+			<view class="item">
+				<text class="label">部门</text>
+				<text class="text">{{deptName}}</text>
+			</view>
+			<view class="item">
+				<text class="label">岗位</text>
+				<text class="text">{{postNames}}</text>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	import {getUserInfoByUserId,getUserInfo} from '@/api/user.js';
+	export default {
+		data() {
+			return {
+				avatar:"/static/images/default.png", 
+				nickName:"",
+				deptName:"",
+				postNames:"",
+				phonenumber:"",
+				email:"",
+				sex:"",
+				isShow:false,
+				userId:undefined,
+			}
+		},
+		onLoad(option) {
+			// 修改顶部导航背景色
+			// uni.setNavigationBarColor({
+			// 	frontColor: '#ffffff',
+			// 	backgroundColor: '#4BC9B1',
+			// 	animation: {
+			// 		duration: 400,
+			// 		timingFunc: 'easeIn'
+			// 	}
+			// })
+			if(!this.utils.isEmpty(option.userId)){
+				this.userId = option.userId;
+				this.isShow=true;
+			}
+			
+		},
+		onShow() {
+			if(this.isShow){
+				this.getUserInfoByUserId(this.userId);
+			}
+			else{
+				this.getUserInfo();
+			}
+		},
+		methods: {
+			bindUser(data){
+				var that=this;
+				that.nickName=data.user.nickName;
+				that.deptName=data.user.dept.deptName;
+				that.posts=data.post;
+				that.phonenumber=data.user.phonenumber;
+				that.email=data.user.email;
+				if(data.user.sex==0){
+					that.sex="男";
+				}
+				else if(data.user.sex==1){
+					that.sex="女";
+				}
+				else if(data.user.sex==2){
+					that.sex="未知";
+				}
+				if(data.post!=null&&data.post.length>0){
+					var posts=[];
+					data.post.forEach( (v,i) => {
+						posts.push(v);
+					},this);
+					that.postNames=posts.toString()
+				}
+				if(!that.utils.isEmpty(data.user.avatar)){
+					that.avatar=uni.getStorageSync('requestPath')+data.user.avatar;
+				}
+			},
+			getUserInfoByUserId(userId){
+				var data = {userId:userId};
+				var that=this;
+				getUserInfoByUserId(data).then(
+					res => {
+						that.bindUser(res);
+						
+					},
+					rej => {}
+				);
+			},
+			getUserInfo(){
+				var data = {};
+				var that=this;
+				getUserInfo(data).then(
+					res => {
+						that.bindUser(res);
+					},
+					rej => {}
+				);
+			},
+			// 拨打电话
+			callPhone(tel){
+				uni.makePhoneCall({
+					phoneNumber: tel
+				})
+			},
+			// 个人信息编辑
+			editInfo() {
+				uni.navigateTo({
+					url: '../editUser'
+				})
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	page{
+		background: #fff;
+	}
+</style>
+<style scoped lang="scss">
+	.content{
+		// 个人信息
+		.user-info{
+			background: #115296;
+			padding: 0 30upx;
+			.info-box{
+				display: flex;
+				align-items: center;
+				justify-content: space-between;
+				.left{
+					display: flex;
+					align-items: center;
+					justify-content: center;
+					padding: 20upx 0;
+					.head-box{
+						width: 120upx;
+						height: 120upx;
+						line-height: 100upx;
+						font-size: 30upx;
+						color: #fff;
+						text-align: center;
+						margin-right: 20upx;
+						.img{
+							border-radius: 50%;
+							width: 100%;
+							height: 100%;
+						}
+					}
+					.info{
+						height: 80upx;
+						display: flex;
+						flex-direction: column;
+						justify-content: space-between;
+						.name{
+							font-size: 30upx;
+							color: #fff;
+						}
+						.title{
+							font-size: 28upx;
+							color: #fff;
+						}
+					}
+				}
+				.right{
+					width: 40upx;
+					height: 40upx;
+				}
+			}
+			.comp-info{
+				padding: 20upx 0 40upx;
+				display: flex;
+				align-items: center;
+				.img{
+					width: 30upx;
+					height: 30upx;
+					margin-right: 20upx;
+				}
+				.text{
+					font-size: 30upx;
+					color: #fff;
+				}
+			}
+		}
+		// 详细信息
+		.info-detail{
+			padding: 0 24upx;
+			.item{
+				padding: 20upx 0;
+				border-bottom: 1px solid #f7f7f7;
+				.label{
+					font-size: 30upx;
+					color: #999;
+					margin-right: 20upx;
+				}
+				.text{
+					font-size: 30upx;
+					color: #333;
+				}
+				&.column{
+					display: flex;
+					align-items: center;
+					justify-content: space-between;
+					.img{
+						width: 50upx;
+						height: 50upx;
+					}
+				}
+			}
+		}
+	}
+</style>

+ 256 - 0
pages/user/users/users.vue

@@ -0,0 +1,256 @@
+<template>
+	<view>
+		<view class="top">
+			<u-search placeholder="输入姓名搜索" v-model="searchContent" @custom="search()" @search="search()"></u-search>
+		</view>
+		<scroll-view scroll-y class="indexes" :scroll-into-view="'indexes-'+ listCurID" :style="[{height:'calc(100vh -  88rpx)'}]"
+		 :scroll-with-animation="true" :enable-back-to-top="true">
+			<view v-for="(item,index) in list" :key="index">
+				<view :class="'indexItem-' + item.firstLetter" :id="'indexes-' + item.firstLetter" :data-index="item.firstLetter">
+					<view class="zm">{{item.firstLetter}}</view>
+					<view class="user-list">
+						<view class="user-item" v-for="(subitem) in item.list" @click="navTo(subitem)" >
+							<view class="avatar" >
+								<image class="img" :src="subitem.avatar?(baseUrl+subitem.avatar):avatar" mode="aspectFill"></image>
+							</view> 
+							<view class="content">
+								<view class="name">
+									{{subitem.nickName}}
+								</view>
+								<view class="dept">
+									{{subitem.deptName}}
+								</view>
+							</view>
+						</view>
+					</view>
+				</view>
+			</view>
+		</scroll-view>
+		<view class="indexBar" :style="[{height:'calc(100vh - 88rpx)'}]">
+			<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.firstLetter}}</view>
+			</view>
+		</view>
+		<!--选择显示-->
+		<view v-show="!hidden" class="indexToast">
+			{{listCur}}
+		</view>
+	</view>
+</template>
+
+<script>
+	import {getAllUsers} from '@/api/user.js';
+	export default {
+		data() {
+			return {
+				baseUrl:uni.getStorageSync('requestPath'),
+				avatar:"/static/images/default.png",
+				CustomBar: 0,
+				hidden: true,
+				listCurID: '',
+				list: [],
+				listCur: '',
+				searchContent:"",
+			}
+		},
+		onLoad() {
+			this.getUser();
+		},
+		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: {
+			getUser(){
+				var data = {searchKey:this.searchContent};
+				var that=this;
+				getAllUsers(data).then(
+					res => {
+						if(res.code==200){
+							 that.list=res.users;
+						}
+					},
+					rej => {}
+				);
+			},
+			//获取文字信息
+			getCur(e) {
+				this.hidden = false;
+				this.listCur = this.list[e.target.id].firstLetter;
+			},
+			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);
+					console.log(num);
+					if(num<that.list.length){
+						this.listCur = that.list[num].firstLetter
+					}
+				};
+			},
+			
+			//触发全部开始选择
+			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].firstLetter;
+						that.movableY = i * 20
+						return false
+					}
+				}
+			},
+			navTo(subitem){
+				uni.navigateTo({
+					url: './userInfo?userId='+subitem.userId
+				})
+			},
+			search(){
+				this.getUser()
+			}
+			
+		}
+	}
+</script>
+<style  >
+	 
+	page {
+		 
+	}
+	.top{
+		height: 88rpx;
+		background-color: #fff;
+		padding: 0rpx 15rpx;
+	}
+	.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;
+	}
+	.zm{
+		height:50rpx;
+		padding: 0rpx 20rpx;
+	}
+	.user-list{
+		
+		background-color: #fff;
+		 
+	}
+	.user-item{
+		padding: 15rpx;
+		width: 100%;
+		display: flex;
+		align-items: center;
+		justify-content: flex-start;
+		border-bottom: 1px solid #f7f7f7;
+	}
+	.user-item .avatar{
+		width: 100upx;
+		height: 100upx;
+	}
+	.user-item .avatar .img{
+		border-radius: 50%;
+		width: 100%;
+		height: 100%;
+	}
+	.user-item .content{
+		margin-left: 15rpx;
+	}
+	.user-item .content .name{
+		font-size: 26rpx;
+		color: #111;
+	}
+	.user-item .content .dept{
+		font-size: 24rpx;
+		color: #888;
+		margin-top: 15rpx;
+		
+	}
+</style>

+ 46 - 0
router/router.js

@@ -0,0 +1,46 @@
+import Vue from 'vue';
+import Router from 'uni-simple-router';
+
+Vue.use(Router)
+import utils from '../utils/common.js'
+//初始化
+const router = new Router({
+    routes:ROUTES //路由表
+});
+
+const filters = ['/pages/auth/login','/pages/common/launch']
+
+//进入的路由   要出去的路由
+router.beforeEach((to, from, next) => {
+	console.log("路由进入")
+	console.log(to)
+	let index = filters.indexOf(to.path);
+	//过滤
+	console.log(index)
+	if (index > -1) {
+		next();
+		return;
+	}
+	var data=utils.isLogin();
+	console.log(data)
+	if(utils.isLogin()){
+		console.log("ok")
+		next();
+	}
+	else{
+		next({path: "/pages/auth/login"});
+	}
+  //判断是否校验路由
+  // if (to.meta.isAure) {
+  //   let name = sessionStorage.getItem('username');
+  //   //有状态,并且存在sessionStorage,则next,否则返回登陆页面
+  //   if (name) {
+  //     next();
+  //   } else {
+  //     next({path: "/login",query:{redirect:to.name}});
+  //   }
+  // } else {
+  //   next();
+  // }
+});
+export default router;

BIN
static/account.png


BIN
static/images/customer.png


BIN
static/images/default.png


BIN
static/images/empty.png


BIN
static/images/home.png


BIN
static/images/home_select.png


BIN
static/images/icon_about_us.png


BIN
static/images/icon_ai.png


BIN
static/images/icon_comp.png


BIN
static/images/icon_comp_white.png


BIN
static/images/icon_cust.png


BIN
static/images/icon_edit.png


BIN
static/images/icon_phone.png


BIN
static/images/icon_set.png


BIN
static/images/menu_info.png


BIN
static/images/my.png


BIN
static/images/my_select.png


BIN
static/images/office.png


BIN
static/images/order.png


BIN
static/images/order_select.png


BIN
static/images/phone.png


BIN
static/images/report.png


BIN
static/images/report_select.png


BIN
static/images/right_arrow.png


BIN
static/images/send.png


BIN
static/images/sms.png


BIN
static/logo.png


BIN
static/password.png


+ 77 - 0
uni.scss

@@ -0,0 +1,77 @@
+/**
+ * 这里是uni-app内置的常用样式变量
+ *
+ * uni-app 官方扩展插件及插件市场(https://ext.dcloud.net.cn)上很多三方插件均使用了这些样式变量
+ * 如果你是插件开发者,建议你使用scss预处理,并在插件代码中直接使用这些变量(无需 import 这个文件),方便用户通过搭积木的方式开发整体风格一致的App
+ *
+ */
+
+/**
+ * 如果你是App开发者(插件使用者),你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能
+ *
+ * 如果你的项目同样使用了scss预处理,你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件
+ */
+
+/* 颜色变量 */
+@import '@/uni_modules/uview-ui/theme.scss';
+
+/* 行为相关颜色 */
+$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-font-size-sm:12px;
+$uni-font-size-base:14px;
+$uni-font-size-lg:16;
+
+/* 图片尺寸 */
+$uni-img-size-sm:20px;
+$uni-img-size-base:26px;
+$uni-img-size-lg:40px;
+
+/* Border Radius */
+$uni-border-radius-sm: 2px;
+$uni-border-radius-base: 3px;
+$uni-border-radius-lg: 6px;
+$uni-border-radius-circle: 50%;
+
+/* 水平间距 */
+$uni-spacing-row-sm: 5px;
+$uni-spacing-row-base: 10px;
+$uni-spacing-row-lg: 15px;
+
+/* 垂直间距 */
+$uni-spacing-col-sm: 4px;
+$uni-spacing-col-base: 8px;
+$uni-spacing-col-lg: 12px;
+
+/* 透明度 */
+$uni-opacity-disabled: 0.3; // 组件禁用态的透明度
+
+/* 文章场景相关 */
+$uni-color-title: #2C405A; // 文章标题颜色
+$uni-font-size-title:20px;
+$uni-color-subtitle: #555555; // 二级标题颜色
+$uni-font-size-subtitle:26px;
+$uni-color-paragraph: #3F536E; // 文章段落颜色
+$uni-font-size-paragraph:15px;

+ 6 - 0
uni_modules/mescroll-uni/changelog.md

@@ -0,0 +1,6 @@
+## 1.3.7(2021-04-13)
+1. 新增`mescroll-swiper-sticky.vue`的示例, 轮播吸顶菜单导航  
+2. 新增`mescroll-empty.vue`的示例, 单独使用空布局组件  
+3. 简化tabs在具体项目中的使用,并简化对应的示例  
+4. mescroll-uni 支持动态禁止滚动的属性 disableScroll (注: mescroll-body不支持)  
+-by 小瑾同学

+ 19 - 0
uni_modules/mescroll-uni/components/mescroll-body/mescroll-body.css

@@ -0,0 +1,19 @@
+.mescroll-body {
+	position: relative; /* 下拉刷新区域相对自身定位 */
+	height: auto; /* 不可固定高度,否则overflow:hidden导致无法滑动; 同时使设置的最小高生效,实现列表不满屏仍可下拉*/
+	overflow: hidden; /* 当有元素写在mescroll-body标签前面时,可遮住下拉刷新区域 */
+	box-sizing: border-box; /* 避免设置padding出现双滚动条的问题 */
+}
+
+/* 使sticky生效: 父元素不能overflow:hidden或者overflow:auto属性 */
+.mescroll-body.mescorll-sticky{
+	overflow: unset !important
+}
+
+/* 适配 iPhoneX */
+@supports (bottom: constant(safe-area-inset-bottom)) or (bottom: env(safe-area-inset-bottom)) {
+	.mescroll-safearea {
+		padding-bottom: constant(safe-area-inset-bottom);
+		padding-bottom: env(safe-area-inset-bottom);
+	}
+}

+ 400 - 0
uni_modules/mescroll-uni/components/mescroll-body/mescroll-body.vue

@@ -0,0 +1,400 @@
+<template>
+	<view 
+	class="mescroll-body mescroll-render-touch" 
+	:class="{'mescorll-sticky': sticky}"
+	:style="{'minHeight':minHeight, 'padding-top': padTop, 'padding-bottom': padBottom}" 
+	@touchstart="wxsBiz.touchstartEvent" 
+	@touchmove="wxsBiz.touchmoveEvent" 
+	@touchend="wxsBiz.touchendEvent" 
+	@touchcancel="wxsBiz.touchendEvent"
+	:change:prop="wxsBiz.propObserver"
+	:prop="wxsProp"
+	>
+		<!-- 状态栏 -->
+		<view v-if="topbar&&statusBarHeight" class="mescroll-topbar" :style="{height: statusBarHeight+'px', background: topbar}"></view>
+		
+		<view class="mescroll-body-content mescroll-wxs-content" :style="{ transform: translateY, transition: transition }" :change:prop="wxsBiz.callObserver" :prop="callProp">
+			<!-- 下拉加载区域 (支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-down组件实现)-->
+			<!-- <mescroll-down :option="mescroll.optDown" :type="downLoadType" :rate="downRate"></mescroll-down> -->
+			<view v-if="mescroll.optDown.use" class="mescroll-downwarp" :style="{'background':mescroll.optDown.bgColor,'color':mescroll.optDown.textColor}">
+				<view class="downwarp-content">
+					<view class="downwarp-progress mescroll-wxs-progress" :class="{'mescroll-rotate': isDownLoading}" :style="{'border-color':mescroll.optDown.textColor, 'transform': downRotate}"></view>
+					<view class="downwarp-tip">{{downText}}</view>
+				</view>
+			</view>
+	
+			<!-- 列表内容 -->
+			<slot></slot>
+
+			<!-- 空布局 -->
+			<mescroll-empty v-if="isShowEmpty" :option="mescroll.optUp.empty" @emptyclick="emptyClick"></mescroll-empty>
+
+			<!-- 上拉加载区域 (下拉刷新时不显示, 支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-up组件实现)-->
+			<!-- <mescroll-up v-if="mescroll.optUp.use && !isDownLoading && upLoadType!==3" :option="mescroll.optUp" :type="upLoadType"></mescroll-up> -->
+			<view v-if="mescroll.optUp.use && !isDownLoading && upLoadType!==3" class="mescroll-upwarp" :style="{'background':mescroll.optUp.bgColor,'color':mescroll.optUp.textColor}">
+				<!-- 加载中 (此处不能用v-if,否则android小程序快速上拉可能会不断触发上拉回调) -->
+				<view v-show="upLoadType===1">
+					<view class="upwarp-progress mescroll-rotate" :style="{'border-color':mescroll.optUp.textColor}"></view>
+					<view class="upwarp-tip">{{ mescroll.optUp.textLoading }}</view>
+				</view>
+				<!-- 无数据 -->
+				<view v-if="upLoadType===2" class="upwarp-nodata">{{ mescroll.optUp.textNoMore }}</view>
+			</view>
+		</view>
+		
+		<!-- 底部是否偏移TabBar的高度(默认仅在H5端的tab页生效) -->
+		<!-- #ifdef H5 -->
+		<view v-if="bottombar && windowBottom>0" class="mescroll-bottombar" :style="{height: windowBottom+'px'}"></view>
+		<!-- #endif -->
+		
+		<!-- 适配iPhoneX -->
+		<view v-if="safearea" class="mescroll-safearea"></view>
+		
+		<!-- 回到顶部按钮 (fixed元素需写在transform外面,防止降级为absolute)-->
+		<mescroll-top v-model="isShowToTop" :option="mescroll.optUp.toTop" @click="toTopClick"></mescroll-top>
+		
+		<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 -->
+		<!-- renderjs的数据载体,不可写在mescroll-downwarp内部,避免use为false时,载体丢失,无法更新数据 -->
+		<view :change:prop="renderBiz.propObserver" :prop="wxsProp"></view>
+		<!-- #endif -->
+	</view>
+</template>
+
+<!-- 微信小程序, QQ小程序, app, h5使用wxs -->
+<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 -->
+<script src="../mescroll-uni/wxs/wxs.wxs" module="wxsBiz" lang="wxs"></script>
+<!-- #endif -->
+
+<!-- app, h5使用renderjs -->
+<!-- #ifdef APP-PLUS || H5 -->
+<script module="renderBiz" lang="renderjs">
+	import renderBiz from "../mescroll-uni/wxs/renderjs.js";
+	export default {
+		mixins: [renderBiz]
+	}
+</script>
+<!-- #endif -->
+
+<script>
+	// 引入mescroll-uni.js,处理核心逻辑
+	import MeScroll from "../mescroll-uni/mescroll-uni.js";
+	// 引入全局配置
+	import GlobalOption from "../mescroll-uni/mescroll-uni-option.js";
+	// 引入国际化工具类
+	import mescrollI18n from '../mescroll-uni/mescroll-i18n.js';
+	// 引入回到顶部组件
+	import MescrollTop from "../mescroll-uni/components/mescroll-top.vue";
+	// 引入兼容wxs(含renderjs)写法的mixins
+	import WxsMixin from "../mescroll-uni/wxs/mixins.js";
+	
+	/**
+	 * mescroll-body 基于page滚动的下拉刷新和上拉加载组件, 支持嵌套原生组件, 性能好
+	 * @property {Object} down 下拉刷新的参数配置
+	 * @property {Object} up 上拉加载的参数配置
+	 * @property {Object} i18n 国际化的参数配置
+	 * @property {String, Number} top 下拉布局往下的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
+	 * @property {Boolean, String} topbar 偏移量top是否加上状态栏高度, 默认false (使用场景:取消原生导航栏时,配置此项可留出状态栏的占位, 支持传入字符串背景,如色值,背景图,渐变)
+	 * @property {String, Number} bottom 上拉布局往上的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
+	 * @property {Boolean} safearea 偏移量bottom是否加上底部安全区的距离, 默认false (需要适配iPhoneX时使用)
+	 * @property {Boolean} fixed 是否通过fixed固定mescroll的高度, 默认true
+	 * @property {String, Number} height 指定mescroll最小高度,默认windowHeight,使列表不满屏仍可下拉
+	 * @property {Boolean} bottombar 底部是否偏移TabBar的高度 (仅在H5端的tab页生效)
+	 * @property {Boolean} sticky 是否支持sticky,默认false; 当值配置true时,需避免在mescroll-body标签前面加非定位的元素,否则下拉区域无法隐藏
+	 * @event {Function} init 初始化完成的回调 
+	 * @event {Function} down 下拉刷新的回调
+	 * @event {Function} up 上拉加载的回调 
+	 * @event {Function} emptyclick 点击empty配置的btnText按钮回调
+	 * @event {Function} topclick 点击回到顶部的按钮回调
+	 * @event {Function} scroll 滚动监听 (需在 up 配置 onScroll:true 才生效)
+	 * @example <mescroll-body ref="mescrollRef" @init="mescrollInit" @down="downCallback" @up="upCallback"> ... </mescroll-body>
+	 */
+	export default {
+		name: 'mescroll-body',
+		mixins: [WxsMixin],
+		components: {
+			MescrollTop
+		},
+		props: {
+			down: Object,
+			up: Object,
+			i18n: Object,
+			top: [String, Number],
+			topbar: [Boolean, String],
+			bottom: [String, Number],
+			safearea: Boolean,
+			height: [String, Number],
+			bottombar:{
+				type: Boolean,
+				default: true
+			},
+			sticky: Boolean
+		},
+		data() {
+			return {
+				mescroll: {optDown:{},optUp:{}}, // mescroll实例
+				downHight: 0, //下拉刷新: 容器高度
+				downRate: 0, // 下拉比率(inOffset: rate<1; outOffset: rate>=1)
+				downLoadType: 0, // 下拉刷新状态: 0(loading前), 1(inOffset), 2(outOffset), 3(showLoading), 4(endDownScroll)
+				upLoadType: 0, // 上拉加载状态:0(loading前),1(loading中),2(没有更多了,显示END文本提示),3(没有更多了,不显示END文本提示)
+				isShowEmpty: false, // 是否显示空布局
+				isShowToTop: false, // 是否显示回到顶部按钮
+				windowHeight: 0, // 可使用窗口的高度
+				windowBottom: 0, // 可使用窗口的底部位置
+				statusBarHeight: 0 // 状态栏高度
+			};
+		},
+		computed: {
+			// mescroll最小高度,默认windowHeight,使列表不满屏仍可下拉
+			minHeight(){
+				return this.toPx(this.height || '100%') + 'px'
+			},
+			// 下拉布局往下偏移的距离 (px)
+			numTop() {
+				return this.toPx(this.top)
+			},
+			padTop() {
+				return this.numTop + 'px';
+			},
+			// 上拉布局往上偏移 (px)
+			numBottom() {
+				return this.toPx(this.bottom);
+			},
+			padBottom() {
+				return this.numBottom + 'px';
+			},
+			// 是否为重置下拉的状态
+			isDownReset() {
+				return this.downLoadType === 3 || this.downLoadType === 4;
+			},
+			// 过渡
+			transition() {
+				return this.isDownReset ? 'transform 300ms' : '';
+			},
+			translateY() {
+				return this.downHight > 0 ? 'translateY(' + this.downHight + 'px)' : ''; // transform会使fixed失效,需注意把fixed元素写在mescroll之外
+			},
+			// 是否在加载中
+			isDownLoading(){
+				return this.downLoadType === 3
+			},
+			// 旋转的角度
+			downRotate(){
+				return 'rotate(' + 360 * this.downRate + 'deg)'
+			},
+			// 文本提示
+			downText(){
+				if(!this.mescroll) return ""; // 避免头条小程序初始化时报错
+				switch (this.downLoadType){
+					case 1: return this.mescroll.optDown.textInOffset;
+					case 2: return this.mescroll.optDown.textOutOffset;
+					case 3: return this.mescroll.optDown.textLoading;
+					case 4: return this.mescroll.isDownEndSuccess ? this.mescroll.optDown.textSuccess : this.mescroll.isDownEndSuccess==false ? this.mescroll.optDown.textErr : this.mescroll.optDown.textInOffset;
+					default: return this.mescroll.optDown.textInOffset;
+				}
+			}
+		},
+		methods: {
+			//number,rpx,upx,px,% --> px的数值
+			toPx(num) {
+				if (typeof num === 'string') {
+					if (num.indexOf('px') !== -1) {
+						if (num.indexOf('rpx') !== -1) {
+							// "10rpx"
+							num = num.replace('rpx', '');
+						} else if (num.indexOf('upx') !== -1) {
+							// "10upx"
+							num = num.replace('upx', '');
+						} else {
+							// "10px"
+							return Number(num.replace('px', ''));
+						}
+					} else if (num.indexOf('%') !== -1) {
+						// 传百分比,则相对于windowHeight,传"10%"则等于windowHeight的10%
+						let rate = Number(num.replace('%', '')) / 100;
+						return this.windowHeight * rate;
+					}
+				}
+				return num ? uni.upx2px(Number(num)) : 0;
+			},
+			// 点击空布局的按钮回调
+			emptyClick() {
+				this.$emit('emptyclick', this.mescroll);
+			},
+			// 点击回到顶部的按钮回调
+			toTopClick() {
+				this.mescroll.scrollTo(0, this.mescroll.optUp.toTop.duration); // 执行回到顶部
+				this.$emit('topclick', this.mescroll); // 派发点击回到顶部按钮的回调
+			}
+		},
+		// 使用created初始化mescroll对象; 如果用mounted部分css样式编译到H5会失效
+		created() {
+			let vm = this;
+
+			let diyOption = {
+				// 下拉刷新的配置
+				down: {
+					inOffset() {
+						vm.downLoadType = 1; // 下拉的距离进入offset范围内那一刻的回调 (自定义mescroll组件时,此行不可删)
+					},
+					outOffset() {
+						vm.downLoadType = 2; // 下拉的距离大于offset那一刻的回调 (自定义mescroll组件时,此行不可删)
+					},
+					onMoving(mescroll, rate, downHight) {
+						// 下拉过程中的回调,滑动过程一直在执行;
+						vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
+						vm.downRate = rate; //下拉比率 (inOffset: rate<1; outOffset: rate>=1)
+					},
+					showLoading(mescroll, downHight) {
+						vm.downLoadType = 3; // 显示下拉刷新进度的回调 (自定义mescroll组件时,此行不可删)
+						vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
+					},
+					beforeEndDownScroll(mescroll){
+						vm.downLoadType = 4; 
+						return mescroll.optDown.beforeEndDelay // 延时结束的时长
+					},
+					endDownScroll() {
+						vm.downLoadType = 4; // 结束下拉 (自定义mescroll组件时,此行不可删)
+						vm.downHight = 0; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
+						if(vm.downResetTimer) {clearTimeout(vm.downResetTimer); vm.downResetTimer = null} // 移除重置倒计时
+						vm.downResetTimer = setTimeout(()=>{ // 过渡动画执行完毕后,需重置为0的状态,避免下次inOffset不及时显示textInOffset
+							if(vm.downLoadType === 4) vm.downLoadType = 0
+						},300)
+					},
+					// 派发下拉刷新的回调
+					callback: function(mescroll) {
+						vm.$emit('down', mescroll);
+					}
+				},
+				// 上拉加载的配置
+				up: {
+					// 显示加载中的回调
+					showLoading() {
+						vm.upLoadType = 1;
+					},
+					// 显示无更多数据的回调
+					showNoMore() {
+						vm.upLoadType = 2;
+					},
+					// 隐藏上拉加载的回调
+					hideUpScroll(mescroll) {
+						vm.upLoadType = mescroll.optUp.hasNext ? 0 : 3;
+					},
+					// 空布局
+					empty: {
+						onShow(isShow) {
+							// 显示隐藏的回调
+							vm.isShowEmpty = isShow;
+						}
+					},
+					// 回到顶部
+					toTop: {
+						onShow(isShow) {
+							// 显示隐藏的回调
+							vm.isShowToTop = isShow;
+						}
+					},
+					// 派发上拉加载的回调
+					callback: function(mescroll) {
+						vm.$emit('up', mescroll);
+					}
+				}
+			};
+			
+			let i18nType = mescrollI18n.getType() // 当前语言类型
+			let i18nOption = {type: i18nType} // 国际化配置
+			MeScroll.extend(i18nOption, vm.i18n) // 具体页面的国际化配置
+			MeScroll.extend(i18nOption, GlobalOption.i18n) // 全局的国际化配置
+			MeScroll.extend(diyOption, i18nOption[i18nType]); // 混入国际化配置
+			MeScroll.extend(diyOption, {down:GlobalOption.down, up:GlobalOption.up}); // 混入全局的配置
+			let myOption = JSON.parse(JSON.stringify({down: vm.down,up: vm.up})); // 深拷贝,避免对props的影响
+			MeScroll.extend(myOption, diyOption); // 混入具体界面的配置
+
+			// 初始化MeScroll对象
+			vm.mescroll = new MeScroll(myOption, true); // 传入true,标记body为滚动区域
+			// 挂载语言包
+			vm.mescroll.i18n = i18nOption;
+			// init回调mescroll对象
+			vm.$emit('init', vm.mescroll);
+
+			// 设置高度
+			const sys = uni.getSystemInfoSync();
+			if (sys.windowHeight) vm.windowHeight = sys.windowHeight;
+			if (sys.windowBottom) vm.windowBottom = sys.windowBottom;
+			if (sys.statusBarHeight) vm.statusBarHeight = sys.statusBarHeight;
+			// 使down的bottomOffset生效
+			vm.mescroll.setBodyHeight(sys.windowHeight);
+
+			// 因为使用的是page的scroll,这里需自定义scrollTo
+			vm.mescroll.resetScrollTo((y, t) => {
+				if(typeof y === 'string'){
+					// 滚动到指定view (y为css选择器)
+					setTimeout(()=>{ // 延时确保view已渲染; 不使用$nextTick
+						let selector;
+						if(y.indexOf('#')==-1 && y.indexOf('.')==-1){
+							selector = '#'+y // 不带#和. 则默认为id选择器
+						}else{
+							selector = y
+							// #ifdef APP-PLUS || H5 || MP-ALIPAY || MP-DINGTALK
+							if(y.indexOf('>>>')!=-1){ // 不支持跨自定义组件的后代选择器 (转为普通的选择器即可跨组件查询)
+								selector = y.split('>>>')[1].trim()
+							}
+							// #endif
+						}
+						uni.createSelectorQuery().select(selector).boundingClientRect(function(rect){
+							if (rect) {
+								let top = rect.top
+								top += vm.mescroll.getScrollTop()
+								uni.pageScrollTo({
+									scrollTop: top,
+									duration: t
+								})
+							} else{
+								console.error(selector + ' does not exist');
+							}
+						}).exec()
+					},30)
+				} else{
+					// 滚动到指定位置 (y必须为数字)
+					uni.pageScrollTo({
+						scrollTop: y,
+						duration: t
+					})
+				}
+			});
+
+			// 具体的界面如果不配置up.toTop.safearea,则取本vue的safearea值
+			if (vm.up && vm.up.toTop && vm.up.toTop.safearea != null) {} else {
+				vm.mescroll.optUp.toTop.safearea = vm.safearea;
+			}
+			
+			// 全局配置监听
+			uni.$on("setMescrollGlobalOption", options=>{
+				if(!options) return;
+				let i18nType = options.i18n ? options.i18n.type : null
+				if(i18nType && vm.mescroll.i18n.type != i18nType){
+					vm.mescroll.i18n.type = i18nType
+					mescrollI18n.setType(i18nType)
+					MeScroll.extend(options, vm.mescroll.i18n[i18nType])
+				}
+				if(options.down){
+					let down = MeScroll.extend({}, options.down)
+					vm.mescroll.optDown = MeScroll.extend(down, vm.mescroll.optDown)
+				}
+				if(options.up){
+					let up = MeScroll.extend({}, options.up)
+					vm.mescroll.optUp = MeScroll.extend(up, vm.mescroll.optUp)
+				}
+			})
+		},
+		destroyed() {
+			// 注销全局配置监听
+			uni.$off("setMescrollGlobalOption")
+		}
+	};
+</script>
+
+<style>
+	@import "../mescroll-body/mescroll-body.css";
+	@import "../mescroll-uni/components/mescroll-down.css";
+	@import "../mescroll-uni/components/mescroll-up.css";
+</style>

+ 47 - 0
uni_modules/mescroll-uni/components/mescroll-diy/beibei/components/mescroll-down.css

@@ -0,0 +1,47 @@
+/*下拉刷新--标语*/
+.mescroll-downwarp .downwarp-slogan{
+	display: block;
+	width: 420rpx;
+	height: 168rpx;
+	margin: auto;
+}
+/*下拉刷新--向下进度动画*/
+.mescroll-downwarp .downwarp-progress{
+	display: inline-block;
+	width: 40rpx;
+	height: 40rpx;
+	border: none;
+	margin: auto;
+	background-size: contain;
+	background-repeat: no-repeat;
+	background-position: center;
+	background-image: url(https://www.mescroll.com/img/beibei/mescroll-progress.png);
+	transition: all 300ms;
+}
+/*下拉刷新--进度条*/
+.mescroll-downwarp .downwarp-loading{
+	display: inline-block;
+	width: 32rpx;
+	height: 32rpx;
+	border-radius: 50%;
+	border: 2rpx solid #FF8095;
+	border-bottom-color: transparent;
+}
+/*下拉刷新--吉祥物*/
+.mescroll-downwarp .downwarp-mascot{
+	position: absolute;
+	right: 16rpx;
+	bottom: 0;
+	width: 100rpx;
+	height: 100rpx;
+	background-size: contain;
+	background-repeat: no-repeat;
+	animation: animMascot .6s steps(1,end) infinite;
+}
+@keyframes animMascot {
+	0% {background-image: url(https://www.mescroll.com/img/beibei/mescroll-bb1.png)}
+	25% {background-image: url(https://www.mescroll.com/img/beibei/mescroll-bb2.png)}
+	50% {background-image: url(https://www.mescroll.com/img/beibei/mescroll-bb3.png)}
+	75% {background-image: url(https://www.mescroll.com/img/beibei/mescroll-bb4.png)}
+	100% {background-image: url(https://www.mescroll.com/img/beibei/mescroll-bb1.png)}
+}

+ 39 - 0
uni_modules/mescroll-uni/components/mescroll-diy/beibei/components/mescroll-down.vue

@@ -0,0 +1,39 @@
+<!-- 下拉刷新区域 -->
+<template>
+	<view v-if="mOption.use" class="mescroll-downwarp" :style="{'background':mOption.bgColor,'color':mOption.textColor}">
+		<view class="downwarp-content">
+			<image class="downwarp-slogan" src="https://www.mescroll.com/img/beibei/mescroll-slogan.jpg?v=1" mode="widthFix"/>
+			<view v-if="isDownLoading" class="downwarp-loading mescroll-rotate"></view>
+			<view v-else class="downwarp-progress" :style="{'transform':downRotate}"></view>
+			<view class="downwarp-mascot"></view>
+		</view>
+	</view>
+</template>
+
+<script>
+export default {
+	props: {
+		option: Object , // down的配置项
+		type: Number // 下拉状态(inOffset:1, outOffset:2, showLoading:3, endDownScroll:4)
+	},
+	computed: {
+		// 支付宝小程序需写成计算属性,prop定义default仍报错
+		mOption(){
+			return this.option || {}
+		},
+		// 是否在加载中
+		isDownLoading(){
+			return this.type === 3
+		},
+		// 旋转的角度
+		downRotate(){
+			return this.type === 2 ? 'rotate(180deg)' : 'rotate(0deg)'
+		}
+	}
+};
+</script>
+
+<style>
+@import "../../../mescroll-uni/components/mescroll-down.css";
+@import "./mescroll-down.css";
+</style>

+ 360 - 0
uni_modules/mescroll-uni/components/mescroll-diy/beibei/mescroll-body.vue

@@ -0,0 +1,360 @@
+<template>
+	<view 
+		class="mescroll-body mescroll-render-touch" 
+		:style="{'minHeight':minHeight, 'padding-top': padTop, 'padding-bottom': padBottom}" 
+		:class="{'mescorll-sticky': sticky}"
+		@touchstart="wxsBiz.touchstartEvent" 
+		@touchmove="wxsBiz.touchmoveEvent" 
+		@touchend="wxsBiz.touchendEvent" 
+		@touchcancel="wxsBiz.touchendEvent"
+		:change:prop="wxsBiz.propObserver"
+		:prop="wxsProp"
+		>
+		
+		<!-- 状态栏 -->
+		<view v-if="topbar&&statusBarHeight" class="mescroll-topbar" :style="{height: statusBarHeight+'px', background: topbar}"></view>
+
+		<view class="mescroll-body-content mescroll-wxs-content" :style="{ transform: translateY, transition: transition }" :change:prop="wxsBiz.callObserver" :prop="callProp">
+			<!-- 下拉加载区域 (支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-down组件实现)-->
+			<!-- <mescroll-down :option="mescroll.optDown" :type="downLoadType"></mescroll-down> -->
+			<view v-if="mescroll.optDown.use" class="mescroll-downwarp" :style="{'background':mescroll.optDown.bgColor,'color':mescroll.optDown.textColor}">
+				<view class="downwarp-content">
+					<image class="downwarp-slogan" src="https://www.mescroll.com/img/beibei/mescroll-slogan.jpg?v=1" mode="widthFix"/>
+					<view v-if="isDownLoading" class="downwarp-loading mescroll-rotate"></view>
+					<view v-else class="downwarp-progress" :style="{'transform':downRotate}"></view>
+					<view class="downwarp-mascot"></view>
+				</view>
+			</view>
+						
+			<!-- 列表内容 -->
+			<slot></slot>
+
+			<!-- 空布局 -->
+			<mescroll-empty v-if="isShowEmpty" :option="mescroll.optUp.empty" @emptyclick="emptyClick"></mescroll-empty>
+
+			<!-- 上拉加载区域 (下拉刷新时不显示, 支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-up组件实现)-->
+			<!-- <mescroll-up v-if="mescroll.optUp.use && !isDownLoading && upLoadType!==3" :option="mescroll.optUp" :type="upLoadType"></mescroll-up> -->
+			<view v-if="mescroll.optUp.use && !isDownLoading && upLoadType!==3" class="mescroll-upwarp" :style="{'background':mescroll.optUp.bgColor,'color':mescroll.optUp.textColor}">
+				<!-- 加载中 (此处不能用v-if,否则android小程序快速上拉可能会不断触发上拉回调) -->
+				<view v-show="upLoadType===1">
+					<view class="upwarp-progress mescroll-rotate" :style="{'border-color':mescroll.optUp.textColor}"></view>
+					<view class="upwarp-tip">{{ mescroll.optUp.textLoading }}</view>
+				</view>
+				<!-- 无数据 -->
+				<view v-if="upLoadType===2" class="upwarp-nodata">{{ mescroll.optUp.textNoMore }}</view>
+			</view>
+		</view>
+		
+		<!-- 底部是否偏移TabBar的高度(仅H5端生效) -->
+		<!-- #ifdef H5 -->
+		<view v-if="bottombar && windowBottom>0" class="mescroll-bottombar" :style="{height: windowBottom+'px'}"></view>
+		<!-- #endif -->
+		
+		<!-- 适配iPhoneX -->
+		<view v-if="safearea" class="mescroll-safearea"></view>
+		
+		<!-- 回到顶部按钮 (fixed元素需写在transform外面,防止降级为absolute)-->
+		<mescroll-top v-model="isShowToTop" :option="mescroll.optUp.toTop" @click="toTopClick"></mescroll-top>
+
+		<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 -->
+		<!-- renderjs的数据载体,不可写在mescroll-downwarp内部,避免use为false时,载体丢失,无法更新数据 -->
+		<view :change:prop="renderBiz.propObserver" :prop="wxsProp"></view>
+		<!-- #endif -->
+	</view>
+</template>
+
+<!-- 微信小程序, QQ小程序, app, h5使用wxs -->
+<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 -->
+<script src="../../mescroll-uni/wxs/wxs.wxs" module="wxsBiz" lang="wxs"></script>
+<!-- #endif -->
+
+<!-- app, h5使用renderjs -->
+<!-- #ifdef APP-PLUS || H5 -->
+<script module="renderBiz" lang="renderjs">
+	import renderBiz from '../../mescroll-uni/wxs/renderjs.js';
+	export default {
+		mixins: [renderBiz]
+	}
+</script>
+<!-- #endif -->
+
+<script>
+	import MeScroll from '../../mescroll-uni/mescroll-uni.js';
+	import MescrollTop from '../../mescroll-uni/components/mescroll-top.vue';
+	import WxsMixin from '../../mescroll-uni/wxs/mixins.js';
+	import mescrollI18n from '../../mescroll-uni/mescroll-i18n.js';
+	import GlobalOption from './mescroll-uni-option.js';
+	
+	export default {
+		mixins: [WxsMixin],
+		components: {
+			MescrollTop
+		},
+		data() {
+			return {
+				mescroll: null, // mescroll实例
+				downHight: 0, //下拉刷新: 容器高度
+				downLoadType: 0, // 下拉刷新状态: 0(loading前), 1(inOffset), 2(outOffset), 3(showLoading), 4(endDownScroll)
+				upLoadType: 0, // 上拉加载状态:0(loading前),1(loading中),2(没有更多了,显示END文本提示),3(没有更多了,不显示END文本提示)
+				isShowEmpty: false, // 是否显示空布局
+				isShowToTop: false, // 是否显示回到顶部按钮
+				windowHeight: 0, // 可使用窗口的高度
+				windowBottom: 0, // 可使用窗口的底部位置
+				statusBarHeight: 0 // 状态栏高度
+			};
+		},
+		props: {
+			down: Object, // 下拉刷新的参数配置
+			up: Object, // 上拉加载的参数配置
+			i18n: Object, // 国际化的参数配置
+			top: [String, Number], // 下拉布局往下的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
+			topbar: [Boolean, String], // top的偏移量是否加上状态栏高度, 默认false (使用场景:取消原生导航栏时,配置此项可留出状态栏的占位, 支持传入字符串背景,如色值,背景图,渐变)
+			bottom: [String, Number], // 上拉布局往上的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
+			safearea: Boolean, // bottom的偏移量是否加上底部安全区的距离, 默认false (需要适配iPhoneX时使用)
+			height: [String, Number], // 指定mescroll最小高度,默认windowHeight,使列表不满屏仍可下拉
+			bottombar:{ // 底部是否偏移TabBar的高度(默认仅在H5端的tab页生效)
+				type: Boolean,
+				default: true
+			},
+			sticky: Boolean // 是否支持sticky,默认false; 当值配置true时,需避免在mescroll-body标签前面加非定位的元素,否则下拉区域无法会隐藏
+		},
+		computed: {
+			// mescroll最小高度,默认windowHeight,使列表不满屏仍可下拉
+			minHeight(){
+				return this.toPx(this.height || '100%') + 'px'
+			},
+			// 下拉布局往下偏移的距离 (px)
+			numTop() {
+				return this.toPx(this.top)
+			},
+			padTop() {
+				return this.numTop + 'px';
+			},
+			// 上拉布局往上偏移 (px)
+			numBottom() {
+				return this.toPx(this.bottom);
+			},
+			padBottom() {
+				return this.numBottom + 'px';
+			},
+			// 是否为重置下拉的状态
+			isDownReset() {
+				return this.downLoadType === 3 || this.downLoadType === 4;
+			},
+			// 过渡
+			transition() {
+				return this.isDownReset ? 'transform 300ms' : '';
+			},
+			translateY() {
+				return this.downHight > 0 ? 'translateY(' + this.downHight + 'px)' : ''; // transform会使fixed失效,需注意把fixed元素写在mescroll之外
+			},
+			// 是否在加载中
+			isDownLoading(){
+				return this.downLoadType === 3
+			},
+			// 旋转的角度
+			downRotate(){
+				return this.downLoadType === 2 ? 'rotate(180deg)' : 'rotate(0deg)'
+			}
+		},
+		methods: {
+			//number,rpx,upx,px,% --> px的数值
+			toPx(num) {
+				if (typeof num === 'string') {
+					if (num.indexOf('px') !== -1) {
+						if (num.indexOf('rpx') !== -1) {
+							// "10rpx"
+							num = num.replace('rpx', '');
+						} else if (num.indexOf('upx') !== -1) {
+							// "10upx"
+							num = num.replace('upx', '');
+						} else {
+							// "10px"
+							return Number(num.replace('px', ''));
+						}
+					} else if (num.indexOf('%') !== -1) {
+						// 传百分比,则相对于windowHeight,传"10%"则等于windowHeight的10%
+						let rate = Number(num.replace('%', '')) / 100;
+						return this.windowHeight * rate;
+					}
+				}
+				return num ? uni.upx2px(Number(num)) : 0;
+			},
+			// 点击空布局的按钮回调
+			emptyClick() {
+				this.$emit('emptyclick', this.mescroll);
+			},
+			// 点击回到顶部的按钮回调
+			toTopClick() {
+				this.mescroll.scrollTo(0, this.mescroll.optUp.toTop.duration); // 执行回到顶部
+				this.$emit('topclick', this.mescroll); // 派发点击回到顶部按钮的回调
+			}
+		},
+		// 使用created初始化mescroll对象; 如果用mounted部分css样式编译到H5会失效
+		created() {
+			let vm = this;
+
+			let diyOption = {
+				// 下拉刷新的配置
+				down: {
+					inOffset() {
+						vm.downLoadType = 1; // 下拉的距离进入offset范围内那一刻的回调 (自定义mescroll组件时,此行不可删)
+					},
+					outOffset() {
+						vm.downLoadType = 2; // 下拉的距离大于offset那一刻的回调 (自定义mescroll组件时,此行不可删)
+					},
+					onMoving(mescroll, rate, downHight) {
+						// 下拉过程中的回调,滑动过程一直在执行;
+						vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
+					},
+					showLoading(mescroll, downHight) {
+						vm.downLoadType = 3; // 显示下拉刷新进度的回调 (自定义mescroll组件时,此行不可删)
+						vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
+					},
+					endDownScroll() {
+						vm.downLoadType = 4; // 结束下拉 (自定义mescroll组件时,此行不可删)
+						vm.downHight = 0; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
+						if(vm.downResetTimer) {clearTimeout(vm.downResetTimer); vm.downResetTimer = null} // 移除重置倒计时
+						vm.downResetTimer = setTimeout(()=>{ // 过渡动画执行完毕后,需重置为0的状态,避免下次inOffset不及时显示textInOffset
+							if(vm.downLoadType === 4) vm.downLoadType = 0
+						},300)
+					},
+					// 派发下拉刷新的回调
+					callback: function(mescroll) {
+						vm.$emit('down', mescroll);
+					}
+				},
+				// 上拉加载的配置
+				up: {
+					// 显示加载中的回调
+					showLoading() {
+						vm.upLoadType = 1;
+					},
+					// 显示无更多数据的回调
+					showNoMore() {
+						vm.upLoadType = 2;
+					},
+					// 隐藏上拉加载的回调
+					hideUpScroll(mescroll) {
+						vm.upLoadType = mescroll.optUp.hasNext ? 0 : 3;
+					},
+					// 空布局
+					empty: {
+						onShow(isShow) {
+							// 显示隐藏的回调
+							vm.isShowEmpty = isShow;
+						}
+					},
+					// 回到顶部
+					toTop: {
+						onShow(isShow) {
+							// 显示隐藏的回调
+							vm.isShowToTop = isShow;
+						}
+					},
+					// 派发上拉加载的回调
+					callback: function(mescroll) {
+						vm.$emit('up', mescroll);
+					}
+				}
+			};
+
+			let i18nType = mescrollI18n.getType() // 当前语言类型
+			let i18nOption = {type: i18nType} // 国际化配置
+			MeScroll.extend(i18nOption, vm.i18n) // 具体页面的国际化配置
+			MeScroll.extend(i18nOption, GlobalOption.i18n) // 全局的国际化配置
+			MeScroll.extend(diyOption, i18nOption[i18nType]); // 混入国际化配置
+			MeScroll.extend(diyOption, {down:GlobalOption.down, up:GlobalOption.up}); // 混入全局的配置
+			let myOption = JSON.parse(JSON.stringify({down: vm.down,up: vm.up})); // 深拷贝,避免对props的影响
+			MeScroll.extend(myOption, diyOption); // 混入具体界面的配置
+
+			// 初始化MeScroll对象
+			vm.mescroll = new MeScroll(myOption, true); // 传入true,标记body为滚动区域
+			// 挂载语言包
+			vm.mescroll.i18n = i18nOption;
+			// init回调mescroll对象
+			vm.$emit('init', vm.mescroll);
+
+			// 设置高度
+			const sys = uni.getSystemInfoSync();
+			if (sys.windowHeight) vm.windowHeight = sys.windowHeight;
+			if (sys.windowBottom) vm.windowBottom = sys.windowBottom;
+			if (sys.statusBarHeight) vm.statusBarHeight = sys.statusBarHeight;
+			// 使down的bottomOffset生效
+			vm.mescroll.setBodyHeight(sys.windowHeight);
+
+			// 因为使用的是page的scroll,这里需自定义scrollTo
+			vm.mescroll.resetScrollTo((y, t) => {
+				if(typeof y === 'string'){
+					// 滚动到指定view (y为css选择器)
+					setTimeout(()=>{ // 延时确保view已渲染; 不使用$nextTick
+						let selector;
+						if(y.indexOf('#')==-1 && y.indexOf('.')==-1){
+							selector = '#'+y // 不带#和. 则默认为id选择器
+						}else{
+							selector = y
+							// #ifdef APP-PLUS || H5 || MP-ALIPAY || MP-DINGTALK
+							if(y.indexOf('>>>')!=-1){ // 不支持跨自定义组件的后代选择器 (转为普通的选择器即可跨组件查询)
+								selector = y.split('>>>')[1].trim()
+							}
+							// #endif
+						}
+						uni.createSelectorQuery().select(selector).boundingClientRect(function(rect){
+							if (rect) {
+								let top = rect.top
+								top += vm.mescroll.getScrollTop()
+								uni.pageScrollTo({
+									scrollTop: top,
+									duration: t
+								})
+							} else{
+								console.error(selector + ' does not exist');
+							}
+						}).exec()
+					},30)
+				} else{
+					// 滚动到指定位置 (y必须为数字)
+					uni.pageScrollTo({
+						scrollTop: y,
+						duration: t
+					})
+				}
+			});
+
+			// 具体的界面如果不配置up.toTop.safearea,则取本vue的safearea值
+			if (vm.up && vm.up.toTop && vm.up.toTop.safearea != null) {} else {
+				vm.mescroll.optUp.toTop.safearea = vm.safearea;
+			}
+			
+			// 全局配置监听
+			uni.$on("setMescrollGlobalOption", options=>{
+				if(!options) return;
+				let i18nType = options.i18n ? options.i18n.type : null
+				if(i18nType && vm.mescroll.i18n.type != i18nType){
+					vm.mescroll.i18n.type = i18nType
+					mescrollI18n.setType(i18nType)
+					MeScroll.extend(options, vm.mescroll.i18n[i18nType])
+				}
+				if(options.down){
+					let down = MeScroll.extend({}, options.down)
+					vm.mescroll.optDown = MeScroll.extend(down, vm.mescroll.optDown)
+				}
+				if(options.up){
+					let up = MeScroll.extend({}, options.up)
+					vm.mescroll.optUp = MeScroll.extend(up, vm.mescroll.optUp)
+				}
+			})
+		},
+		destroyed() {
+			// 注销全局配置监听
+			uni.$off("setMescrollGlobalOption")
+		}
+	};
+</script>
+
+<style>
+	@import "../../mescroll-body/mescroll-body.css";
+	@import "../../mescroll-uni/components/mescroll-down.css";
+	@import "../../mescroll-uni/components/mescroll-up.css";
+	@import "./components/mescroll-down.css";
+</style>

+ 49 - 0
uni_modules/mescroll-uni/components/mescroll-diy/beibei/mescroll-uni-option.js

@@ -0,0 +1,49 @@
+// mescroll-uni和mescroll-body 的全局配置
+const GlobalOption = {
+	down: {
+		// 其他down的配置参数也可以写,这里只展示了常用的配置:
+		offset: uni.upx2px(140), // 在列表顶部,下拉大于140upx,松手即可触发下拉刷新的回调
+		native: false // 是否使用系统自带的下拉刷新; 默认false; 仅在mescroll-body生效 (值为true时,还需在pages配置enablePullDownRefresh:true;详请参考mescroll-native的案例)
+	},
+	up: {
+		// 其他up的配置参数也可以写,这里只展示了常用的配置:
+		offset: 150, // 距底部多远时,触发upCallback
+		toTop: {
+			// 回到顶部按钮,需配置src才显示
+			src: "https://www.mescroll.com/img/mescroll-totop.png", // 图片路径 (建议放入static目录, 如 /static/img/mescroll-totop.png )
+			offset: 1000, // 列表滚动多少距离才显示回到顶部按钮,默认1000px
+			right: 20, // 到右边的距离, 默认20 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx)
+			bottom: 120, // 到底部的距离, 默认120 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx)
+			width: 72 // 回到顶部图标的宽度, 默认72 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx)
+		},
+		empty: {
+			use: true, // 是否显示空布局
+			icon: "https://www.mescroll.com/img/mescroll-empty.png" // 图标路径 (建议放入static目录, 如 /static/img/mescroll-empty.png )
+		}
+	},
+	// 国际化配置
+	i18n: {
+		// 中文
+		zh: {
+			up: {
+				textLoading: '加载中 ...', // 加载中的提示文本
+				textNoMore: '-- END --', // 没有更多数据的提示文本
+				empty: {
+					tip: '~ 暂无相关数据 ~' // 空提示
+				}
+			}
+		},
+		// 英文
+		en: {
+			up: {
+				textLoading: 'loading ...',
+				textNoMore: '-- END --',
+				empty: {
+					tip: '~ absolutely empty ~'
+				}
+			}
+		}
+	}
+}
+
+export default GlobalOption

+ 437 - 0
uni_modules/mescroll-uni/components/mescroll-diy/beibei/mescroll-uni.vue

@@ -0,0 +1,437 @@
+<template>
+	<view class="mescroll-uni-warp">
+		<scroll-view :id="viewId" class="mescroll-uni" :class="{'mescroll-uni-fixed':isFixed}" :style="{'height':scrollHeight,'padding-top':padTop,'padding-bottom':padBottom,'top':fixedTop,'bottom':fixedBottom}" :scroll-top="scrollTop" :scroll-with-animation="scrollAnim" @scroll="scroll" :scroll-y='scrollable' :enable-back-to-top="true" :throttle="false">
+			<view class="mescroll-uni-content mescroll-render-touch"
+			@touchstart="wxsBiz.touchstartEvent" 
+			@touchmove="wxsBiz.touchmoveEvent" 
+			@touchend="wxsBiz.touchendEvent" 
+			@touchcancel="wxsBiz.touchendEvent"
+			:change:prop="wxsBiz.propObserver"
+			:prop="wxsProp">
+						
+				<!-- 状态栏 -->
+				<view v-if="topbar&&statusBarHeight" class="mescroll-topbar" :style="{height: statusBarHeight+'px', background: topbar}"></view>
+							
+				<view class="mescroll-wxs-content" :style="{'transform': translateY, 'transition': transition}" :change:prop="wxsBiz.callObserver" :prop="callProp">
+					<!-- 下拉加载区域 (支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-down组件实现)-->
+					<!-- <mescroll-down :option="mescroll.optDown" :type="downLoadType"></mescroll-down> -->
+					<view v-if="mescroll.optDown.use" class="mescroll-downwarp" :style="{'background':mescroll.optDown.bgColor,'color':mescroll.optDown.textColor}">
+						<view class="downwarp-content">
+							<image class="downwarp-slogan" src="https://www.mescroll.com/img/beibei/mescroll-slogan.jpg?v=1" mode="widthFix"/>
+							<view v-if="isDownLoading" class="downwarp-loading mescroll-rotate"></view>
+							<view v-else class="downwarp-progress" :style="{'transform':downRotate}"></view>
+							<view class="downwarp-mascot"></view>
+						</view>
+					</view>
+
+					<!-- 列表内容 -->
+					<slot></slot>
+
+					<!-- 空布局 -->
+					<mescroll-empty v-if="isShowEmpty" :option="mescroll.optUp.empty" @emptyclick="emptyClick"></mescroll-empty>
+
+					<!-- 上拉加载区域 (下拉刷新时不显示, 支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-up组件实现)-->
+					<!-- <mescroll-up v-if="mescroll.optUp.use && !isDownLoading && upLoadType!==3" :option="mescroll.optUp" :type="upLoadType"></mescroll-up> -->
+					<view v-if="mescroll.optUp.use && !isDownLoading && upLoadType!==3" class="mescroll-upwarp" :style="{'background':mescroll.optUp.bgColor,'color':mescroll.optUp.textColor}">
+						<!-- 加载中 (此处不能用v-if,否则android小程序快速上拉可能会不断触发上拉回调) -->
+						<view v-show="upLoadType===1">
+							<view class="upwarp-progress mescroll-rotate" :style="{'border-color':mescroll.optUp.textColor}"></view>
+							<view class="upwarp-tip">{{ mescroll.optUp.textLoading }}</view>
+						</view>
+						<!-- 无数据 -->
+						<view v-if="upLoadType===2" class="upwarp-nodata">{{ mescroll.optUp.textNoMore }}</view>
+					</view>
+				</view>
+				
+				<!-- 底部是否偏移TabBar的高度(仅H5端生效) -->
+				<!-- #ifdef H5 -->
+				<view v-if="bottombar && windowBottom>0" class="mescroll-bottombar" :style="{height: windowBottom+'px'}"></view>
+				<!-- #endif -->
+				
+				<!-- 适配iPhoneX -->
+				<view v-if="safearea" class="mescroll-safearea"></view>
+			</view>
+		</scroll-view>
+
+		<!-- 回到顶部按钮 (fixed元素,需写在scroll-view外面,防止滚动的时候抖动)-->
+		<mescroll-top v-model="isShowToTop" :option="mescroll.optUp.toTop" @click="toTopClick"></mescroll-top>
+
+		<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 -->
+		<!-- renderjs的数据载体,不可写在mescroll-downwarp内部,避免use为false时,载体丢失,无法更新数据 -->
+		<view :change:prop="renderBiz.propObserver" :prop="wxsProp"></view>
+		<!-- #endif -->
+	</view>
+</template>
+
+<!-- 微信小程序, QQ小程序, app, h5使用wxs -->
+<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 -->
+<script src="../../mescroll-uni/wxs/wxs.wxs" module="wxsBiz" lang="wxs"></script>
+<!-- #endif -->
+
+<!-- app, h5使用renderjs -->
+<!-- #ifdef APP-PLUS || H5 -->
+<script module="renderBiz" lang="renderjs">
+	import renderBiz from '../../mescroll-uni/wxs/renderjs.js';
+	export default {
+		mixins: [renderBiz]
+	}
+</script>
+<!-- #endif -->
+
+<script>
+	import MeScroll from '../../mescroll-uni/mescroll-uni.js';
+	import MescrollTop from '../../mescroll-uni/components/mescroll-top.vue';
+	import WxsMixin from '../../mescroll-uni/wxs/mixins.js';
+	import mescrollI18n from '../../mescroll-uni/mescroll-i18n.js';
+	import GlobalOption from './mescroll-uni-option.js';
+	
+	export default {
+		mixins: [WxsMixin],
+		components: {
+			MescrollTop
+		},
+		data() {
+			return {
+				mescroll: null, // mescroll实例
+				viewId: 'id_' + Math.random().toString(36).substr(2,16), // 随机生成mescroll的id(不能数字开头,否则找不到元素)
+				downHight: 0, //下拉刷新: 容器高度
+				downLoadType: 0, // 下拉刷新状态: 0(loading前), 1(inOffset), 2(outOffset), 3(showLoading), 4(endDownScroll)
+				upLoadType: 0, // 上拉加载状态: 0(loading前), 1loading中, 2没有更多了,显示END文本提示, 3(没有更多了,不显示END文本提示)
+				isShowEmpty: false, // 是否显示空布局
+				isShowToTop: false, // 是否显示回到顶部按钮
+				scrollTop: 0, // 滚动条的位置
+				scrollAnim: false, // 是否开启滚动动画
+				windowTop: 0, // 可使用窗口的顶部位置
+				windowBottom: 0, // 可使用窗口的底部位置
+				windowHeight: 0, // 可使用窗口的高度
+				statusBarHeight: 0 // 状态栏高度
+			}
+		},
+		props: {
+			down: Object, // 下拉刷新的参数配置
+			up: Object, // 上拉加载的参数配置
+			i18n: Object, // 国际化的参数配置
+			top: [String, Number], // 下拉布局往下的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
+			topbar: [Boolean, String], // top的偏移量是否加上状态栏高度, 默认false (使用场景:取消原生导航栏时,配置此项可留出状态栏的占位, 支持传入字符串背景,如色值,背景图,渐变)
+			bottom: [String, Number], // 上拉布局往上的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
+			safearea: Boolean, // bottom的偏移量是否加上底部安全区的距离, 默认false (需要适配iPhoneX时使用)
+			fixed: { // 是否通过fixed固定mescroll的高度, 默认true
+				type: Boolean,
+				default: true
+			},
+			height: [String, Number], // 指定mescroll的高度, 此项有值,则不使用fixed. (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
+			bottombar:{ // 底部是否偏移TabBar的高度(默认仅在H5端的tab页生效)
+				type: Boolean,
+				default: true
+			},
+			disableScroll: Boolean // 是否禁止滚动
+		},
+		computed: {
+			// 是否使用fixed定位 (当height有值,则不使用)
+			isFixed(){
+				return !this.height && this.fixed
+			},
+			// mescroll的高度
+			scrollHeight(){
+				if (this.isFixed) {
+					return "auto"
+				} else if(this.height){
+					return this.toPx(this.height) + 'px'
+				}else{
+					return "100%"
+				}
+			},
+			// 下拉布局往下偏移的距离 (px)
+			numTop() {
+				return this.toPx(this.top)
+			},
+			fixedTop() {
+				return this.isFixed ? (this.numTop + this.windowTop) + 'px' : 0
+			},
+			padTop() {
+				return !this.isFixed ? this.numTop + 'px' : 0
+			},
+			// 上拉布局往上偏移 (px)
+			numBottom() {
+				return this.toPx(this.bottom)
+			},
+			fixedBottom() {
+				return this.isFixed ? (this.numBottom + this.windowBottom) + 'px' : 0
+			},
+			padBottom() {
+				return !this.isFixed ? this.numBottom + 'px' : 0
+			},
+			// 是否为重置下拉的状态
+			isDownReset(){
+				return this.downLoadType===3 || this.downLoadType===4
+			},
+			// 过渡
+			transition() {
+				return this.isDownReset ? 'transform 300ms' : ''
+			},
+			translateY() {
+				return this.downHight > 0 ? 'translateY(' + this.downHight + 'px)' : '' // transform会使fixed失效,需注意把fixed元素写在mescroll之外
+			},
+			// 列表是否可滑动
+			scrollable(){
+				if(this.disableScroll) return false
+				return this.downLoadType===0 || this.isDownReset
+			},
+			// 是否在加载中
+			isDownLoading(){
+				return this.downLoadType === 3
+			},
+			// 旋转的角度
+			downRotate(){
+				return this.downLoadType === 2 ? 'rotate(180deg)' : 'rotate(0deg)'
+			}
+		},
+		methods: {
+			//number,rpx,upx,px,% --> px的数值
+			toPx(num){
+				if(typeof num === "string"){
+					if (num.indexOf('px') !== -1) {
+						if(num.indexOf('rpx') !== -1) { // "10rpx"
+							num = num.replace('rpx', '');
+						} else if(num.indexOf('upx') !== -1) { // "10upx"
+							num = num.replace('upx', '');
+						} else { // "10px"
+							return Number(num.replace('px', ''))
+						}
+					}else if (num.indexOf('%') !== -1){
+						// 传百分比,则相对于windowHeight,传"10%"则等于windowHeight的10%
+						let rate = Number(num.replace("%","")) / 100
+						return this.windowHeight * rate
+					}
+				}
+				return num ? uni.upx2px(Number(num)) : 0
+			},
+			//注册列表滚动事件,用于下拉刷新和上拉加载
+			scroll(e) {
+				this.mescroll.scroll(e.detail, () => {
+					this.$emit('scroll', this.mescroll) // 此时可直接通过 this.mescroll.scrollTop获取滚动条位置; this.mescroll.isScrollUp获取是否向上滑动
+				})
+			},
+			// 点击空布局的按钮回调
+			emptyClick() {
+				this.$emit('emptyclick', this.mescroll)
+			},
+			// 点击回到顶部的按钮回调
+			toTopClick() {
+				this.mescroll.scrollTo(0, this.mescroll.optUp.toTop.duration); // 执行回到顶部
+				this.$emit('topclick', this.mescroll); // 派发点击回到顶部按钮的回调
+			},
+			// 更新滚动区域的高度 (使内容不满屏和到底,都可继续翻页)
+			setClientHeight() {
+				if (this.mescroll.getClientHeight(true) === 0 && !this.isExec) {
+					this.isExec = true; // 避免多次获取
+					this.$nextTick(() => { // 确保dom已渲染
+						this.getClientInfo(data=>{
+							this.isExec = false;
+							if (data) {
+								this.mescroll.setClientHeight(data.height);
+							} else if (this.clientNum != 3) { // 极少部分情况,可能dom还未渲染完毕,递归获取,最多重试3次
+								this.clientNum = this.clientNum == null ? 1 : this.clientNum + 1;
+								setTimeout(() => {
+									this.setClientHeight()
+								}, this.clientNum * 100)
+							}
+						})
+					})
+				}
+			},
+			// 获取滚动区域的信息
+			getClientInfo(success){
+				let query = uni.createSelectorQuery();
+				// #ifndef MP-ALIPAY || MP-DINGTALK
+				query = query.in(this) // 支付宝小程序不支持in(this),而字节跳动小程序必须写in(this), 否则都取不到值
+				// #endif
+				let view = query.select('#' + this.viewId);
+				view.boundingClientRect(data => {
+					success(data)
+				}).exec();
+			}
+		},
+		// 使用created初始化mescroll对象; 如果用mounted部分css样式编译到H5会失效
+		created() {
+			let vm = this;
+
+			let diyOption = {
+				// 下拉刷新的配置
+				down: {
+					inOffset() {
+						vm.downLoadType = 1; // 下拉的距离进入offset范围内那一刻的回调 (自定义mescroll组件时,此行不可删)
+					},
+					outOffset() {
+						vm.downLoadType = 2; // 下拉的距离大于offset那一刻的回调 (自定义mescroll组件时,此行不可删)
+					},
+					onMoving(mescroll, rate, downHight) {
+						// 下拉过程中的回调,滑动过程一直在执行;
+						vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
+					},
+					showLoading(mescroll, downHight) {
+						vm.downLoadType = 3; // 显示下拉刷新进度的回调 (自定义mescroll组件时,此行不可删)
+						vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
+					},
+					endDownScroll() {
+						vm.downLoadType = 4; // 结束下拉 (自定义mescroll组件时,此行不可删)
+						vm.downHight = 0; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
+						vm.downResetTimer && clearTimeout(vm.downResetTimer)
+						vm.downResetTimer = setTimeout(()=>{ // 过渡动画执行完毕后,需重置为0的状态,以便置空this.transition,避免iOS小程序列表渲染不完整
+							if(vm.downLoadType===4) vm.downLoadType = 0
+						},300)
+					},
+					// 派发下拉刷新的回调
+					callback: function(mescroll) {
+						vm.$emit('down', mescroll)
+					}
+				},
+				// 上拉加载的配置
+				up: {
+					// 显示加载中的回调
+					showLoading() {
+						vm.upLoadType = 1;
+					},
+					// 显示无更多数据的回调
+					showNoMore() {
+						vm.upLoadType = 2;
+					},
+					// 隐藏上拉加载的回调
+					hideUpScroll(mescroll) {
+						vm.upLoadType = mescroll.optUp.hasNext ? 0 : 3;
+					},
+					// 空布局
+					empty: {
+						onShow(isShow) { // 显示隐藏的回调
+							vm.isShowEmpty = isShow;
+						}
+					},
+					// 回到顶部
+					toTop: {
+						onShow(isShow) { // 显示隐藏的回调
+							vm.isShowToTop = isShow;
+						}
+					},
+					// 派发上拉加载的回调
+					callback: function(mescroll) {
+						vm.$emit('up', mescroll);
+						// 更新容器的高度 (多mescroll的情况)
+						vm.setClientHeight()
+					}
+				}
+			}
+
+			let i18nType = mescrollI18n.getType() // 当前语言类型
+			let i18nOption = {type: i18nType} // 国际化配置
+			MeScroll.extend(i18nOption, vm.i18n) // 具体页面的国际化配置
+			MeScroll.extend(i18nOption, GlobalOption.i18n) // 全局的国际化配置
+			MeScroll.extend(diyOption, i18nOption[i18nType]); // 混入国际化配置
+			MeScroll.extend(diyOption, {down:GlobalOption.down, up:GlobalOption.up}); // 混入全局的配置
+			let myOption = JSON.parse(JSON.stringify({'down': vm.down,'up': vm.up})) // 深拷贝,避免对props的影响
+			MeScroll.extend(myOption, diyOption); // 混入具体界面的配置
+
+			// 初始化MeScroll对象
+			vm.mescroll = new MeScroll(myOption);
+			vm.mescroll.viewId = vm.viewId; // 附带id
+			// 挂载语言包
+			vm.mescroll.i18n = i18nOption;
+			// init回调mescroll对象
+			vm.$emit('init', vm.mescroll);
+			
+			// 设置高度
+			const sys = uni.getSystemInfoSync();
+			if(sys.windowTop) vm.windowTop = sys.windowTop;
+			if(sys.windowBottom) vm.windowBottom = sys.windowBottom;
+			if(sys.windowHeight) vm.windowHeight = sys.windowHeight;
+			if(sys.statusBarHeight) vm.statusBarHeight = sys.statusBarHeight;
+			// 使down的bottomOffset生效
+			vm.mescroll.setBodyHeight(sys.windowHeight);
+
+			// 因为使用的是scrollview,这里需自定义scrollTo
+			vm.mescroll.resetScrollTo((y, t) => {
+				vm.scrollAnim = (t !== 0); // t为0,则不使用动画过渡
+				if(typeof y === 'string'){
+					// 小程序不支持slot里面的scroll-into-view, 统一使用计算的方式实现
+					vm.getClientInfo(function(rect){
+						let mescrollTop = rect.top // mescroll到顶部的距离
+						let selector;
+						if(y.indexOf('#')==-1 && y.indexOf('.')==-1){
+							selector = '#'+y // 不带#和. 则默认为id选择器
+						}else{
+							selector = y
+							// #ifdef APP-PLUS || H5 || MP-ALIPAY || MP-DINGTALK
+							if(y.indexOf('>>>')!=-1){ // 不支持跨自定义组件的后代选择器 (转为普通的选择器即可跨组件查询)
+								selector = y.split('>>>')[1].trim()
+							}
+							// #endif
+						}
+						uni.createSelectorQuery().select(selector).boundingClientRect(function(rect){
+							if (rect) {
+								let curY = vm.mescroll.getScrollTop()
+								let top = rect.top - mescrollTop
+								top += curY
+								if(!vm.isFixed) top -= vm.numTop
+								vm.scrollTop = curY;
+								vm.$nextTick(function() {
+									vm.scrollTop = top
+								})
+							} else{
+								console.error(selector + ' does not exist');
+							}
+						}).exec()
+					})
+					return;
+				}
+				let curY = vm.mescroll.getScrollTop()
+				if (t === 0 || t === 300) { // 当t使用默认配置的300时,则使用系统自带的动画过渡
+					vm.scrollTop = curY;
+					vm.$nextTick(function() {
+						vm.scrollTop = y
+					})
+				} else {
+					vm.mescroll.getStep(curY, y, step => { // 此写法可支持配置t
+						vm.scrollTop = step
+					}, t)
+				}
+			})
+			
+			// 具体的界面如果不配置up.toTop.safearea,则取本vue的safearea值
+			if (vm.up && vm.up.toTop && vm.up.toTop.safearea != null) {} else {
+				vm.mescroll.optUp.toTop.safearea = vm.safearea;
+			}
+			// 全局配置监听
+			uni.$on("setMescrollGlobalOption", options=>{
+				if(!options) return;
+				let i18nType = options.i18n ? options.i18n.type : null
+				if(i18nType && vm.mescroll.i18n.type != i18nType){
+					vm.mescroll.i18n.type = i18nType
+					mescrollI18n.setType(i18nType)
+					MeScroll.extend(options, vm.mescroll.i18n[i18nType])
+				}
+				if(options.down){
+					let down = MeScroll.extend({}, options.down)
+					vm.mescroll.optDown = MeScroll.extend(down, vm.mescroll.optDown)
+				}
+				if(options.up){
+					let up = MeScroll.extend({}, options.up)
+					vm.mescroll.optUp = MeScroll.extend(up, vm.mescroll.optUp)
+				}
+			})
+		},
+		mounted() {
+			// 设置容器的高度
+			this.setClientHeight()
+		},
+		destroyed() {
+			// 注销全局配置监听
+			uni.$off("setMescrollGlobalOption")
+		}
+	}
+</script>
+
+<style>
+	@import "../../mescroll-uni/mescroll-uni.css";
+	@import "../../mescroll-uni/components/mescroll-down.css";
+	@import "../../mescroll-uni/components/mescroll-up.css";
+	@import "./components/mescroll-down.css";
+</style>

+ 44 - 0
uni_modules/mescroll-uni/components/mescroll-diy/xinlang/components/mescroll-down.css

@@ -0,0 +1,44 @@
+/*下拉刷新--上下箭头*/
+.mescroll-downwarp .downwarp-arrow {
+	display: inline-block;
+	width: 20px;
+	height: 20px;
+	margin: 10px;
+	background-image: url(https://www.mescroll.com/img/xinlang/mescroll-arrow.png);
+	background-size: contain;
+	vertical-align: middle;
+	transition: all 300ms;
+}
+
+/*下拉刷新--旋转进度条*/
+.mescroll-downwarp .downwarp-progress{
+	width: 36px;
+	height: 36px;
+	border: none;
+	margin: auto;
+	background-size: contain;
+	animation: progressRotate 0.6s steps(6, start) infinite;
+}
+@keyframes progressRotate {
+	0% {
+		background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress1.png);
+	}
+	16% {
+		background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress2.png);
+	}
+	32% {
+		background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress3.png);
+	}
+	48% {
+		background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress4.png);
+	}
+	64% {
+		background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress5.png);
+	}
+	80% {
+		background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress6.png);
+	}
+	100% {
+		background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress1.png);
+	}
+}

+ 53 - 0
uni_modules/mescroll-uni/components/mescroll-diy/xinlang/components/mescroll-down.vue

@@ -0,0 +1,53 @@
+<!-- 下拉刷新区域 -->
+<template>
+	<view v-if="mOption.use" class="mescroll-downwarp" :style="{'background':mOption.bgColor,'color':mOption.textColor}">
+		<view class="downwarp-content">
+			<view v-if="isDownLoading" class="downwarp-progress"></view>
+			<view v-else class="downwarp-arrow" :style="{ 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)
+	},
+	computed: {
+		// 支付宝小程序需写成计算属性,prop定义default仍报错
+		mOption() {
+			return this.option || {};
+		},
+		// 是否在加载中
+		isDownLoading() {
+			return this.type === 3;
+		},
+		// 旋转的角度
+		downRotate() {
+			return this.type === 2 ? 'rotate(-180deg)' : 'rotate(0deg)';
+		},
+		// 文本提示
+		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-uni/components/mescroll-down.css';
+@import './mescroll-down.css';
+</style>

+ 32 - 0
uni_modules/mescroll-uni/components/mescroll-diy/xinlang/components/mescroll-up.css

@@ -0,0 +1,32 @@
+/*上拉加载--旋转进度条*/
+.mescroll-upwarp .upwarp-progress {
+	width: 36px;
+	height: 36px;
+	border: none;
+	margin: auto;
+	background-size: contain;
+	animation: progressRotate 0.6s steps(6, start) infinite;
+}
+@keyframes progressRotate {
+	0% {
+		background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress1.png);
+	}
+	16% {
+		background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress2.png);
+	}
+	32% {
+		background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress3.png);
+	}
+	48% {
+		background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress4.png);
+	}
+	64% {
+		background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress5.png);
+	}
+	80% {
+		background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress6.png);
+	}
+	100% {
+		background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress1.png);
+	}
+}

+ 40 - 0
uni_modules/mescroll-uni/components/mescroll-diy/xinlang/components/mescroll-up.vue

@@ -0,0 +1,40 @@
+<!-- 上拉加载区域 -->
+<template>
+	<view class="mescroll-upwarp" :style="{'background':mOption.bgColor,'color':mOption.textColor}">
+		<!-- 加载中 (此处不能用v-if,否则android小程序快速上拉可能会不断触发上拉回调) -->
+		<view v-show="isUpLoading">
+			<view class="upwarp-progress mescroll-rotate"></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(没有更多了,显示END文本提示),3(没有更多了,不显示END文本提示)
+	},
+	computed: {
+		// 支付宝小程序需写成计算属性,prop定义default仍报错
+		mOption() {
+			return this.option || {};
+		},
+		// 加载中
+		isUpLoading() {
+			return this.type === 1;
+		},
+		// 没有更多了
+		isUpNoMore() {
+			return this.type === 2;
+		}
+	}
+};
+</script>
+
+<style>
+@import '../../../mescroll-uni/components/mescroll-up.css';
+@import './mescroll-up.css';
+</style>

+ 380 - 0
uni_modules/mescroll-uni/components/mescroll-diy/xinlang/mescroll-body.vue

@@ -0,0 +1,380 @@
+<template>
+	<view 
+		class="mescroll-body mescroll-render-touch" 
+		:style="{'minHeight':minHeight, 'padding-top': padTop, 'padding-bottom': padBottom}" 
+		:class="{'mescorll-sticky': sticky}"
+		@touchstart="wxsBiz.touchstartEvent" 
+		@touchmove="wxsBiz.touchmoveEvent" 
+		@touchend="wxsBiz.touchendEvent" 
+		@touchcancel="wxsBiz.touchendEvent"
+		:change:prop="wxsBiz.propObserver"
+		:prop="wxsProp"
+		>
+		
+		<!-- 状态栏 -->
+		<view v-if="topbar&&statusBarHeight" class="mescroll-topbar" :style="{height: statusBarHeight+'px', background: topbar}"></view>
+		
+		<view class="mescroll-body-content mescroll-wxs-content" :style="{ transform: translateY, transition: transition }" :change:prop="wxsBiz.callObserver" :prop="callProp">
+			<!-- 下拉加载区域 (支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-down组件实现)-->
+			<!-- <mescroll-down :option="mescroll.optDown" :type="downLoadType"></mescroll-down> -->
+			<view v-if="mescroll.optDown.use" class="mescroll-downwarp" :style="{'background':mescroll.optDown.bgColor,'color':mescroll.optDown.textColor}">
+				<view class="downwarp-content">
+					<view v-if="isDownLoading" class="downwarp-progress"></view>
+					<view v-else class="downwarp-arrow" :style="{ 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 && downLoadType !== 3" :option="mescroll.optUp" :type="upLoadType"></mescroll-up> -->
+			<view class="mescroll-upwarp" :style="{'background':mescroll.optUp.bgColor,'color':mescroll.optUp.textColor}">
+				<!-- 加载中 (此处不能用v-if,否则android小程序快速上拉可能会不断触发上拉回调) -->
+				<view v-show="upLoadType===1">
+					<view class="upwarp-progress mescroll-rotate"></view>
+					<view class="upwarp-tip">{{ mescroll.optUp.textLoading }}</view>
+				</view>
+				<!-- 无数据 -->
+				<view v-if="upLoadType===2" class="upwarp-nodata">{{ mescroll.optUp.textNoMore }}</view>
+			</view>
+		</view>
+		
+		<!-- 底部是否偏移TabBar的高度(仅H5端生效) -->
+		<!-- #ifdef H5 -->
+		<view v-if="bottombar && windowBottom>0" class="mescroll-bottombar" :style="{height: windowBottom+'px'}"></view>
+		<!-- #endif -->
+		
+		<!-- 适配iPhoneX -->
+		<view v-if="safearea" class="mescroll-safearea"></view>
+		
+		<!-- 回到顶部按钮 (fixed元素需写在transform外面,防止降级为absolute)-->
+		<mescroll-top v-model="isShowToTop" :option="mescroll.optUp.toTop" @click="toTopClick"></mescroll-top>
+		
+		<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 -->
+		<!-- renderjs的数据载体,不可写在mescroll-downwarp内部,避免use为false时,载体丢失,无法更新数据 -->
+		<view :change:prop="renderBiz.propObserver" :prop="wxsProp"></view>
+		<!-- #endif -->
+	</view>
+</template>
+
+<!-- 微信小程序, QQ小程序, app, h5使用wxs -->
+<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 -->
+<script src="../../mescroll-uni/wxs/wxs.wxs" module="wxsBiz" lang="wxs"></script>
+<!-- #endif -->
+
+<!-- app, h5使用renderjs -->
+<!-- #ifdef APP-PLUS || H5 -->
+<script module="renderBiz" lang="renderjs">
+	import renderBiz from '../../mescroll-uni/wxs/renderjs.js';
+	export default {
+		mixins: [renderBiz]
+	}
+</script>
+<!-- #endif -->
+
+<script>
+	import MeScroll from '../../mescroll-uni/mescroll-uni.js';
+	import MescrollTop from '../../mescroll-uni/components/mescroll-top.vue';
+	import WxsMixin from '../../mescroll-uni/wxs/mixins.js';
+	import mescrollI18n from '../../mescroll-uni/mescroll-i18n.js';
+	import GlobalOption from './mescroll-uni-option.js';
+
+	export default {
+		mixins: [WxsMixin],
+		components: {
+			MescrollTop
+		},
+		data() {
+			return {
+				mescroll: null, // mescroll实例
+				downHight: 0, //下拉刷新: 容器高度
+				downLoadType: 0, // 下拉刷新状态: 0(loading前), 1(inOffset), 2(outOffset), 3(showLoading), 4(endDownScroll)
+				upLoadType: 0, // 上拉加载状态:0(loading前),1(loading中),2(没有更多了,显示END文本提示),3(没有更多了,不显示END文本提示)
+				isShowEmpty: false, // 是否显示空布局
+				isShowToTop: false, // 是否显示回到顶部按钮
+				windowHeight: 0, // 可使用窗口的高度
+				windowBottom: 0, // 可使用窗口的底部位置
+				statusBarHeight: 0 // 状态栏高度
+			};
+		},
+		props: {
+			down: Object, // 下拉刷新的参数配置
+			up: Object, // 上拉加载的参数配置
+			i18n: Object, // 国际化的参数配置
+			top: [String, Number], // 下拉布局往下的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
+			topbar: [Boolean, String], // top的偏移量是否加上状态栏高度, 默认false (使用场景:取消原生导航栏时,配置此项可留出状态栏的占位, 支持传入字符串背景,如色值,背景图,渐变)
+			bottom: [String, Number], // 上拉布局往上的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
+			safearea: Boolean, // bottom的偏移量是否加上底部安全区的距离, 默认false (需要适配iPhoneX时使用)
+			height: [String, Number], // 指定mescroll最小高度,默认windowHeight,使列表不满屏仍可下拉
+			bottombar:{ // 底部是否偏移TabBar的高度(默认仅在H5端的tab页生效)
+				type: Boolean,
+				default: true
+			},
+			sticky: Boolean // 是否支持sticky,默认false; 当值配置true时,需避免在mescroll-body标签前面加非定位的元素,否则下拉区域无法会隐藏
+		},
+		computed: {
+			// mescroll最小高度,默认windowHeight,使列表不满屏仍可下拉
+			minHeight(){
+				return this.toPx(this.height || '100%') + 'px'
+			},
+			// 下拉布局往下偏移的距离 (px)
+			numTop() {
+				return this.toPx(this.top)
+			},
+			padTop() {
+				return this.numTop + 'px';
+			},
+			// 上拉布局往上偏移 (px)
+			numBottom() {
+				return this.toPx(this.bottom);
+			},
+			padBottom() {
+				return this.numBottom + 'px';
+			},
+			// 是否为重置下拉的状态
+			isDownReset() {
+				return this.downLoadType === 3 || this.downLoadType === 4;
+			},
+			// 过渡
+			transition() {
+				return this.isDownReset ? 'transform 300ms' : '';
+			},
+			translateY() {
+				return this.downHight > 0 ? 'translateY(' + this.downHight + 'px)' : ''; // transform会使fixed失效,需注意把fixed元素写在mescroll之外
+			},
+			// 是否在加载中
+			isDownLoading() {
+				return this.downLoadType === 3;
+			},
+			// 旋转的角度
+			downRotate() {
+				return this.downLoadType === 2 ? 'rotate(-180deg)' : 'rotate(0deg)';
+			},
+			// 文本提示
+			downText() {
+				if(!this.mescroll) return "";
+				switch (this.downLoadType) {
+					case 1:
+						return this.mescroll.optDown.textInOffset;
+					case 2:
+						return this.mescroll.optDown.textOutOffset;
+					case 3:
+						return this.mescroll.optDown.textLoading;
+					case 4:
+						return this.mescroll.isDownEndSuccess ? this.mescroll.optDown.textSuccess : this.mescroll.isDownEndSuccess==false ? this.mescroll.optDown.textErr : this.mescroll.optDown.textInOffset;
+					default:
+						return this.mescroll.optDown.textInOffset;
+				}
+			}
+		},
+		methods: {
+			//number,rpx,upx,px,% --> px的数值
+			toPx(num) {
+				if (typeof num === 'string') {
+					if (num.indexOf('px') !== -1) {
+						if (num.indexOf('rpx') !== -1) {
+							// "10rpx"
+							num = num.replace('rpx', '');
+						} else if (num.indexOf('upx') !== -1) {
+							// "10upx"
+							num = num.replace('upx', '');
+						} else {
+							// "10px"
+							return Number(num.replace('px', ''));
+						}
+					} else if (num.indexOf('%') !== -1) {
+						// 传百分比,则相对于windowHeight,传"10%"则等于windowHeight的10%
+						let rate = Number(num.replace('%', '')) / 100;
+						return this.windowHeight * rate;
+					}
+				}
+				return num ? uni.upx2px(Number(num)) : 0;
+			},
+			// 点击空布局的按钮回调
+			emptyClick() {
+				this.$emit('emptyclick', this.mescroll);
+			},
+			// 点击回到顶部的按钮回调
+			toTopClick() {
+				this.mescroll.scrollTo(0, this.mescroll.optUp.toTop.duration); // 执行回到顶部
+				this.$emit('topclick', this.mescroll); // 派发点击回到顶部按钮的回调
+			}
+		},
+		// 使用created初始化mescroll对象; 如果用mounted部分css样式编译到H5会失效
+		created() {
+			let vm = this;
+
+			let diyOption = {
+				// 下拉刷新的配置
+				down: {
+					inOffset() {
+						vm.downLoadType = 1; // 下拉的距离进入offset范围内那一刻的回调 (自定义mescroll组件时,此行不可删)
+					},
+					outOffset() {
+						vm.downLoadType = 2; // 下拉的距离大于offset那一刻的回调 (自定义mescroll组件时,此行不可删)
+					},
+					onMoving(mescroll, rate, downHight) {
+						// 下拉过程中的回调,滑动过程一直在执行;
+						vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
+					},
+					showLoading(mescroll, downHight) {
+						vm.downLoadType = 3; // 显示下拉刷新进度的回调 (自定义mescroll组件时,此行不可删)
+						vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
+					},
+					beforeEndDownScroll(mescroll){
+						vm.downLoadType = 4; 
+						return mescroll.optDown.beforeEndDelay // 延时结束的时长
+					},
+					endDownScroll() {
+						vm.downLoadType = 4; // 结束下拉 (自定义mescroll组件时,此行不可删)
+						vm.downHight = 0; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
+						if(vm.downResetTimer) {clearTimeout(vm.downResetTimer); vm.downResetTimer = null} // 移除重置倒计时
+						vm.downResetTimer = setTimeout(()=>{ // 过渡动画执行完毕后,需重置为0的状态,避免下次inOffset不及时显示textInOffset
+							if(vm.downLoadType === 4) vm.downLoadType = 0
+						},300)
+					},
+					// 派发下拉刷新的回调
+					callback: function(mescroll) {
+						vm.$emit('down', mescroll);
+					}
+				},
+				// 上拉加载的配置
+				up: {
+					// 显示加载中的回调
+					showLoading() {
+						vm.upLoadType = 1;
+					},
+					// 显示无更多数据的回调
+					showNoMore() {
+						vm.upLoadType = 2;
+					},
+					// 隐藏上拉加载的回调
+					hideUpScroll(mescroll) {
+						vm.upLoadType = mescroll.optUp.hasNext ? 0 : 3;
+					},
+					// 空布局
+					empty: {
+						onShow(isShow) {
+							// 显示隐藏的回调
+							vm.isShowEmpty = isShow;
+						}
+					},
+					// 回到顶部
+					toTop: {
+						onShow(isShow) {
+							// 显示隐藏的回调
+							vm.isShowToTop = isShow;
+						}
+					},
+					// 派发上拉加载的回调
+					callback: function(mescroll) {
+						vm.$emit('up', mescroll);
+					}
+				}
+			};
+
+			let i18nType = mescrollI18n.getType() // 当前语言类型
+			let i18nOption = {type: i18nType} // 国际化配置
+			MeScroll.extend(i18nOption, vm.i18n) // 具体页面的国际化配置
+			MeScroll.extend(i18nOption, GlobalOption.i18n) // 全局的国际化配置
+			MeScroll.extend(diyOption, i18nOption[i18nType]); // 混入国际化配置
+			MeScroll.extend(diyOption, {down:GlobalOption.down, up:GlobalOption.up}); // 混入全局的配置
+			let myOption = JSON.parse(JSON.stringify({down: vm.down,up: vm.up})); // 深拷贝,避免对props的影响
+			MeScroll.extend(myOption, diyOption); // 混入具体界面的配置
+
+			// 初始化MeScroll对象
+			vm.mescroll = new MeScroll(myOption, true); // 传入true,标记body为滚动区域
+			// 挂载语言包
+			vm.mescroll.i18n = i18nOption;
+			// init回调mescroll对象
+			vm.$emit('init', vm.mescroll);
+
+			// 设置高度
+			const sys = uni.getSystemInfoSync();
+			if (sys.windowHeight) vm.windowHeight = sys.windowHeight;
+			if (sys.windowBottom) vm.windowBottom = sys.windowBottom;
+			if (sys.statusBarHeight) vm.statusBarHeight = sys.statusBarHeight;
+			// 使down的bottomOffset生效
+			vm.mescroll.setBodyHeight(sys.windowHeight);
+			
+			// 因为使用的是page的scroll,这里需自定义scrollTo
+			vm.mescroll.resetScrollTo((y, t) => {
+				if(typeof y === 'string'){
+					// 滚动到指定view (y为css选择器)
+					setTimeout(()=>{ // 延时确保view已渲染; 不使用$nextTick
+						let selector;
+						if(y.indexOf('#')==-1 && y.indexOf('.')==-1){
+							selector = '#'+y // 不带#和. 则默认为id选择器
+						}else{
+							selector = y
+							// #ifdef APP-PLUS || H5 || MP-ALIPAY || MP-DINGTALK
+							if(y.indexOf('>>>')!=-1){ // 不支持跨自定义组件的后代选择器 (转为普通的选择器即可跨组件查询)
+								selector = y.split('>>>')[1].trim()
+							}
+							// #endif
+						}
+						uni.createSelectorQuery().select(selector).boundingClientRect(function(rect){
+							if (rect) {
+								let top = rect.top
+								top += vm.mescroll.getScrollTop()
+								uni.pageScrollTo({
+									scrollTop: top,
+									duration: t
+								})
+							} else{
+								console.error(selector + ' does not exist');
+							}
+						}).exec()
+					},30)
+				} else{
+					// 滚动到指定位置 (y必须为数字)
+					uni.pageScrollTo({
+						scrollTop: y,
+						duration: t
+					})
+				}
+			});
+			
+			// 具体的界面如果不配置up.toTop.safearea,则取本vue的safearea值
+			if (vm.up && vm.up.toTop && vm.up.toTop.safearea != null) {} else {
+				vm.mescroll.optUp.toTop.safearea = vm.safearea;
+			}
+			
+			// 全局配置监听
+			uni.$on("setMescrollGlobalOption", options=>{
+				if(!options) return;
+				let i18nType = options.i18n ? options.i18n.type : null
+				if(i18nType && vm.mescroll.i18n.type != i18nType){
+					vm.mescroll.i18n.type = i18nType
+					mescrollI18n.setType(i18nType)
+					MeScroll.extend(options, vm.mescroll.i18n[i18nType])
+				}
+				if(options.down){
+					let down = MeScroll.extend({}, options.down)
+					vm.mescroll.optDown = MeScroll.extend(down, vm.mescroll.optDown)
+				}
+				if(options.up){
+					let up = MeScroll.extend({}, options.up)
+					vm.mescroll.optUp = MeScroll.extend(up, vm.mescroll.optUp)
+				}
+			})
+		},
+		destroyed() {
+			// 注销全局配置监听
+			uni.$off("setMescrollGlobalOption")
+		}
+	};
+</script>
+
+<style>
+	@import "../../mescroll-uni/mescroll-uni.css";
+	@import "../../mescroll-uni/components/mescroll-down.css";
+	@import "../../mescroll-uni/components/mescroll-up.css";
+	@import "./components/mescroll-down.css";
+	@import "./components/mescroll-up.css";
+</style>

+ 64 - 0
uni_modules/mescroll-uni/components/mescroll-diy/xinlang/mescroll-uni-option.js

@@ -0,0 +1,64 @@
+// 全局配置
+// mescroll-body 和 mescroll-uni 通用
+const GlobalOption = {
+	down: {
+		// 其他down的配置参数也可以写,这里只展示了常用的配置:
+		offset: 80, // 在列表顶部,下拉大于80px,松手即可触发下拉刷新的回调
+		native: false // 是否使用系统自带的下拉刷新; 默认false; 仅在mescroll-body生效 (值为true时,还需在pages配置enablePullDownRefresh:true;详请参考mescroll-native的案例)
+	},
+	up: {
+		// 其他up的配置参数也可以写,这里只展示了常用的配置:
+		offset: 150, // 距底部多远时,触发upCallback,仅mescroll-uni生效 ( mescroll-body配置的是pages.json的 onReachBottomDistance )
+		toTop: {
+			// 回到顶部按钮,需配置src才显示
+			src: "https://www.mescroll.com/img/mescroll-totop.png", // 图片路径 (建议放入static目录, 如 /static/img/mescroll-totop.png )
+			offset: 1000, // 列表滚动多少距离才显示回到顶部按钮,默认1000px
+			right: 20, // 到右边的距离, 默认20 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx)
+			bottom: 120, // 到底部的距离, 默认120 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx)
+			width: 72 // 回到顶部图标的宽度, 默认72 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx)
+		},
+		empty: {
+			use: true, // 是否显示空布局
+			icon: "https://www.mescroll.com/img/mescroll-empty.png" // 图标路径 (建议放入static目录, 如 /static/img/mescroll-empty.png )
+		}
+	},
+	// 国际化配置
+	i18n: {
+		// 中文
+		zh: {
+			down: {
+				textInOffset: '下拉刷新', // 下拉的距离在offset范围内的提示文本
+				textOutOffset: '释放更新', // 下拉的距离大于offset范围的提示文本
+				textLoading: '加载中 ...', // 加载中的提示文本
+				textSuccess: '加载成功', // 加载成功的文本
+				textErr: '加载失败', // 加载失败的文本
+			},
+			up: {
+				textLoading: '加载中 ...', // 加载中的提示文本
+				textNoMore: '-- END --', // 没有更多数据的提示文本
+				empty: {
+					tip: '~ 空空如也 ~' // 空提示
+				}
+			}
+		},
+		// 英文
+		en: {
+			down: {
+				textInOffset: 'drop down refresh',
+				textOutOffset: 'release updates',
+				textLoading: 'loading ...',
+				textSuccess: 'loaded successfully',
+				textErr: 'loading failed'
+			},
+			up: {
+				textLoading: 'loading ...',
+				textNoMore: '-- END --',
+				empty: {
+					tip: '~ absolutely empty ~'
+				}
+			}
+		}
+	}
+}
+
+export default GlobalOption

+ 462 - 0
uni_modules/mescroll-uni/components/mescroll-diy/xinlang/mescroll-uni.vue

@@ -0,0 +1,462 @@
+<template>
+	<view class="mescroll-uni-warp">
+		<scroll-view :id="viewId" class="mescroll-uni" :class="{'mescroll-uni-fixed':isFixed}" :style="{'height':scrollHeight,'padding-top':padTop,'padding-bottom':padBottom,'top':fixedTop,'bottom':fixedBottom}" :scroll-top="scrollTop" :scroll-with-animation="scrollAnim" @scroll="scroll"  :scroll-y='scrollable' :enable-back-to-top="true" :throttle="false">
+			<view class="mescroll-uni-content mescroll-render-touch"
+			@touchstart="wxsBiz.touchstartEvent" 
+			@touchmove="wxsBiz.touchmoveEvent" 
+			@touchend="wxsBiz.touchendEvent" 
+			@touchcancel="wxsBiz.touchendEvent"
+			:change:prop="wxsBiz.propObserver"
+			:prop="wxsProp">
+			
+				<!-- 状态栏 -->
+				<view v-if="topbar&&statusBarHeight" class="mescroll-topbar" :style="{height: statusBarHeight+'px', background: topbar}"></view>
+				
+				<view class="mescroll-wxs-content" :style="{'transform': translateY, 'transition': transition}" :change:prop="wxsBiz.callObserver" :prop="callProp">
+					<!-- 下拉加载区域 (支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-down组件实现)-->
+					<!-- <mescroll-down :option="mescroll.optDown" :type="downLoadType"></mescroll-down> -->
+					<view v-if="mescroll.optDown.use" class="mescroll-downwarp" :style="{'background':mescroll.optDown.bgColor,'color':mescroll.optDown.textColor}">
+						<view class="downwarp-content">
+							<view v-if="isDownLoading" class="downwarp-progress"></view>
+							<view v-else class="downwarp-arrow" :style="{ 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 && downLoadType !== 3" :option="mescroll.optUp" :type="upLoadType"></mescroll-up> -->
+					<view class="mescroll-upwarp" :style="{'background':mescroll.optUp.bgColor,'color':mescroll.optUp.textColor}">
+						<!-- 加载中 (此处不能用v-if,否则android小程序快速上拉可能会不断触发上拉回调) -->
+						<view v-show="upLoadType===1">
+							<view class="upwarp-progress mescroll-rotate"></view>
+							<view class="upwarp-tip">{{ mescroll.optUp.textLoading }}</view>
+						</view>
+						<!-- 无数据 -->
+						<view v-if="upLoadType===2" class="upwarp-nodata">{{ mescroll.optUp.textNoMore }}</view>
+					</view>
+				</view>
+				
+				<!-- 底部是否偏移TabBar的高度(仅H5端生效) -->
+				<!-- #ifdef H5 -->
+				<view v-if="bottombar && windowBottom>0" class="mescroll-bottombar" :style="{height: windowBottom+'px'}"></view>
+				<!-- #endif -->
+				
+				<!-- 适配iPhoneX -->
+				<view v-if="safearea" class="mescroll-safearea"></view>
+			
+			</view>
+		</scroll-view>
+
+		<!-- 回到顶部按钮 (fixed元素,需写在scroll-view外面,防止滚动的时候抖动)-->
+		<mescroll-top v-model="isShowToTop" :option="mescroll.optUp.toTop" @click="toTopClick"></mescroll-top>
+		
+		<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 -->
+		<!-- renderjs的数据载体,不可写在mescroll-downwarp内部,避免use为false时,载体丢失,无法更新数据 -->
+		<view :change:prop="renderBiz.propObserver" :prop="wxsProp"></view>
+		<!-- #endif -->
+	</view>
+</template>
+
+<!-- 微信小程序, QQ小程序, app, h5使用wxs -->
+<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 -->
+<script src="../../mescroll-uni/wxs/wxs.wxs" module="wxsBiz" lang="wxs"></script>
+<!-- #endif -->
+
+<!-- app, h5使用renderjs -->
+<!-- #ifdef APP-PLUS || H5 -->
+<script module="renderBiz" lang="renderjs">
+	import renderBiz from '../../mescroll-uni/wxs/renderjs.js';
+	export default {
+		mixins: [renderBiz]
+	}
+</script>
+<!-- #endif -->
+
+<script>
+	import MeScroll from '../../mescroll-uni/mescroll-uni.js';
+	import MescrollTop from '../../mescroll-uni/components/mescroll-top.vue';
+	import WxsMixin from '../../mescroll-uni/wxs/mixins.js';
+	import mescrollI18n from '../../mescroll-uni/mescroll-i18n.js';
+	import GlobalOption from './mescroll-uni-option.js';
+	
+	export default {
+		mixins: [WxsMixin],
+		components: {
+			MescrollTop
+		},
+		data() {
+			return {
+				mescroll: null, // mescroll实例
+				viewId: 'id_' + Math.random().toString(36).substr(2,16), // 随机生成mescroll的id(不能数字开头,否则找不到元素)
+				downHight: 0, //下拉刷新: 容器高度
+				downLoadType: 0, // 下拉刷新状态: 0(loading前), 1(inOffset), 2(outOffset), 3(showLoading), 4(endDownScroll)
+				upLoadType: 0, // 上拉加载状态: 0(loading前), 1loading中, 2没有更多了,显示END文本提示, 3(没有更多了,不显示END文本提示)
+				isShowEmpty: false, // 是否显示空布局
+				isShowToTop: false, // 是否显示回到顶部按钮
+				scrollTop: 0, // 滚动条的位置
+				scrollAnim: false, // 是否开启滚动动画
+				windowTop: 0, // 可使用窗口的顶部位置
+				windowBottom: 0, // 可使用窗口的底部位置
+				windowHeight: 0, // 可使用窗口的高度
+				statusBarHeight: 0 // 状态栏高度
+			}
+		},
+		props: {
+			down: Object, // 下拉刷新的参数配置
+			up: Object, // 上拉加载的参数配置
+			i18n: Object, // 国际化的参数配置
+			top: [String, Number], // 下拉布局往下的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
+			topbar: [Boolean, String], // top的偏移量是否加上状态栏高度, 默认false (使用场景:取消原生导航栏时,配置此项可留出状态栏的占位, 支持传入字符串背景,如色值,背景图,渐变)
+			bottom: [String, Number], // 上拉布局往上的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
+			safearea: Boolean, // bottom的偏移量是否加上底部安全区的距离, 默认false (需要适配iPhoneX时使用)
+			fixed: { // 是否通过fixed固定mescroll的高度, 默认true
+				type: Boolean,
+				default: true
+			},
+			height: [String, Number], // 指定mescroll的高度, 此项有值,则不使用fixed. (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
+			bottombar:{ // 底部是否偏移TabBar的高度(默认仅在H5端的tab页生效)
+				type: Boolean,
+				default: true
+			},
+			disableScroll: Boolean // 是否禁止滚动
+		},
+		computed: {
+			// 是否使用fixed定位 (当height有值,则不使用)
+			isFixed(){
+				return !this.height && this.fixed
+			},
+			// mescroll的高度
+			scrollHeight(){
+				if (this.isFixed) {
+					return "auto"
+				} else if(this.height){
+					return this.toPx(this.height) + 'px'
+				}else{
+					return "100%"
+				}
+			},
+			// 下拉布局往下偏移的距离 (px)
+			numTop() {
+				return this.toPx(this.top)
+			},
+			fixedTop() {
+				return this.isFixed ? (this.numTop + this.windowTop) + 'px' : 0
+			},
+			padTop() {
+				return !this.isFixed ? this.numTop + 'px' : 0
+			},
+			// 上拉布局往上偏移 (px)
+			numBottom() {
+				return this.toPx(this.bottom)
+			},
+			fixedBottom() {
+				return this.isFixed ? (this.numBottom + this.windowBottom) + 'px' : 0
+			},
+			padBottom() {
+				return !this.isFixed ? this.numBottom + 'px' : 0
+			},
+			// 是否为重置下拉的状态
+			isDownReset(){
+				return this.downLoadType===3 || this.downLoadType===4
+			},
+			// 过渡
+			transition() {
+				return this.isDownReset ? 'transform 300ms' : ''
+			},
+			translateY() {
+				return this.downHight > 0 ? 'translateY(' + this.downHight + 'px)' : '' // transform会使fixed失效,需注意把fixed元素写在mescroll之外
+			},
+			// 列表是否可滑动
+			scrollable(){
+				if(this.disableScroll) return false
+				return this.downLoadType===0 || this.isDownReset
+			},
+			// 是否在加载中
+			isDownLoading() {
+				return this.downLoadType === 3;
+			},
+			// 旋转的角度
+			downRotate() {
+				return this.downLoadType === 2 ? 'rotate(-180deg)' : 'rotate(0deg)';
+			},
+			// 文本提示
+			downText() {
+				if(!this.mescroll) return "";
+				switch (this.downLoadType) {
+					case 1:
+						return this.mescroll.optDown.textInOffset;
+					case 2:
+						return this.mescroll.optDown.textOutOffset;
+					case 3:
+						return this.mescroll.optDown.textLoading;
+					case 4:
+						return this.mescroll.isDownEndSuccess ? this.mescroll.optDown.textSuccess : this.mescroll.isDownEndSuccess==false ? this.mescroll.optDown.textErr : this.mescroll.optDown.textInOffset;
+					default:
+						return this.mescroll.optDown.textInOffset;
+				}
+			}
+		},
+		methods: {
+			//number,rpx,upx,px,% --> px的数值
+			toPx(num){
+				if(typeof num === "string"){
+					if (num.indexOf('px') !== -1) {
+						if(num.indexOf('rpx') !== -1) { // "10rpx"
+							num = num.replace('rpx', '');
+						} else if(num.indexOf('upx') !== -1) { // "10upx"
+							num = num.replace('upx', '');
+						} else { // "10px"
+							return Number(num.replace('px', ''))
+						}
+					}else if (num.indexOf('%') !== -1){
+						// 传百分比,则相对于windowHeight,传"10%"则等于windowHeight的10%
+						let rate = Number(num.replace("%","")) / 100
+						return this.windowHeight * rate
+					}
+				}
+				return num ? uni.upx2px(Number(num)) : 0
+			},
+			//注册列表滚动事件,用于下拉刷新和上拉加载
+			scroll(e) {
+				this.mescroll.scroll(e.detail, () => {
+					this.$emit('scroll', this.mescroll) // 此时可直接通过 this.mescroll.scrollTop获取滚动条位置; this.mescroll.isScrollUp获取是否向上滑动
+				})
+			},
+			// 点击空布局的按钮回调
+			emptyClick() {
+				this.$emit('emptyclick', this.mescroll)
+			},
+			// 点击回到顶部的按钮回调
+			toTopClick() {
+				this.mescroll.scrollTo(0, this.mescroll.optUp.toTop.duration); // 执行回到顶部
+				this.$emit('topclick', this.mescroll); // 派发点击回到顶部按钮的回调
+			},
+			// 更新滚动区域的高度 (使内容不满屏和到底,都可继续翻页)
+			setClientHeight() {
+				if (this.mescroll.getClientHeight(true) === 0 && !this.isExec) {
+					this.isExec = true; // 避免多次获取
+					this.$nextTick(() => { // 确保dom已渲染
+						this.getClientInfo(data=>{
+							this.isExec = false;
+							if (data) {
+								this.mescroll.setClientHeight(data.height);
+							} else if (this.clientNum != 3) { // 极少部分情况,可能dom还未渲染完毕,递归获取,最多重试3次
+								this.clientNum = this.clientNum == null ? 1 : this.clientNum + 1;
+								setTimeout(() => {
+									this.setClientHeight()
+								}, this.clientNum * 100)
+							}
+						})
+					})
+				}
+			},
+			// 获取滚动区域的信息
+			getClientInfo(success){
+				let query = uni.createSelectorQuery();
+				// #ifndef MP-ALIPAY || MP-DINGTALK
+				query = query.in(this) // 支付宝小程序不支持in(this),而字节跳动小程序必须写in(this), 否则都取不到值
+				// #endif
+				let view = query.select('#' + this.viewId);
+				view.boundingClientRect(data => {
+					success(data)
+				}).exec();
+			}
+		},
+		// 使用created初始化mescroll对象; 如果用mounted部分css样式编译到H5会失效
+		created() {
+			let vm = this;
+
+			let diyOption = {
+				// 下拉刷新的配置
+				down: {
+					inOffset() {
+						vm.downLoadType = 1; // 下拉的距离进入offset范围内那一刻的回调 (自定义mescroll组件时,此行不可删)
+					},
+					outOffset() {
+						vm.downLoadType = 2; // 下拉的距离大于offset那一刻的回调 (自定义mescroll组件时,此行不可删)
+					},
+					onMoving(mescroll, rate, downHight) {
+						// 下拉过程中的回调,滑动过程一直在执行;
+						vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
+					},
+					showLoading(mescroll, downHight) {
+						vm.downLoadType = 3; // 显示下拉刷新进度的回调 (自定义mescroll组件时,此行不可删)
+						vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
+					},
+					beforeEndDownScroll(mescroll){
+						vm.downLoadType = 4; 
+						return mescroll.optDown.beforeEndDelay // 延时结束的时长
+					},
+					endDownScroll() {
+						vm.downLoadType = 4; // 结束下拉 (自定义mescroll组件时,此行不可删)
+						vm.downHight = 0; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
+						vm.downResetTimer && clearTimeout(vm.downResetTimer)
+						vm.downResetTimer = setTimeout(()=>{ // 过渡动画执行完毕后,需重置为0的状态,以便置空this.transition,避免iOS小程序列表渲染不完整
+							if(vm.downLoadType===4) vm.downLoadType = 0
+						},300)
+					},
+					// 派发下拉刷新的回调
+					callback: function(mescroll) {
+						vm.$emit('down', mescroll)
+					}
+				},
+				// 上拉加载的配置
+				up: {
+					// 显示加载中的回调
+					showLoading() {
+						vm.upLoadType = 1;
+					},
+					// 显示无更多数据的回调
+					showNoMore() {
+						vm.upLoadType = 2;
+					},
+					// 隐藏上拉加载的回调
+					hideUpScroll(mescroll) {
+						vm.upLoadType = mescroll.optUp.hasNext ? 0 : 3;
+					},
+					// 空布局
+					empty: {
+						onShow(isShow) { // 显示隐藏的回调
+							vm.isShowEmpty = isShow;
+						}
+					},
+					// 回到顶部
+					toTop: {
+						onShow(isShow) { // 显示隐藏的回调
+							vm.isShowToTop = isShow;
+						}
+					},
+					// 派发上拉加载的回调
+					callback: function(mescroll) {
+						vm.$emit('up', mescroll);
+						// 更新容器的高度 (多mescroll的情况)
+						vm.setClientHeight()
+					}
+				}
+			}
+
+			let i18nType = mescrollI18n.getType() // 当前语言类型
+			let i18nOption = {type: i18nType} // 国际化配置
+			MeScroll.extend(i18nOption, vm.i18n) // 具体页面的国际化配置
+			MeScroll.extend(i18nOption, GlobalOption.i18n) // 全局的国际化配置
+			MeScroll.extend(diyOption, i18nOption[i18nType]); // 混入国际化配置
+			MeScroll.extend(diyOption, {down:GlobalOption.down, up:GlobalOption.up}); // 混入全局的配置
+			let myOption = JSON.parse(JSON.stringify({
+				'down': vm.down,
+				'up': vm.up
+			})) // 深拷贝,避免对props的影响
+			MeScroll.extend(myOption, diyOption); // 混入具体界面的配置
+
+			// 初始化MeScroll对象
+			vm.mescroll = new MeScroll(myOption);
+			vm.mescroll.viewId = vm.viewId; // 附带id
+			// 挂载语言包
+			vm.mescroll.i18n = i18nOption;
+			// init回调mescroll对象
+			vm.$emit('init', vm.mescroll);
+			
+			// 设置高度
+			const sys = uni.getSystemInfoSync();
+			if(sys.windowTop) vm.windowTop = sys.windowTop;
+			if(sys.windowBottom) vm.windowBottom = sys.windowBottom;
+			if(sys.windowHeight) vm.windowHeight = sys.windowHeight;
+			if(sys.statusBarHeight) vm.statusBarHeight = sys.statusBarHeight;
+			// 使down的bottomOffset生效
+			vm.mescroll.setBodyHeight(sys.windowHeight);
+
+			// 因为使用的是scrollview,这里需自定义scrollTo
+			vm.mescroll.resetScrollTo((y, t) => {
+				vm.scrollAnim = (t !== 0); // t为0,则不使用动画过渡
+				if(typeof y === 'string'){
+					// 小程序不支持slot里面的scroll-into-view, 统一使用计算的方式实现
+					vm.getClientInfo(function(rect){
+						let mescrollTop = rect.top // mescroll到顶部的距离
+						let selector;
+						if(y.indexOf('#')==-1 && y.indexOf('.')==-1){
+							selector = '#'+y // 不带#和. 则默认为id选择器
+						}else{
+							selector = y
+							// #ifdef APP-PLUS || H5 || MP-ALIPAY || MP-DINGTALK
+							if(y.indexOf('>>>')!=-1){ // 不支持跨自定义组件的后代选择器 (转为普通的选择器即可跨组件查询)
+								selector = y.split('>>>')[1].trim()
+							}
+							// #endif
+						}
+						uni.createSelectorQuery().select(selector).boundingClientRect(function(rect){
+							if (rect) {
+								let curY = vm.mescroll.getScrollTop()
+								let top = rect.top - mescrollTop
+								top += curY
+								if(!vm.isFixed) top -= vm.numTop
+								vm.scrollTop = curY;
+								vm.$nextTick(function() {
+									vm.scrollTop = top
+								})
+							} else{
+								console.error(selector + ' does not exist');
+							}
+						}).exec()
+					})
+					return;
+				}
+				let curY = vm.mescroll.getScrollTop()
+				if (t === 0 || t === 300) { // 当t使用默认配置的300时,则使用系统自带的动画过渡
+					vm.scrollTop = curY;
+					vm.$nextTick(function() {
+						vm.scrollTop = y
+					})
+				} else {
+					vm.mescroll.getStep(curY, y, step => { // 此写法可支持配置t
+						vm.scrollTop = step
+					}, t)
+				}
+			})
+			
+			// 具体的界面如果不配置up.toTop.safearea,则取本vue的safearea值
+			if (vm.up && vm.up.toTop && vm.up.toTop.safearea != null) {} else {
+				vm.mescroll.optUp.toTop.safearea = vm.safearea;
+			}
+			
+			// 全局配置监听
+			uni.$on("setMescrollGlobalOption", options=>{
+				if(!options) return;
+				let i18nType = options.i18n ? options.i18n.type : null
+				if(i18nType && vm.mescroll.i18n.type != i18nType){
+					vm.mescroll.i18n.type = i18nType
+					mescrollI18n.setType(i18nType)
+					MeScroll.extend(options, vm.mescroll.i18n[i18nType])
+				}
+				if(options.down){
+					let down = MeScroll.extend({}, options.down)
+					vm.mescroll.optDown = MeScroll.extend(down, vm.mescroll.optDown)
+				}
+				if(options.up){
+					let up = MeScroll.extend({}, options.up)
+					vm.mescroll.optUp = MeScroll.extend(up, vm.mescroll.optUp)
+				}
+			})
+		},
+		mounted() {
+			// 设置容器的高度
+			this.setClientHeight()
+		},
+		destroyed() {
+			// 注销全局配置监听
+			uni.$off("setMescrollGlobalOption")
+		}
+	}
+</script>
+
+<style>
+	@import "../../mescroll-uni/mescroll-uni.css";
+	@import "../../mescroll-uni/components/mescroll-down.css";
+	@import "../../mescroll-uni/components/mescroll-up.css";
+	@import "./components/mescroll-down.css";
+	@import "./components/mescroll-up.css";
+</style>

+ 116 - 0
uni_modules/mescroll-uni/components/mescroll-empty/mescroll-empty.vue

@@ -0,0 +1,116 @@
+<!--空布局:
+遵循easycom规范, 可作为独立的组件, 不使用mescroll的页面也能使用:
+<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 }">
+		<view> <image v-if="icon" class="empty-icon" :src="icon" mode="widthFix" /> </view>
+		<view v-if="tip" class="empty-tip">{{ tip }}</view>
+		<view v-if="btnText" class="empty-btn" @click="emptyClick">{{ btnText }}</view>
+	</view>
+</template>
+
+<script>
+// 引入全局配置
+import GlobalOption from '../mescroll-uni/mescroll-uni-option.js';
+// 引入国际化工具类
+import mescrollI18n from '../mescroll-uni/mescroll-i18n.js';
+export default {
+	props: {
+		// empty的配置项: 默认为GlobalOption.up.empty
+		option: {
+			type: Object,
+			default() {
+				return {};
+			}
+		}
+	},
+	// 使用computed获取配置,用于支持option的动态配置
+	computed: {
+		// 图标
+		icon() {
+			if (this.option.icon != null) { // 此处不使用短路求值, 用于支持传空串不显示图标
+				return this.option.icon
+			} else{
+				let i18nType = mescrollI18n.getType() // 国际化配置
+				if (this.option.i18n) {
+					return this.option.i18n[i18nType].icon
+				} else{
+					return GlobalOption.i18n[i18nType].up.empty.icon || GlobalOption.up.empty.icon
+				}
+			}
+		},
+		// 文本提示
+		tip() {
+			if (this.option.tip != null) { // 支持传空串不显示文本提示
+				return this.option.tip
+			} else{
+				let i18nType = mescrollI18n.getType() // 国际化配置
+				if (this.option.i18n) {
+					return this.option.i18n[i18nType].tip
+				} else{
+					return GlobalOption.i18n[i18nType].up.empty.tip || GlobalOption.up.empty.tip
+				}
+			}
+		},
+		// 按钮文本
+		btnText() {
+			if (this.option.i18n) {
+				let i18nType = mescrollI18n.getType() // 国际化配置
+				return this.option.i18n[i18nType].btnText
+			} else{
+				return this.option.btnText
+			}
+		}
+	},
+	methods: {
+		// 点击按钮
+		emptyClick() {
+			this.$emit('emptyclick');
+		}
+	}
+};
+</script>
+
+<style>
+/* 无任何数据的空布局 */
+.mescroll-empty {
+	box-sizing: border-box;
+	width: 100%;
+	padding: 300rpx 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: 200rpx;
+	height: 150rpx;
+}
+
+.mescroll-empty .empty-tip {
+	margin-top: 20rpx;
+	font-size: 28rpx;
+	color:  #bbbbbb;
+}
+
+.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>

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

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

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

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

+ 83 - 0
uni_modules/mescroll-uni/components/mescroll-uni/components/mescroll-top.vue

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

+ 47 - 0
uni_modules/mescroll-uni/components/mescroll-uni/components/mescroll-up.css

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

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

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

+ 15 - 0
uni_modules/mescroll-uni/components/mescroll-uni/mescroll-i18n.js

@@ -0,0 +1,15 @@
+// 国际化工具类
+const mescrollI18n = {
+	// 默认语言
+	def: "zh",
+	// 获取当前语言类型
+	getType(){
+		return uni.getStorageSync("mescroll-i18n") || this.def
+	},
+	// 设置当前语言类型
+	setType(type){
+		uni.setStorageSync("mescroll-i18n", type)
+	}
+}
+
+export default mescrollI18n

+ 57 - 0
uni_modules/mescroll-uni/components/mescroll-uni/mescroll-mixins.js

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

+ 64 - 0
uni_modules/mescroll-uni/components/mescroll-uni/mescroll-uni-option.js

@@ -0,0 +1,64 @@
+// 全局配置
+// mescroll-body 和 mescroll-uni 通用
+const GlobalOption = {
+	down: {
+		// 其他down的配置参数也可以写,这里只展示了常用的配置:
+		offset: 80, // 在列表顶部,下拉大于80px,松手即可触发下拉刷新的回调
+		native: false // 是否使用系统自带的下拉刷新; 默认false; 仅在mescroll-body生效 (值为true时,还需在pages配置enablePullDownRefresh:true;详请参考mescroll-native的案例)
+	},
+	up: {
+		// 其他up的配置参数也可以写,这里只展示了常用的配置:
+		offset: 150, // 距底部多远时,触发upCallback,仅mescroll-uni生效 ( mescroll-body配置的是pages.json的 onReachBottomDistance )
+		toTop: {
+			// 回到顶部按钮,需配置src才显示
+			src: "https://www.mescroll.com/img/mescroll-totop.png", // 图片路径 (建议放入static目录, 如 /static/img/mescroll-totop.png )
+			offset: 1000, // 列表滚动多少距离才显示回到顶部按钮,默认1000px
+			right: 20, // 到右边的距离, 默认20 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx)
+			bottom: 120, // 到底部的距离, 默认120 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx)
+			width: 72 // 回到顶部图标的宽度, 默认72 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx)
+		},
+		empty: {
+			use: true, // 是否显示空布局
+			icon: "https://www.mescroll.com/img/mescroll-empty.png" // 图标路径 (建议放入static目录, 如 /static/img/mescroll-empty.png )
+		}
+	},
+	// 国际化配置
+	i18n: {
+		// 中文
+		zh: {
+			down: {
+				textInOffset: '下拉刷新', // 下拉的距离在offset范围内的提示文本
+				textOutOffset: '释放更新', // 下拉的距离大于offset范围的提示文本
+				textLoading: '加载中 ...', // 加载中的提示文本
+				textSuccess: '加载成功', // 加载成功的文本
+				textErr: '加载失败', // 加载失败的文本
+			},
+			up: {
+				textLoading: '加载中 ...', // 加载中的提示文本
+				textNoMore: '-- END --', // 没有更多数据的提示文本
+				empty: {
+					tip: '~ 空空如也 ~' // 空提示
+				}
+			}
+		},
+		// 英文
+		en: {
+			down: {
+				textInOffset: 'drop down refresh',
+				textOutOffset: 'release updates',
+				textLoading: 'loading ...',
+				textSuccess: 'loaded successfully',
+				textErr: 'loading failed'
+			},
+			up: {
+				textLoading: 'loading ...',
+				textNoMore: '-- END --',
+				empty: {
+					tip: '~ absolutely empty ~'
+				}
+			}
+		}
+	}
+}
+
+export default GlobalOption

+ 36 - 0
uni_modules/mescroll-uni/components/mescroll-uni/mescroll-uni.css

@@ -0,0 +1,36 @@
+.mescroll-uni-warp{
+	height: 100%;
+}
+
+.mescroll-uni-content{
+	height: 100%;
+}
+
+.mescroll-uni {
+	position: relative;
+	width: 100%;
+	height: 100%;
+	min-height: 200rpx;
+	overflow-y: auto;
+	box-sizing: border-box; /* 避免设置padding出现双滚动条的问题 */
+}
+
+/* 定位的方式固定高度 */
+.mescroll-uni-fixed{
+	z-index: 1;
+	position: fixed;
+	top: 0;
+	left: 0;
+	right: 0;
+	bottom: 0;
+	width: auto; /* 使right生效 */
+	height: auto; /* 使bottom生效 */
+}
+
+/* 适配 iPhoneX */
+@supports (bottom: constant(safe-area-inset-bottom)) or (bottom: env(safe-area-inset-bottom)) {
+	.mescroll-safearea {
+		padding-bottom: constant(safe-area-inset-bottom);
+		padding-bottom: env(safe-area-inset-bottom);
+	}
+}

Daži faili netika attēloti, jo izmaiņu fails ir pārāk liels