Kaynağa Gözat

贞达市政小程序代码上传

wang_xy 2 yıl önce
işleme
916aafb994
89 değiştirilmiş dosya ile 13363 ekleme ve 0 silme
  1. 30 0
      App.vue
  2. 152 0
      api/common.js
  3. 9 0
      api/index.js
  4. 8 0
      api/login.js
  5. 67 0
      app.json
  6. 96 0
      assets/style.less
  7. 6 0
      common/http.js
  8. 137 0
      common/lodash.min.js
  9. 150 0
      common/request.js
  10. 290 0
      common/utils.js
  11. 39 0
      components/back.vue
  12. 311 0
      components/ld-select/ld-select.vue
  13. 160 0
      components/ming-pop/ming-pop.vue
  14. 14 0
      index.html
  15. 36 0
      main.js
  16. 83 0
      manifest.json
  17. 219 0
      pages.json
  18. 77 0
      project.config.json
  19. 77 0
      uni.scss
  20. 21 0
      uview-ui/LICENSE
  21. 106 0
      uview-ui/README.md
  22. 216 0
      uview-ui/components/u-badge/u-badge.vue
  23. 596 0
      uview-ui/components/u-button/u-button.vue
  24. 123 0
      uview-ui/components/u-checkbox-group/u-checkbox-group.vue
  25. 284 0
      uview-ui/components/u-checkbox/u-checkbox.vue
  26. 193 0
      uview-ui/components/u-empty/u-empty.vue
  27. 431 0
      uview-ui/components/u-form-item/u-form-item.vue
  28. 134 0
      uview-ui/components/u-form/u-form.vue
  29. 336 0
      uview-ui/components/u-icon/u-icon.vue
  30. 387 0
      uview-ui/components/u-input/u-input.vue
  31. 147 0
      uview-ui/components/u-line-progress/u-line-progress.vue
  32. 123 0
      uview-ui/components/u-mask/u-mask.vue
  33. 7 0
      uview-ui/components/u-navbar/style.components.scss
  34. 339 0
      uview-ui/components/u-navbar/u-navbar.vue
  35. 676 0
      uview-ui/components/u-picker/u-picker.vue
  36. 456 0
      uview-ui/components/u-popup/u-popup.vue
  37. 342 0
      uview-ui/components/u-search/u-search.vue
  38. 417 0
      uview-ui/components/u-select/u-select.vue
  39. 330 0
      uview-ui/components/u-tabbar/u-tabbar.vue
  40. 220 0
      uview-ui/components/u-toast/u-toast.vue
  41. 654 0
      uview-ui/components/u-upload/u-upload.vue
  42. 910 0
      uview-ui/iconfont.css
  43. 141 0
      uview-ui/index.js
  44. 23 0
      uview-ui/index.scss
  45. 15 0
      uview-ui/libs/config/config.js
  46. 20 0
      uview-ui/libs/config/zIndex.js
  47. 155 0
      uview-ui/libs/css/color.scss
  48. 176 0
      uview-ui/libs/css/common.scss
  49. 7 0
      uview-ui/libs/css/style.components.scss
  50. 8 0
      uview-ui/libs/css/style.h5.scss
  51. 72 0
      uview-ui/libs/css/style.mp.scss
  52. 3 0
      uview-ui/libs/css/style.nvue.scss
  53. 175 0
      uview-ui/libs/css/style.vue.scss
  54. 18 0
      uview-ui/libs/function/$parent.js
  55. 8 0
      uview-ui/libs/function/addUnit.js
  56. 5 0
      uview-ui/libs/function/bem.js
  57. 37 0
      uview-ui/libs/function/color.js
  58. 134 0
      uview-ui/libs/function/colorGradient.js
  59. 29 0
      uview-ui/libs/function/debounce.js
  60. 23 0
      uview-ui/libs/function/deepClone.js
  61. 30 0
      uview-ui/libs/function/deepMerge.js
  62. 47 0
      uview-ui/libs/function/getParent.js
  63. 41 0
      uview-ui/libs/function/guid.js
  64. 385 0
      uview-ui/libs/function/md5.js
  65. 58 0
      uview-ui/libs/function/queryParams.js
  66. 10 0
      uview-ui/libs/function/random.js
  67. 7 0
      uview-ui/libs/function/randomArray.js
  68. 122 0
      uview-ui/libs/function/route.js
  69. 9 0
      uview-ui/libs/function/sys.js
  70. 232 0
      uview-ui/libs/function/test.js
  71. 32 0
      uview-ui/libs/function/throttle.js
  72. 51 0
      uview-ui/libs/function/timeFormat.js
  73. 47 0
      uview-ui/libs/function/timeFrom.js
  74. 9 0
      uview-ui/libs/function/toast.js
  75. 15 0
      uview-ui/libs/function/trim.js
  76. 35 0
      uview-ui/libs/function/type2icon.js
  77. 64 0
      uview-ui/libs/mixin/mixin.js
  78. 18 0
      uview-ui/libs/mixin/mpShare.js
  79. 169 0
      uview-ui/libs/request/index.js
  80. 19 0
      uview-ui/libs/store/index.js
  81. 1 0
      uview-ui/libs/util/area.js
  82. 1356 0
      uview-ui/libs/util/async-validator.js
  83. 1 0
      uview-ui/libs/util/city.js
  84. 51 0
      uview-ui/libs/util/emitter.js
  85. 1 0
      uview-ui/libs/util/province.js
  86. 39 0
      uview-ui/package.json
  87. 38 0
      uview-ui/theme.scss
  88. 9 0
      zdsz_WeChatApplet.iml
  89. 9 0
      zdsz_applet.iml

+ 30 - 0
App.vue

@@ -0,0 +1,30 @@
+<script>
+	export default {
+		onLaunch: function() {
+			wx.onAppRoute(function(res) {
+			  console.log('route',res)
+			  let pages = getCurrentPages()
+			  let view = pages[pages.length - 1]
+			  if(view) {
+			    wx.showShareMenu({
+			      withShareTicket:true,
+			      menus:['shareAppMessage','shareTimeline']
+			    })
+			  }
+			})
+			console.log('App Launch')
+		},
+		onShow: function() {
+			console.log('App Show')
+		},
+		onHide: function() {
+			console.log('App Hide')
+		}
+	}
+</script>
+
+<style lang="scss">
+	@import "uview-ui/index.scss";
+	@import "./assets/style.less";
+	/*每个页面公共css */
+</style>

+ 152 - 0
api/common.js

@@ -0,0 +1,152 @@
+import utils from '../common/request.js'
+export default {
+	//选择小区
+	getArea(data){
+	    return utils.requestEn(`/mobile/area/getArea`, data)
+	},
+	//选择单元
+	getUnit(data){
+	    return utils.requestEn(`/mobile/unit/getUnit`, data)
+	},
+	//选择楼宇
+	getBuilding(data){
+	    return utils.requestEn(`/mobile/building/getBuilding`, data)
+	},
+	//选择房屋(门牌)
+	getHouse(data){
+	    return utils.requestEn(`/mobile/house/getHouse`, data)
+	},
+	//公告列表
+	getNoticeList(data){
+	    return utils.requestEn(`/mobile/notice/list`, data)
+	},
+	//关于我们
+	getAboutUs(data){
+	    return utils.requestEn(`/mobile/aboutUs/getOne`, data)
+	},
+	//服务说明
+	getExplain(data){
+	    return utils.requestEn(`/mobile/explain/getOne`, data)
+	},
+	//上门类型
+	getDic(data){
+	    return utils.requestEn(`/mobile/order/getDic`, data)
+	},
+	//管子类别
+	getPipeType(data){
+	    return utils.requestEn(`/mobile/pipeType/getPipeType`, data)
+	},
+	//管子长度
+	getPipeLength(data){
+	    return utils.requestEn(`/mobile/pipeLength/getPipeLength`, data)
+	},
+	//历史施工
+	getListAll(data){
+	    return utils.requestFn(`/mobile/order/getListAll`, data)
+	},
+	//历史施工(查看详情页)
+	getOrderPhoto(data){
+	    return utils.requestEn(`/mobile/orderPhoto/getPhoto`, data)
+	},
+	//提交数据
+	submitOrder(data){
+	    return utils.requestFn(`/mobile/order`, data,'POST')
+	},
+	//工程列表
+	getEngineList(data){
+	    return utils.requestEn(`/mobile/otherEngine/getList`, data)
+	},
+	//查看非市政项目
+	getEngineDetail(data){
+	    return utils.requestEn(`/mobile/otherEngine/getById`, data)
+	},
+	//查看非市政项目页 确认上传照片
+	updateEnginePhoto(data){
+	    return utils.requestFn(`/mobile/otherEngine`, data,'PUT')
+	},
+	//门牌验证
+	// getOrderForStatus(data){
+	//     return utils.requestEn(`/mobile/order/getOrderForStatus`, data)
+	// },
+	//首页轮播图
+	getLunBo(data){
+	    return utils.requestEn(`/mobile/rotation/list`, data)
+	},
+	//获取项目类型
+	getProjectType(data){
+	    return utils.requestEn(`/mobile/order/getDic`, data)
+	},
+	//获取项目列表
+	getProjectList(data){
+	    return utils.requestEn(`/mobile/otherEngine/getList`, data)
+	},
+	//自闭阀类型
+	getProject(data){
+	    return utils.requestEn(`/mobile/valve/getValveType`, data)
+	},
+	//上传照片
+	getImage(data){
+	    return utils.requestFn(`/obs`, data,'POST')
+	},
+	//获取用户名
+	getUserName(data){
+	    return utils.requestFn(`/mobile/user/getName`, data)
+	},
+	//工程列表
+	getList(data){
+		return utils.requestEn(`/mobile/order/getList`, data)
+	},
+	//小区合格率
+	getExamineArea(data){
+		return utils.requestEn(`/mobile/area/getExamineArea`, data)
+	},
+
+	//列表
+	getOtherList(data){
+		return utils.requestEn(`/mobile/order/getOtherList`, data)
+	},
+	//职工合格率
+	getExamineWorker(data){
+		return utils.requestEn(`/mobile/area/getExamineWorker`, data)
+	},
+	//小区服务统计
+	getExamineServe(data){
+		return utils.requestEn(`/mobile/area/getExamineServe`, data)
+	},
+	//管材类别统计
+	getExaminePipeType(data){
+		return utils.requestEn(`/mobile/area/getExaminePipeType`, data)
+	},
+	//管材长度统计
+	getExaminePipeLength(data){
+		return utils.requestEn(`/mobile/area/getExaminePipeLength`, data)
+	},
+	//自闭阀类别统计
+	getExamineValveType(data){
+		return utils.requestEn(`/mobile/area/getExamineValveType`, data)
+	},
+	//小区汇总统计
+	getAreaSum(data){
+		return utils.requestEn(`/mobile/area/getAreaSum`, data)
+	},
+	//获取职工
+	getWorker(data){
+		return utils.requestEn(`/mobile/area/getWorker`, data)
+	},
+
+	getById(data){
+		return utils.requestEn(`/mobile/order/getById`, data)
+	},
+	
+	getOrderList(data){
+		return utils.requestEn(`/mobile/order/getList`, data)
+	},
+	//修改
+	getUpdateOrder(data){
+	    return utils.requestFn(`/mobile/order`, data, 'PUT')
+	},
+	//选择门牌(安检,工程管理页)
+	getAllHouse(data){
+	    return utils.requestEn(`/mobile/house/getAllHouse`, data)
+	},
+}

+ 9 - 0
api/index.js

@@ -0,0 +1,9 @@
+import common from "./common.js"
+import login from "./login.js"
+const service = {
+	// 公共方法
+	...common,
+	...login,
+}
+
+export default service

+ 8 - 0
api/login.js

@@ -0,0 +1,8 @@
+import utils from '../common/request.js'
+export default {
+	// 登录
+	login (data) {
+		return utils.requestEn('/mobile/user', data,'POST')
+	},
+	
+}

+ 67 - 0
app.json

@@ -0,0 +1,67 @@
+{
+  "pages": [
+    "pages/login/login",
+    "pages/noLogin/uploadProject",
+    "pages/noLogin/typeList",
+    "pages/historyConstruction/historyDetail",
+    "pages/historyConstruction/updateInfo",
+    "pages/noLogin/cityProject",
+    "pages/noLogin/data",
+    "pages/noLogin/list",
+    "pages/notice/noticeDetail",
+    "pages/notice/noticeList",
+    "pages/cityConstrution/cityConstrution",
+    "pages/historyConstruction/historyConstruction",
+    "pages/service/service",
+    "pages/aboutme/aboutme",
+    "pages/index/index",
+    "pages/chart/chart",
+	"pages/chart/chartAll",
+    "pages/chart/chartDetail",
+	"pages/chart/newchartDetail",
+    "pages/chart/chartList",
+    "pages/chart/details",
+    "pages/noLogin/repair",
+    "pages/noLogin/My",
+    "pages/noLogin/Myphoto"
+  ],
+  "subPackages": [],
+  "window": {
+    "navigationBarTextStyle": "black",
+    "navigationBarTitleText": "uni-app",
+    "navigationBarBackgroundColor": "#F8F8F8",
+    "backgroundColor": "#F8F8F8"
+  },
+  "tabBar": {
+    "list": [
+      {
+        "pagePath": "pages/index/index",
+        "text": "首页",
+        "iconPath": "static/icon/logo.png",
+        "selectedIconPath": "static/icon/logo.png"
+      },
+      {
+        "pagePath": "pages/historyConstruction/historyConstruction",
+        "text": "施工",
+        "iconPath": "static/icon/constructions.png",
+        "selectedIconPath": "static/icon/constructions.png"
+      },
+      // {
+      //   "pagePath": "pages/chart/chart",
+      //   "text": "统计图",
+      //   "iconPath": "static/icon/chart.png",
+      //   "selectedIconPath": "static/icon/chart.png"
+      // },
+	  {
+        "pagePath": "pages/chart/chartAll",
+        "text": "统计",
+        "iconPath": "static/icon/chart.png",
+        "selectedIconPath": "static/icon/chart.png"
+      }
+    ]
+  },
+  "usingComponents": {
+    "back": "/components/back",
+    "ming-pop": "/components/ming-pop/ming-pop"
+  }
+}

+ 96 - 0
assets/style.less

@@ -0,0 +1,96 @@
+.font-forty-eight{
+	font-size: 48rpx;
+	font-family: PingFangSC-Medium, PingFang SC;
+	font-weight: 500;
+}
+.thirsty{
+	font-size: 30rpx;
+	font-family: PingFangSC-Medium, PingFang SC;
+	font-weight: 500;
+}
+.font-senventy-two{
+	font-size: 72rpx;
+	font-family: PingFangSC-Medium, PingFang SC;
+	font-weight: 500;
+}
+.font-sixty-four{
+	font-size: 64rpx;
+	font-family: PingFangSC-Medium, PingFang SC;
+	font-weight: 500;
+}
+.font-sixty-seven{
+	font-size: 67rpx;
+	font-family: PingFangSC-Medium, PingFang SC;
+	font-weight: 500;
+}
+.font-fifty-six{
+	font-size: 56rpx;
+	font-family: PingFangSC-Medium, PingFang SC;
+	font-weight: 500;
+}
+.font-thirty-six{
+	font-size: 36rpx;
+	font-family: SourceHanSansCN-Regular, SourceHanSansCN;
+	font-weight: 500;
+}
+.font-thirty-two{
+	font-size: 32rpx;
+	font-family: SourceHanSansCN-Regular, SourceHanSansCN;
+	font-weight: 500;
+}
+.font-forty{
+	font-size: 40rpx;
+	font-family: SourceHanSansCN-Regular, SourceHanSansCN;
+	font-weight: 500;
+}
+.font-twenty-eight{
+	font-size: 28rpx;
+	font-family: SourceHanSansCN-Regular, SourceHanSansCN;
+	font-weight: 500;
+}
+.title-text{
+	font-size: 72rpx;
+	font-family: SourceHanSansCN-Medium, SourceHanSansCN;
+	font-weight: 500;
+	color: #232146;
+}
+.SourceHanSansCN{
+	font-family: SourceHanSansCN-Medium, SourceHanSansCN;
+}
+.white{
+	color: #FFFFFF;
+}
+.background-color1{
+	background-color: #3857F3;
+}
+.red{
+	color: #FF0700;
+}
+.blue{
+	color: #3857F3;
+}
+.black{
+	color: #333333;
+}
+.gray{
+	color: #727272;
+}
+.darkgray{
+	color: #979797;
+}
+
+.background-color2{
+	background-color: #43CEB1;
+}
+.flex{
+	display: flex;
+}
+.align-items{
+	display: flex;
+	align-items: center;
+}
+.justify-content{
+	display: flex;
+	align-items: center;
+	justify-content: space-between;
+}

+ 6 - 0
common/http.js

@@ -0,0 +1,6 @@
+// 配置信息
+export default {
+	// webUrl: 'http://127.0.0.1:8080',
+	webUrl: 'https://cczdsz.com/prod-api/',
+	// webUrl: 'http://192.168.0.129:8080',
+}

Dosya farkı çok büyük olduğundan ihmal edildi
+ 137 - 0
common/lodash.min.js


+ 150 - 0
common/request.js

@@ -0,0 +1,150 @@
+import http from './http.js'
+export default {
+	// 请求方法
+	requestEn(url, data = {}, methods) {
+		return new Promise((resolve, reject) => {
+			// console.log(http.webUrl + url +'---'+ uni.getStorageSync('token'))
+			uni.request({
+				url: http.webUrl + url,
+				method: methods ? methods : 'GET',
+				data: data,
+				success: res => {
+					if (res.data.code === 200) {
+						resolve(res.data.data === undefined ? null : res.data.data)
+					} else if (res.data.code === 403 ||res.data.code === 401 ) {
+						uni.showToast({
+							title: 'Token超时,请重新登录',
+							icon: 'none'
+						})
+						if (uni.getStorageSync('token')) {
+							uni.removeStorage({
+								key: 'token',
+								success: () => {
+									uni.navigateTo({
+										url: '/pages/login/login'
+									})
+								}
+							})
+						} else {
+							uni.navigateTo({
+								url: '/pages/login/login'
+							})
+						}
+					} else {
+						uni.showToast({
+							title: res.data.msg ? res.data.msg : '系统异常',
+							icon: 'none'
+						})
+					}
+				},
+				fail: () => {
+					uni.showToast({
+						title: '网络异常',
+						icon: 'none'
+					})
+				}
+			})
+		})
+	},
+	// 请求方法
+	requestFn(url, data = {}, methods) {
+		return new Promise((resolve, reject) => {
+			console.log(http.webUrl + url +'---'+ uni.getStorageSync('token'))
+			uni.request({
+				url: http.webUrl + url,
+				method: methods ? methods : 'GET',
+				data: data,
+				header: { 
+					"MAuthorization": `wxBearer ${uni.getStorageSync('token') ? uni.getStorageSync('token') : ''}`,
+					"userType":'MOdBILE'
+				},
+				success: res => {
+					console.log(res)
+					
+					if (res.data.code === 200) {
+						resolve(res.data.data === undefined ? null : res.data.data)
+					} else if (res.data.code === 403 ||res.data.code === 401 ) {
+						uni.showToast({
+							title: 'Token超时,请重新登录',
+							icon: 'none'
+						})
+						if (uni.getStorageSync('token')) {
+							uni.removeStorage({
+								key: 'token',
+								success: () => {
+									uni.navigateTo({
+										url: '/pages/login/login'
+									})
+								}
+							})
+						} else {
+							uni.navigateTo({
+								url: '/pages/login/login'
+							})
+						}
+					} else {
+						uni.showToast({
+							title: res.data.msg ? res.data.msg : '系统异常',
+							icon: 'none'
+						})
+					}
+				},
+				fail: () => {
+					uni.showToast({
+						title: '网络异常',
+						icon: 'none'
+					})
+				}
+			})
+		})
+	},
+ requestLo(url, data = {}, methods) {
+  return new Promise((resolve, reject) => {
+   console.log(http.webUrl + url)
+   uni.request({
+    url: http.webUrl + url,
+    method: methods ? methods : 'GET',
+    data: data,
+    header: {
+     "userType":'MOBILE'
+    },
+    success: res => {
+     console.log(res)
+     if (res.data.code === 200) {
+      resolve(res.data === undefined ? null : res.data)
+     } else if (res.data.code === 403) {
+      uni.showToast({
+       title: res.data.msg ? res.data.msg : 'Token超时,请重新登录',
+       icon: 'none'
+      })
+      if (uni.getStorageSync('token')) {
+       uni.removeStorage({
+        key: 'token',
+        success: () => {
+         uni.navigateTo({
+          url: '/pages/login/login'
+         })
+        }
+       })
+      } else {
+       uni.navigateTo({
+        url: '/pages/login/login'
+       })
+      }
+     } else {
+      uni.showToast({
+       title: res.data.msg ? res.data.msg : '系统异常',
+       icon: 'none'
+      })
+     }
+    },
+    fail: () => {
+     uni.showToast({
+      title: '网络异常',
+      icon: 'none'
+     })
+    }
+   })
+  })
+ }
+}

+ 290 - 0
common/utils.js

@@ -0,0 +1,290 @@
+export default {
+	// 页面提示信息函数
+	showPrompt(msg) {
+		uni.showToast({
+			title: msg,
+			icon: 'none',
+			mask: true
+		})
+	},
+	// APP名称
+	appName() {
+		return "贞达市政工程"
+	},
+	formatRichText(html){
+	  let newContent= html.replace(/<img[^>]*>/gi,function(match,capture){
+		match = match.replace(/style="[^"]+"/gi, '').replace(/style='[^']+'/gi, '');
+		match = match.replace(/width="[^"]+"/gi, '').replace(/width='[^']+'/gi, '');
+		match = match.replace(/height="[^"]+"/gi, '').replace(/height='[^']+'/gi, '');
+		return match;
+	  });
+	  newContent = newContent.replace(/style="[^"]+"/gi,function(match,capture){
+		match = match.replace(/width:[^;]+;/gi, 'max-width:100%;').replace(/width:[^;]+;/gi, 'max-width:100%;');
+		return match;
+	  });
+	  newContent = newContent.replace(/<br[^>]*\/>/gi, '');
+	  newContent = newContent.replace(/\<img/gi, '<img style="max-width:100%;height:auto;display:block;margin-top:0;margin-bottom:0;"');
+	  return newContent;
+	},
+	getHeight(e) {
+		console.log(e)
+	},
+	changeTwoDecimal_f(x) {
+		var f_x = parseFloat(x);
+		if (isNaN(f_x)) {
+			return 0;
+		}
+		var f_x = Math.round(x * 100) / 100;
+		var s_x = f_x.toString();
+		var pos_decimal = s_x.indexOf('.');
+		if (pos_decimal < 0) {
+			pos_decimal = s_x.length;
+			s_x += '.';
+		}
+		while (s_x.length <= pos_decimal + 2) {
+			s_x += '0';
+		}
+		return s_x;
+	},
+	// token过期
+	tokenOverdue() {
+		uni.setStorage({
+			key: "jumpUrl",
+			data: ''
+		})
+		uni.setStorage({
+			key: "jumpType",
+			data: ''
+		})
+		uni.navigateTo({
+			url: "/pages/login/login"
+		})
+	},
+	checkLoginTo(url, type) {
+		console.log(type)
+		let status = uni.getStorageSync('token')
+		console.log(status)
+
+		if (status) {
+			uni.navigateTo({
+				url: url
+			})
+		} else {
+			uni.showModal({
+				title: '',
+				content: '您未登录,是否登录?',
+				success: res => {
+					if (res.confirm) {
+						uni.setStorage({
+							key: "jumpUrl",
+							data: url
+						})
+						uni.setStorage({
+							key: "jumpType",
+							data: type
+						})
+						uni.navigateTo({
+							url: "/pages/login/login"
+						})
+					}
+				}
+			})
+		}
+	},
+	// 拨打电话
+	toCall(tel) {
+		if (!tel) {
+			this.showPrompt('暂无商家电话')
+			return
+		}
+		uni.makePhoneCall({
+			phoneNumber: tel
+		});
+	},
+	toBack() {
+		uni.navigateBack({
+			delta: 1
+		})
+	},
+	// 图片预览
+	previewImage(url) {
+		let urlList = []
+		if (typeof url == 'string') {
+			urlList.push(url)
+		} else {
+			urlList = url
+		}
+		uni.previewImage({
+			urls: urlList
+		})
+	},
+	// 登录成功后的跳转
+	loginSuccess() {
+		let type = uni.getStorageSync('jumpType')
+		// console.log(type)
+		if (type) {
+			let url = uni.getStorageSync('jumpUrl')
+			// console.log(url)
+			if (!url) {
+				uni.navigateBack({
+					delta: 1
+				});
+				return
+			}
+			if (type == 'page') {
+				uni.redirectTo({
+					url: url
+				})
+			} else {
+				uni.switchTab({
+					url: url,
+					fail: error => {
+						console.log(error)
+					}
+				})
+			}
+		} else {
+			// uni.switchTab({
+			// 	url: "/pages/index/index"
+			// })
+			uni.navigateBack({
+				delta: 1
+			});
+		}
+	},
+	parseNumber(num) {
+		return num < 10 ? "0" + num : num;
+	},
+	// 时间格式化基类
+	dateFormat(date, formatStr) {
+		let dateObj = {},
+			rStr = /\{([^}]+)\}/,
+			mons = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12'];
+		dateObj["Y"] = date.getFullYear();
+		dateObj["M"] = date.getMonth() + 1;
+		dateObj["MM"] = this.parseNumber(dateObj["M"]);
+		dateObj["Mon"] = mons[dateObj['M'] - 1];
+		dateObj["D"] = date.getDate();
+		dateObj["DD"] = this.parseNumber(dateObj["D"]);
+		dateObj["h"] = date.getHours();
+		dateObj["hh"] = this.parseNumber(dateObj["h"]);
+		// dateObj["t"] = dateObj["h"] > 12 ? dateObj["h"] - 12 : dateObj["h"];
+		dateObj["t"] = dateObj["h"]
+		dateObj["tt"] = this.parseNumber(dateObj["t"]);
+		// dateObj["A"] = dateObj["h"] > 12 ? '下午' : '上午';
+		dateObj["i"] = date.getMinutes();
+		dateObj["ii"] = this.parseNumber(dateObj["i"]);
+		dateObj["s"] = date.getSeconds();
+		dateObj["ss"] = this.parseNumber(dateObj["s"]);
+		while (rStr.test(formatStr)) {
+			formatStr = formatStr.replace(rStr, dateObj[RegExp.$1]);
+		}
+		return formatStr;
+	},
+	// 时间格式 年月日时分秒
+	gettime(shorttime, type) {
+		shorttime = shorttime.toString().length < 13 ? shorttime * 1000 : shorttime;
+		let now = (new Date()).getTime();
+		let cha = (now - parseInt(shorttime)) / 1000
+		// type: 格式化类型  1为年月日时分秒  2为年月日时分  3为年月日
+		if (type == 1) {
+			return this.dateFormat(new Date(shorttime), "{Y}-{MM}-{DD} {t}:{ii}:{ss}");
+		} else if (type == 2) {
+			return this.dateFormat(new Date(shorttime), "{Y}-{MM}-{DD} {t}:{ii}");
+		} else if (type == 3) {
+			return this.dateFormat(new Date(shorttime), "{Y}-{MM}-{DD}");
+		}
+
+	},
+	// 时间格式 年月日
+	getDate(shorttime) {
+		shorttime = shorttime.toString().length < 13 ? shorttime * 1000 : shorttime
+		let now = (new Date()).getTime()
+		let cha = (now - parseInt(shorttime)) / 1000
+		return this.dateFormat(new Date(shorttime), "{Y}-{MM}-{DD}");
+	},
+	// 时间格式 月日
+	getDateMd(shorttime) {
+		shorttime = shorttime.toString().length < 13 ? shorttime * 1000 : shorttime
+		let now = (new Date()).getTime()
+		let cha = (now - parseInt(shorttime)) / 1000
+		return this.dateFormat(new Date(shorttime), "{MM}-{DD}");
+	},
+	getSevenDate(num) {
+		let date1 = new Date();
+		//今天时间
+		let time1 = ((date1.getMonth() + 1) < 10 ? '0' + (date1.getMonth() + 1).toString() : (date1.getMonth() + 1)) +
+			"-" +
+			(date1.getDate() < 10 ? '0' + date1.getDate().toString() : date1.getDate())
+		let date2 = new Date(date1);
+		date2.setDate(date1.getDate() + num);
+		//num是正数表示之后的时间,num负数表示之前的时间,0表示今天
+		let time2 = (date2.getMonth() + 1) + "-" + date2.getDate();
+		return this.getdiffdate(time1, time2, num)
+	},
+	//获取两日期之间日期列表函数
+	getdiffdate(stime, etime, num) {
+		//初始化日期列表,数组
+		let diffdate = new Array()
+		let i = 0
+		//开始日期小于等于结束日期,并循环
+		while (i <= num - 1) {
+			diffdate.push({
+				dateStr: stime,
+				weekStr: this.getWeek(new Date().getFullYear().toString() + "-" + stime)
+			})
+			//获取开始日期时间戳
+			let stime_ts = new Date(stime).getTime()
+			// console.log('当前日期:'+stime   +'当前时间戳:'+stime_ts)	        
+			//增加一天时间戳后的日期
+			let next_date = stime_ts + (24 * 60 * 60 * 1000)
+			//拼接年月日,这里的月份会返回(0-11),所以要+1
+			let next_dates_y = new Date(next_date).getFullYear() + '-'
+			let next_dates_m = (new Date(next_date).getMonth() + 1 < 10) ? '0' + (new Date(next_date).getMonth() + 1) +
+				'-' : (
+					new Date(next_date).getMonth() + 1) + '-'
+			let next_dates_d = (new Date(next_date).getDate() < 10) ? '0' + new Date(next_date).getDate() : new Date(
+				next_date).getDate()
+			stime = next_dates_m + next_dates_d
+			// 增加数组key
+			i++
+		}
+		return diffdate
+	},
+	// 根据日期转换星期
+	getWeek(dateString) {
+		let dateArray = dateString.split("-");
+		let date = new Date(dateArray[0], parseInt(dateArray[1] - 1), dateArray[2]);
+		return "周" + "日一二三四五六".charAt(date.getDay());
+	},
+	// 获取当前位置
+	getNowAddress() {
+		console.log(222)
+		return new Promise((resolve, reject) => {
+			let mapkey = ''
+			// #ifdef MP-WEIXIN
+			mapkey = 'c827c04d1d74d8589ec826fce02a135a'
+			// #endif
+			// #ifdef APP-PLUS
+			mapkey = '7923ab86c887ab9c77e9e28828567622	'
+			// #endif
+			console.log(mapkey)
+			let amapPlugin = new amap.AMapWX({
+				key: mapkey
+			})
+			amapPlugin.getRegeo({
+				success: res => {
+					resolve(res[0])
+				},
+				fail: error => {
+					console.log(error)
+					uni.showToast({
+						mask: true,
+						icon: "none",
+						title: error
+					})
+				}
+			})
+		})
+	}
+}

+ 39 - 0
components/back.vue

@@ -0,0 +1,39 @@
+<template>
+	<view>
+		<view class="back-icon" @click="back()">
+			<u-icon name="arrow-left" color="#ffffff" size="25px" class="arrow"></u-icon>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+
+			}
+		},
+		onLoad() {
+
+		},
+		methods: {
+			back() {
+				uni.navigateBack()
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	.back-icon {
+		width: 80rpx;
+		height: 80rpx;
+		border-radius: 50%;
+		background-color: #333333;
+		margin-left: 20rpx;
+		.arrow{
+			text-align: center; 
+			padding: 15rpx 12rpx;
+		}
+	}
+</style>

Dosya farkı çok büyük olduğundan ihmal edildi
+ 311 - 0
components/ld-select/ld-select.vue


+ 160 - 0
components/ming-pop/ming-pop.vue

@@ -0,0 +1,160 @@
+<template>
+	<view :class="direction==='center'?'centers':''" v-if="direction==='center'?open:true">
+		<view class="product-window"
+			:style="{width:width+'%',height:height=='fit-content'?height:(open?height:'fit-content')}"
+			:class="(open ? 'on' : '')+' '+direction" @touchmove.stop.prevent="">
+			<!-- #ifndef MP -->
+			<!-- #endif -->
+			<slot></slot>
+		</view>
+		<view class="mask" v-if="is_mask" @touchmove.prevent :hidden="!open" @click="close(1)"></view>
+	</view>
+</template>
+
+<script>
+	export default {
+		props: {
+			direction: {
+				type: String,
+				default: "below"
+			},
+			width: {
+				type: Number,
+				default: 100
+			},
+			height: {
+				type: String,
+				default: "fit-content"
+			},
+			is_close: {
+				type: Boolean,
+				default: true
+			},
+			is_mask: {
+				type: Boolean,
+				default: true
+			},
+			maskFun: {
+				type: Boolean,
+				default: true
+			}
+		},
+		data() {
+			return {
+				open: false
+			};
+		},
+		methods: {
+			show() {
+				this.open = true;
+				this.$emit("watchOpen")
+			},
+			close(e) {
+				if (e == 1 && !this.maskFun) return;
+				this.open = false;
+				this.$emit("watchClose")
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	view {
+		box-sizing: border-box;
+	}
+
+	.centers {
+		width: 100vw;
+		display: flex;
+		justify-content: center;
+		align-items: center;
+		// height: 100%;
+		position: fixed;
+		top: 0;
+		left: 0;
+		right: 0;
+		bottom: 0;
+	}
+
+	.product-window {
+		position: fixed;
+		background-color: #fff;
+		z-index: 77;
+		border-radius: 32rpx 32rpx 0rpx 0rpx;
+		padding: 50rpx 30rpx;
+		transition: all .3s cubic-bezier(.25, .5, .5, .9);
+		-webkit-transition: all .3s cubic-bezier(.25, .5, .5, .9);
+	}
+
+	.below {
+		left: 0;
+		bottom: 0;
+		transform: translate3d(0, 100%, 0);
+		-webkit-transform: translate3d(0, 100%, 0);
+	}
+
+	.up {
+		top: 0;
+		left: 0;
+		transform: translate3d(0, -100%, 0);
+		-webkit-transform: translate3d(0, -100%, 0);
+	}
+
+	.right {
+		right: 0;
+		top: 0;
+		height: 100%;
+		transform: translate3d(100vw, 0, 0);
+		-webkit-transform: translate3d(100vw, 0, 0);
+	}
+
+	.left {
+		left: 0;
+		top: 0;
+		height: 100%;
+		transform: translate3d(-100vw, 0, 0);
+		-webkit-transform: translate3d(-100vw, 0, 0);
+	}
+
+	.center {
+		position: static;
+		-webkit-position: static;
+		transform: translate3d(-100vw, -100%, 0);
+		-webkit-transform: translate3d(-100vw, -100%, 0);
+	}
+
+	.product-window.on {
+		transform: translate3d(0, 0, 0);
+		-webkit-transform: translate3d(0, 0, 0);
+	}
+
+	.mask {
+		position: fixed;
+		top: 0;
+		left: 0;
+		right: 0;
+		bottom: 0;
+		background-color: #000;
+		opacity: .5;
+		z-index: 5;
+	}
+
+
+	.product-window .iconfont {
+		position: fixed;
+		right: 30rpx;
+		top: 20rpx;
+		font-size: 35rpx;
+		color: #8a8a8a;
+		width: 50rpx;
+		height: 50rpx;
+	}
+
+	//兼容h5顶部导航空位
+	// #ifndef MP
+	.product-window .iconfont-h5 {
+		top: 100rpx;
+	}
+
+	// #endif
+</style>

+ 14 - 0
index.html

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

+ 36 - 0
main.js

@@ -0,0 +1,36 @@
+import Vue from 'vue'
+import App from './App'
+import service from './api/index.js'
+import utils from './common/utils.js'
+import http from './common/http.js'
+import lodash from './common/lodash.min.js'
+// 引入全局uView
+import uView from 'uview-ui';
+Vue.use(uView);
+Vue.config.productionTip = false
+//返回上一页按钮
+import back from '@/components/back.vue'
+Vue.component('back',back)
+
+//弹窗
+import mingPop from '@/components/ming-pop/ming-pop.vue'
+Vue.component("ming-pop",mingPop)
+
+// 挂载全局图片地址
+Vue.prototype.$HTTP = http
+
+// 挂载全局函数 
+Vue.prototype.$UTILS = utils
+
+App.mpType = 'app'
+ 
+const app = new Vue({
+    ...App
+})
+app.$mount()
+
+// // #ifndef VUE3
+// // #endif
+
+// // #ifdef VUE3
+// // #endif

+ 83 - 0
manifest.json

@@ -0,0 +1,83 @@
+{
+    "name" : "贞达市政工程",
+    "appid" : "__UNI__5FB7046",
+    "description" : "",
+    "versionName" : "1.0.0",
+    "versionCode" : "100",
+    "transformPx" : false,
+    /* 5+App特有相关 */
+    "app-plus" : {
+        "usingComponents" : true,
+        "nvueStyleCompiler" : "uni-app",
+        "compilerVersion" : 3,
+        "splashscreen" : {
+            "alwaysShowBeforeRender" : true,
+            "waiting" : true,
+            "autoclose" : true,
+            "delay" : 0
+        },
+        /* 模块配置 */
+        "modules" : {},
+        /* 应用发布信息 */
+        "distribute" : {
+            /* android打包配置 */
+            "android" : {
+                "permissions" : [
+                    "<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
+                    "<uses-permission android:name=\"android.permission.VIBRATE\"/>",
+                    "<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
+                    "<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
+                    "<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
+                    "<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.CAMERA\"/>",
+                    "<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
+                    "<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
+                    "<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
+                    "<uses-feature android:name=\"android.hardware.camera\"/>",
+                    "<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
+                ]
+            },
+            /* ios打包配置 */
+            "ios" : {},
+            /* SDK配置 */
+            "sdkConfigs" : {}
+        }
+    },
+    /* 快应用特有相关 */
+    "quickapp" : {},
+    /* 小程序特有相关 */
+    "mp-weixin" : {
+        "appid" : "wx79abaa2be02ef711",
+        "setting" : {
+            "urlCheck" : true,
+            "minified" : true,
+            "postcss" : true,
+            "es6" : true
+        },
+        "usingComponents" : true,
+        "permission" : {
+            "scope.userLocation" : {
+                "desc" : "我需要你的定位权限,请同意"
+            }
+        },
+        "uniStatistics" : {
+            "enable" : false
+        }
+    },
+    "mp-alipay" : {
+        "usingComponents" : true
+    },
+    "mp-baidu" : {
+        "usingComponents" : true
+    },
+    "mp-toutiao" : {
+        "usingComponents" : true
+    },
+    "uniStatistics" : {
+        "enable" : false
+    },
+    "vueVersion" : "2"
+}

+ 219 - 0
pages.json

@@ -0,0 +1,219 @@
+{
+	"easycom": {
+		"^u-(.*)": "@/uview-ui/components/u-$1/u-$1.vue"
+	},
+	"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
+		{
+			//登录
+			"path": "pages/login/login",
+			"style": {
+				"navigationStyle": "custom"
+			}
+		},
+		{
+			//上传市政项目
+			"path": "pages/noLogin/uploadProject",
+			"style": {
+				"navigationStyle": "custom"
+			}
+		},
+		
+		{
+			//项目类型
+			"path": "pages/noLogin/typeList",
+			"style": {
+				"navigationStyle": "custom"
+			}
+		},
+		{
+			//历史施工(详情页)
+			"path": "pages/historyConstruction/historyDetail",
+			"style": {
+				"navigationStyle": "custom"
+			}
+		},
+		{
+			//修改
+			"path": "pages/historyConstruction/updateInfo",
+			"style": {
+				"navigationBarTitleText":"修改"
+			}
+		},
+		{
+			//市政项目
+			"path": "pages/noLogin/cityProject",
+			"style": {
+				"navigationStyle": "custom"
+			}
+		},
+		{
+			//安检
+			"path": "pages/noLogin/data",
+			"style": {
+				"navigationStyle": "custom"
+			}
+		},
+		{
+			//安检
+			"path": "pages/noLogin/list",
+			"style": {
+				"navigationBarTitleText":"列表",
+				"enablePullDownRefresh":true//开启下拉刷新
+			}
+		},
+		
+		{
+			//公告详情
+			"path": "pages/notice/noticeDetail",
+			"style": {
+				"navigationStyle": "custom"
+			}
+		},
+		{
+			//公告列表
+			"path": "pages/notice/noticeList",
+			"style": {
+				"navigationStyle": "custom"
+			}
+		},
+		{
+			//阀管施工
+			"path": "pages/cityConstrution/cityConstrution",
+			"style": {
+				"navigationStyle": "custom"
+			}
+		},
+		{
+			//历史施工
+			"path": "pages/historyConstruction/historyConstruction",
+			"style": {
+				"navigationStyle": "custom"
+			}
+		},
+		{
+			//服务说明
+			"path": "pages/service/service",
+			"style": {
+				"navigationStyle": "custom"
+			}
+		},
+		{
+			//关于我们
+			"path": "pages/aboutme/aboutme",
+			"style": {
+				"navigationStyle": "custom"
+			}
+		},
+	
+		{
+			//首页
+			"path": "pages/index/index",
+			"style": {
+				"navigationStyle": "custom"
+			}
+		},
+		{
+			//统计图页面
+			"path":"pages/chart/chart",
+			"style": {
+				"navigationStyle": "custom"
+			}
+		},
+		{
+			//汇总统计图
+			"path":"pages/chart/chartAll",
+			"style": {
+				"navigationStyle": "custom"
+			}
+		},
+		{
+			//统计图
+			"path":"pages/chart/chartDetail",
+			"style": {
+				"navigationStyle": "custom"
+			}
+		},
+		{
+			//统计图
+			"path":"pages/chart/newchartDetail",
+			"style": {
+				"navigationStyle": "custom"
+			}
+		},
+		{
+			//工程管理
+			"path":"pages/chart/chartList",
+			"style": {
+				"navigationBarTitleText":"工程管理"
+			}
+		},
+		{
+			//工程管理
+			"path":"pages/chart/details",
+			"style": {
+				"navigationBarTitleText":"详情"
+			}
+		},
+		{	//安检
+			"path":"pages/noLogin/repair",
+			"style": {
+				"navigationStyle": "custom"
+			}
+		},
+		{	//安检
+			"path":"pages/noLogin/My",
+			"style": {
+				"navigationStyle": "custom"
+			}
+		},
+		{	//安检(查看照片)
+			"path":"pages/noLogin/Myphoto",
+			"style": {
+				"navigationStyle": "custom"
+			}
+		}
+	],
+	"globalStyle": {
+		"navigationBarTextStyle": "black",
+		"navigationBarTitleText": "uni-app",
+		"navigationBarBackgroundColor": "#F8F8F8",
+		"backgroundColor": "#F8F8F8"
+	},
+	"tabBar": {
+		"list": [{
+				"pagePath": "pages/index/index",
+				"text": "首页",
+				"iconPath": "static/icon/logo.png",
+				"selectedIconPath": "static/icon/logo.png"
+			},
+			{
+				"pagePath": "pages/historyConstruction/historyConstruction",
+				"text": "施工",
+				"iconPath": "static/icon/constructions.png",
+				"selectedIconPath": "static/icon/constructions.png"
+			},
+			// {
+			// 	"pagePath": "pages/chart/chart",
+			// 	"text": "统计图",
+			// 	"iconPath": "static/icon/chart.png",
+			// 	"selectedIconPath": "static/icon/chart.png"
+			// },
+			{
+				"pagePath": "pages/chart/chartAll",
+				"text": "统计",
+				"iconPath": "static/icon/chart.png",
+				"selectedIconPath": "static/icon/chart.png"
+			}
+		]
+	},
+	"condition" : { //模式配置,仅开发期间生效
+		"current": 0, //当前激活的模式(list 的索引项)
+		"list": [
+			{
+				"name": "", //模式名称
+				"path": "", //启动页面,必选
+				"query": "" //启动参数,在页面的onLoad函数里面得到
+			}
+		]
+	}
+}

+ 77 - 0
project.config.json

@@ -0,0 +1,77 @@
+{
+  "description": "项目配置文件",
+  "packOptions": {
+    "ignore": []
+  },
+  "setting": {
+    "urlCheck": true,
+    "es6": true,
+    "enhance": true,
+    "postcss": true,
+    "preloadBackgroundData": false,
+    "minified": true,
+    "newFeature": false,
+    "coverView": true,
+    "nodeModules": false,
+    "autoAudits": false,
+    "showShadowRootInWxmlPanel": true,
+    "scopeDataCheck": false,
+    "uglifyFileName": false,
+    "checkInvalidKey": true,
+    "checkSiteMap": true,
+    "uploadWithSourceMap": true,
+    "compileHotReLoad": false,
+    "lazyloadPlaceholderEnable": false,
+    "useMultiFrameRuntime": false,
+    "useApiHook": false,
+    "useApiHostProcess": false,
+    "babelSetting": {
+      "ignore": [],
+      "disablePlugins": [],
+      "outputPath": ""
+    },
+    "enableEngineNative": false,
+    "useIsolateContext": true,
+    "userConfirmedBundleSwitch": false,
+    "packNpmManually": false,
+    "packNpmRelationList": [],
+    "minifyWXSS": true,
+    "disableUseStrict": false,
+    "minifyWXML": true,
+    "showES6CompileOption": false,
+    "useCompilerPlugins": false
+  },
+  "compileType": "miniprogram",
+  "libVersion": "2.21.0",
+  "appid": "wx9efdc4932f07063d",
+  "projectname": "applet",
+  "debugOptions": {
+    "hidedInDevtools": []
+  },
+  "scripts": {},
+  "staticServerOptions": {
+    "baseURL": "",
+    "servePath": ""
+  },
+  "isGameTourist": false,
+  "condition": {
+    "search": {
+      "list": []
+    },
+    "conversation": {
+      "list": []
+    },
+    "game": {
+      "list": []
+    },
+    "plugin": {
+      "list": []
+    },
+    "gamePlugin": {
+      "list": []
+    },
+    "miniprogram": {
+      "list": []
+    }
+  }
+}

+ 77 - 0
uni.scss

@@ -0,0 +1,77 @@
+/**
+ * 这里是uni-app内置的常用样式变量
+ *
+ * uni-app 官方扩展插件及插件市场(https://ext.dcloud.net.cn)上很多三方插件均使用了这些样式变量
+ * 如果你是插件开发者,建议你使用scss预处理,并在插件代码中直接使用这些变量(无需 import 这个文件),方便用户通过搭积木的方式开发整体风格一致的App
+ *
+ */
+
+/**
+ * 如果你是App开发者(插件使用者),你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能
+ *
+ * 如果你的项目同样使用了scss预处理,你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件
+ */
+
+/* 颜色变量 */
+
+/* 行为相关颜色 */
+$uni-color-primary: #007aff;
+$uni-color-success: #4cd964;
+$uni-color-warning: #f0ad4e;
+$uni-color-error: #dd524d;
+
+/* 文字基本颜色 */
+$uni-text-color:#333;//基本色
+$uni-text-color-inverse:#fff;//反色
+$uni-text-color-grey:#999;//辅助灰色,如加载更多的提示信息
+$uni-text-color-placeholder: #808080;
+$uni-text-color-disable:#c0c0c0;
+
+/* 背景颜色 */
+$uni-bg-color:#ffffff;
+$uni-bg-color-grey:#f8f8f8;
+$uni-bg-color-hover:#f1f1f1;//点击状态颜色
+$uni-bg-color-mask:rgba(0, 0, 0, 0.4);//遮罩颜色
+
+/* 边框颜色 */
+$uni-border-color:#c8c7cc;
+
+/* 尺寸变量 */
+
+/* 文字尺寸 */
+$uni-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;
+@import   'uview-ui/theme.scss'

+ 21 - 0
uview-ui/LICENSE

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

+ 106 - 0
uview-ui/README.md

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

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

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

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

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

+ 123 - 0
uview-ui/components/u-checkbox-group/u-checkbox-group.vue

@@ -0,0 +1,123 @@
+<template>
+	<view class="u-checkbox-group u-clearfix">
+		<slot></slot>
+	</view>
+</template>
+
+<script>
+	import Emitter from '../../libs/util/emitter.js';
+	/**
+	 * checkboxGroup 开关选择器父组件Group
+	 * @description 复选框组件一般用于需要多个选择的场景,该组件功能完整,使用方便
+	 * @tutorial https://www.uviewui.com/components/checkbox.html
+	 * @property {String Number} max 最多能选中多少个checkbox(默认999)
+	 * @property {String Number} size 组件整体的大小,单位rpx(默认40)
+	 * @property {Boolean} disabled 是否禁用所有checkbox(默认false)
+	 * @property {String Number} icon-size 图标大小,单位rpx(默认20)
+	 * @property {Boolean} label-disabled 是否禁止点击文本操作checkbox(默认false)
+	 * @property {String} width 宽度,需带单位
+	 * @property {String} width 宽度,需带单位
+	 * @property {String} shape 外观形状,shape-方形,circle-圆形(默认circle)
+	 * @property {Boolean} wrap 是否每个checkbox都换行(默认false)
+	 * @property {String} active-color 选中时的颜色,应用到所有子Checkbox组件(默认#2979ff)
+	 * @event {Function} change 任一个checkbox状态发生变化时触发,回调为一个对象
+	 * @example <u-checkbox-group></u-checkbox-group>
+	 */
+	export default {
+		name: 'u-checkbox-group',
+		mixins: [Emitter],
+		props: {
+			// 最多能选中多少个checkbox
+			max: {
+				type: [Number, String],
+				default: 999
+			},
+			// 所有选中项的 name
+			// value: {
+			// 	default: Array,
+			// 	default() {
+			// 		return []
+			// 	}
+			// },
+			// 是否禁用所有复选框
+			disabled: {
+				type: Boolean,
+				default: false
+			},
+			// 在表单内提交时的标识符
+			name: {
+				type: [Boolean, String],
+				default: ''
+			},
+			// 是否禁止点击提示语选中复选框
+			labelDisabled: {
+				type: Boolean,
+				default: false
+			},
+			// 形状,square为方形,circle为原型
+			shape: {
+				type: String,
+				default: 'square'
+			},
+			// 选中状态下的颜色
+			activeColor: {
+				type: String,
+				default: '#2979ff'
+			},
+			// 组件的整体大小
+			size: {
+				type: [String, Number],
+				default: 34
+			},
+			// 每个checkbox占u-checkbox-group的宽度
+			width: {
+				type: String,
+				default: 'auto'
+			},
+			// 是否每个checkbox都换行
+			wrap: { 
+				type: Boolean,
+				default: false
+			},
+			// 图标的大小,单位rpx
+			iconSize: {
+				type: [String, Number],
+				default: 20
+			},
+		},
+		data() {
+			return {
+			}
+		},
+		created() {
+			// 如果将children定义在data中,在微信小程序会造成循环引用而报错
+			this.children = [];
+		},
+		methods: {
+			emitEvent() {
+				let values = [];
+				this.children.map(val => {
+					if(val.value) values.push(val.name);
+				})
+				this.$emit('change', values);
+				// 发出事件,用于在表单组件中嵌入checkbox的情况,进行验证
+				// 由于头条小程序执行迟钝,故需要用几十毫秒的延时
+				setTimeout(() => {
+					// 将当前的值发送到 u-form-item 进行校验
+					this.dispatch('u-form-item', 'on-form-change', values);
+				}, 60)
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	@import "../../libs/css/style.components.scss";
+
+	.u-checkbox-group {
+		/* #ifndef MP || APP-NVUE */
+		display: inline-flex;
+		flex-wrap: wrap;
+		/* #endif */
+	}
+</style>

+ 284 - 0
uview-ui/components/u-checkbox/u-checkbox.vue

@@ -0,0 +1,284 @@
+<template>
+	<view class="u-checkbox" :style="[checkboxStyle]">
+		<view class="u-checkbox__icon-wrap" @tap="toggle" :class="[iconClass]" :style="[iconStyle]">
+			<u-icon class="u-checkbox__icon-wrap__icon" name="checkbox-mark" :size="checkboxIconSize" :color="iconColor"/>
+		</view>
+		<view class="u-checkbox__label" @tap="onClickLabel" :style="{
+			fontSize: $u.addUnit(labelSize)
+		}">
+			<slot />
+		</view>
+	</view>
+</template>
+
+<script>
+	/**
+	 * checkbox 复选框
+	 * @description 该组件需要搭配checkboxGroup组件使用,以便用户进行操作时,获得当前复选框组的选中情况。
+	 * @tutorial https://www.uviewui.com/components/checkbox.html
+	 * @property {String Number} icon-size 图标大小,单位rpx(默认20)
+	 * @property {String Number} label-size label字体大小,单位rpx(默认28)
+	 * @property {String Number} name checkbox组件的标示符
+	 * @property {String} shape 形状,见官网说明(默认circle)
+	 * @property {Boolean} disabled 是否禁用
+	 * @property {Boolean} label-disabled 是否禁止点击文本操作checkbox
+	 * @property {String} active-color 选中时的颜色,如设置CheckboxGroup的active-color将失效
+	 * @event {Function} change 某个checkbox状态发生变化时触发,回调为一个对象
+	 * @example <u-checkbox v-model="checked" :disabled="false">天涯</u-checkbox>
+	 */
+	export default {
+		name: "u-checkbox",
+		props: {
+			// checkbox的名称
+			name: {
+				type: [String, Number],
+				default: ''
+			},
+			// 形状,square为方形,circle为原型
+			shape: {
+				type: String,
+				default: ''
+			},
+			// 是否为选中状态
+			value: {
+				type: Boolean,
+				default: false
+			},
+			// 是否禁用
+			disabled: {
+				type: [String, Boolean],
+				default: ''
+			},
+			// 是否禁止点击提示语选中复选框
+			labelDisabled: {
+				type: [String, Boolean],
+				default: ''
+			},
+			// 选中状态下的颜色,如设置此值,将会覆盖checkboxGroup的activeColor值
+			activeColor: {
+				type: String,
+				default: ''
+			},
+			// 图标的大小,单位rpx
+			iconSize: {
+				type: [String, Number],
+				default: ''
+			},
+			// label的字体大小,rpx单位
+			labelSize: {
+				type: [String, Number],
+				default: ''
+			},
+			// 组件的整体大小
+			size: {
+				type: [String, Number],
+				default: ''
+			},
+		},
+		data() {
+			return {
+				parentDisabled: false,
+				newParams: {},
+			};
+		},
+		created() {
+			// 支付宝小程序不支持provide/inject,所以使用这个方法获取整个父组件,在created定义,避免循环应用
+			this.parent = this.$u.$parent.call(this, 'u-checkbox-group');
+			// 如果存在u-checkbox-group,将本组件的this塞进父组件的children中
+			this.parent && this.parent.children.push(this);
+		},
+		computed: {
+			// 是否禁用,如果父组件u-checkbox-group禁用的话,将会忽略子组件的配置
+			isDisabled() {
+				return this.disabled !== '' ? this.disabled : this.parent ? this.parent.disabled : false;
+			},
+			// 是否禁用label点击
+			isLabelDisabled() {
+				return this.labelDisabled !== '' ? this.labelDisabled : this.parent ? this.parent.labelDisabled : false;
+			},
+			// 组件尺寸,对应size的值,默认值为34rpx
+			checkboxSize() {
+				return this.size ? this.size : (this.parent ? this.parent.size : 34);
+			},
+			// 组件的勾选图标的尺寸,默认20
+			checkboxIconSize() {
+				return this.iconSize ? this.iconSize : (this.parent ? this.parent.iconSize : 20);
+			},
+			// 组件选中激活时的颜色
+			elActiveColor() {
+				return this.activeColor ? this.activeColor : (this.parent ? this.parent.activeColor : 'primary');
+			},
+			// 组件的形状
+			elShape() {
+				return this.shape ? this.shape : (this.parent ? this.parent.shape : 'square');
+			},
+			iconStyle() {
+				let style = {};
+				// 既要判断是否手动禁用,还要判断用户v-model绑定的值,如果绑定为false,那么也无法选中
+				if (this.elActiveColor && this.value && !this.isDisabled) {
+					style.borderColor = this.elActiveColor; 
+					style.backgroundColor = this.elActiveColor;
+				}
+				style.width = this.$u.addUnit(this.checkboxSize);
+				style.height = this.$u.addUnit(this.checkboxSize);
+				return style;
+			},
+			// checkbox内部的勾选图标,如果选中状态,为白色,否则为透明色即可
+			iconColor() {
+				return this.value ? '#ffffff' : 'transparent';
+			},
+			iconClass() {
+				let classes = [];
+				classes.push('u-checkbox__icon-wrap--' + this.elShape);
+				if (this.value == true) classes.push('u-checkbox__icon-wrap--checked');
+				if (this.isDisabled) classes.push('u-checkbox__icon-wrap--disabled');
+				if (this.value && this.isDisabled) classes.push('u-checkbox__icon-wrap--disabled--checked');
+				// 支付宝小程序无法动态绑定一个数组类名,否则解析出来的结果会带有",",而导致失效
+				return classes.join(' ');
+			},
+			checkboxStyle() {
+				let style = {};
+				if(this.parent && this.parent.width) {
+					style.width = this.parent.width;
+					// #ifdef MP
+					// 各家小程序因为它们特殊的编译结构,使用float布局
+					style.float = 'left';
+					// #endif
+					// #ifndef MP
+					// H5和APP使用flex布局
+					style.flex = `0 0 ${this.parent.width}`;
+					// #endif
+				}
+				if(this.parent && this.parent.wrap) {
+					style.width = '100%';
+					// #ifndef MP
+					// H5和APP使用flex布局,将宽度设置100%,即可自动换行
+					style.flex = '0 0 100%';
+					// #endif
+				}
+				return style;
+			}
+		},
+		methods: {
+			onClickLabel() {
+				if (!this.isLabelDisabled && !this.isDisabled) {
+					this.setValue();
+				}
+			},
+			toggle() {
+				if (!this.isDisabled) {
+					this.setValue();
+				}
+			},
+			emitEvent() {
+				this.$emit('change', {
+					value: !this.value,
+					name: this.name
+				})
+				// 执行父组件u-checkbox-group的事件方法
+				// 等待下一个周期再执行,因为this.$emit('input')作用于父组件,再反馈到子组件内部,需要时间
+				setTimeout(() => {
+					if(this.parent && this.parent.emitEvent) this.parent.emitEvent();
+				}, 80);
+			},
+			// 设置input的值,这里通过input事件,设置通过v-model绑定的组件的值
+			setValue() {
+				// 判断是否超过了可选的最大数量
+				let checkedNum = 0;
+				if(this.parent && this.parent.children) {
+					// 只要父组件的某一个子元素的value为true,就加1(已有的选中数量)
+					this.parent.children.map(val => {
+						if (val.value) checkedNum++;
+					})
+				}
+				// 如果原来为选中状态,那么可以取消
+				if (this.value == true) {
+					this.emitEvent();
+					this.$emit('input', !this.value);
+				} else {
+					// 如果超出最多可选项,提示
+					if(this.parent && checkedNum >= this.parent.max) {
+						return this.$u.toast(`最多可选${this.parent.max}项`);
+					}
+					// 如果原来为未选中状态,需要选中的数量少于父组件中设置的max值,才可以选中
+					this.emitEvent();
+					this.$emit('input', !this.value);
+				}
+			}
+		}
+	};
+</script>
+
+<style lang="scss" scoped>
+	@import "../../libs/css/style.components.scss";
+
+	.u-checkbox {
+		/* #ifndef APP-NVUE */
+		display: inline-flex;
+		/* #endif */
+		align-items: center;
+		overflow: hidden;
+		user-select: none;
+		line-height: 1.8;
+		
+		&__icon-wrap {
+			color: $u-content-color;
+			flex: none;
+			display: -webkit-flex;
+			@include vue-flex;
+			align-items: center;
+			justify-content: center;
+			box-sizing: border-box;
+			width: 42rpx;
+			height: 42rpx;
+			color: transparent;
+			text-align: center;
+			transition-property: color, border-color, background-color;
+			font-size: 20px;
+			border: 1px solid #c8c9cc;
+			transition-duration: 0.2s;
+			
+			/* #ifdef MP-TOUTIAO */
+			// 头条小程序兼容性问题,需要设置行高为0,否则图标偏下
+			&__icon {
+				line-height: 0;
+			}
+			/* #endif */
+			
+			&--circle {
+				border-radius: 100%;
+			}
+			
+			&--square {
+				border-radius: 6rpx;
+			}
+			
+			&--checked {
+				color: #fff;
+				background-color: $u-type-primary;
+				border-color: $u-type-primary;
+			}
+			
+			&--disabled {
+				background-color: #ebedf0;
+				border-color: #c8c9cc;
+			}
+			
+			&--disabled--checked {
+				color: #c8c9cc !important;
+			}
+		}
+	
+		&__label {
+			word-wrap: break-word;
+			margin-left: 10rpx;
+			margin-right: 24rpx;
+			color: $u-content-color;
+			font-size: 30rpx;
+			
+			&--disabled {
+				color: #c8c9cc;
+			}
+		}
+	}
+</style>

+ 193 - 0
uview-ui/components/u-empty/u-empty.vue

@@ -0,0 +1,193 @@
+<template>
+	<view class="u-empty" v-if="show" :style="{
+		marginTop: marginTop + 'rpx'
+	}">
+		<u-icon
+			:name="src ? src : 'empty-' + mode"
+			:custom-style="iconStyle"
+			:label="text ? text : icons[mode]"
+			label-pos="bottom"
+			:label-color="color"
+			:label-size="fontSize"
+			:size="iconSize"
+			:color="iconColor"
+			margin-top="14"
+		></u-icon>
+		<view class="u-slot-wrap">
+			<slot name="bottom"></slot>
+		</view>
+	</view>
+</template>
+
+<script>
+	/**
+	 * empty 内容为空
+	 * @description 该组件用于需要加载内容,但是加载的第一页数据就为空,提示一个"没有内容"的场景, 我们精心挑选了十几个场景的图标,方便您使用。
+	 * @tutorial https://www.uviewui.com/components/empty.html
+	 * @property {String} color 文字颜色(默认#c0c4cc)
+	 * @property {String} text 文字提示(默认“无内容”)
+	 * @property {String} src 自定义图标路径,如定义,mode参数会失效
+	 * @property {String Number} font-size 提示文字的大小,单位rpx(默认28)
+	 * @property {String} mode 内置的图标,见官网说明(默认data)
+	 * @property {String Number} img-width 图标的宽度,单位rpx(默认240)
+	 * @property {String} img-height 图标的高度,单位rpx(默认auto)
+	 * @property {String Number} margin-top 组件距离上一个元素之间的距离(默认0)
+	 * @property {Boolean} show 是否显示组件(默认true)
+	 * @event {Function} click 点击组件时触发
+	 * @event {Function} close 点击关闭按钮时触发
+	 * @example <u-empty text="所谓伊人,在水一方" mode="list"></u-empty>
+	 */
+	export default {
+		name: "u-empty",
+		props: {
+			// 图标路径
+			src: {
+				type: String,
+				default: ''
+			},
+			// 提示文字
+			text: {
+				type: String,
+				default: ''
+			},
+			// 文字颜色
+			color: {
+				type: String,
+				default: '#c0c4cc'
+			},
+			// 图标的颜色
+			iconColor: {
+				type: String,
+				default: '#c0c4cc'
+			},
+			// 图标的大小
+			iconSize: {
+				type: [String, Number],
+				default: 120
+			},
+			// 文字大小,单位rpx
+			fontSize: {
+				type: [String, Number],
+				default: 26
+			},
+			// 选择预置的图标类型
+			mode: {
+				type: String,
+				default: 'data'
+			},
+			//  图标宽度,单位rpx
+			imgWidth: {
+				type: [String, Number],
+				default: 120
+			},
+			// 图标高度,单位rpx
+			imgHeight: {
+				type: [String, Number],
+				default: 'auto'
+			},
+			// 是否显示组件
+			show: {
+				type: Boolean,
+				default: true
+			},
+			// 组件距离上一个元素之间的距离
+			marginTop: {
+				type: [String, Number],
+				default: 0
+			},
+			iconStyle: {
+				type: Object,
+				default() {
+					return {}
+				}
+			}
+		},
+		data() {
+			return {
+				icons: {
+					car: '购物车为空',
+					page: '页面不存在',
+					search: '没有搜索结果',
+					address: '没有收货地址',
+					wifi: '没有WiFi',
+					order: '订单为空',
+					coupon: '没有优惠券',
+					favor: '暂无收藏',
+					permission: '无权限',
+					history: '无历史记录',
+					news: '无新闻列表',
+					message: '消息列表为空',
+					list: '列表为空',
+					data: '数据为空'
+				},
+				// icons: [{
+				// 	icon: 'car',
+				// 	text: '购物车为空'
+				// },{
+				// 	icon: 'page',
+				// 	text: '页面不存在'
+				// },{
+				// 	icon: 'search',
+				// 	text: '没有搜索结果'
+				// },{
+				// 	icon: 'address',
+				// 	text: '没有收货地址'
+				// },{
+				// 	icon: 'wifi',
+				// 	text: '没有WiFi'
+				// },{
+				// 	icon: 'order',
+				// 	text: '订单为空'
+				// },{
+				// 	icon: 'coupon',
+				// 	text: '没有优惠券'
+				// },{
+				// 	icon: 'favor',
+				// 	text: '暂无收藏'
+				// },{
+				// 	icon: 'permission',
+				// 	text: '无权限'
+				// },{
+				// 	icon: 'history',
+				// 	text: '无历史记录'
+				// },{
+				// 	icon: 'news',
+				// 	text: '无新闻列表'
+				// },{
+				// 	icon: 'message',
+				// 	text: '消息列表为空'
+				// },{
+				// 	icon: 'list',
+				// 	text: '列表为空'
+				// },{
+				// 	icon: 'data',
+				// 	text: '数据为空'
+				// }],
+
+			}
+		}
+	}
+</script>
+
+<style scoped lang="scss">
+	@import "../../libs/css/style.components.scss";
+
+	.u-empty {
+		@include vue-flex;
+		flex-direction: column;
+		justify-content: center;
+		align-items: center;
+		height: 100%;
+	}
+
+	.u-image {
+		margin-bottom: 20rpx;
+	}
+
+	.u-slot-wrap {
+		@include vue-flex;
+		justify-content: center;
+		align-items: center;
+		margin-top: 20rpx;
+	}
+</style>

+ 431 - 0
uview-ui/components/u-form-item/u-form-item.vue

@@ -0,0 +1,431 @@
+<template>
+	<view class="u-form-item" :class="{'u-border-bottom': elBorderBottom, 'u-form-item__border-bottom--error': validateState === 'error' && showError('border-bottom')}">
+		<view class="u-form-item__body" :style="{
+			flexDirection: elLabelPosition == 'left' ? 'row' : 'column'
+		}">
+			<!-- 微信小程序中,将一个参数设置空字符串,结果会变成字符串"true" -->
+			<view class="u-form-item--left" :style="{
+				width: uLabelWidth,
+				flex: `0 0 ${uLabelWidth}`,
+				marginBottom: elLabelPosition == 'left' ? 0 : '10rpx',
+			}">
+				<!-- 为了块对齐 -->
+				<view class="u-form-item--left__content" v-if="required || leftIcon || label">
+					<!-- nvue不支持伪元素before -->
+					<text v-if="required" class="u-form-item--left__content--required">*</text>
+					<view class="u-form-item--left__content__icon" v-if="leftIcon">
+						<u-icon :name="leftIcon" :custom-style="leftIconStyle"></u-icon>
+					</view>
+					<view class="u-form-item--left__content__label" :style="[elLabelStyle, {
+						'justify-content': elLabelAlign == 'left' ? 'flex-start' : elLabelAlign == 'center' ? 'center' : 'flex-end'
+					}]">
+						{{label}}
+					</view>
+				</view>
+			</view>
+			<view class="u-form-item--right u-flex">
+				<view class="u-form-item--right__content">
+					<view class="u-form-item--right__content__slot ">
+						<slot />
+					</view>
+					<view class="u-form-item--right__content__icon u-flex" v-if="$slots.right || rightIcon">
+						<u-icon :custom-style="rightIconStyle" v-if="rightIcon" :name="rightIcon"></u-icon>
+						<slot name="right" />
+					</view>
+				</view>
+			</view>
+		</view>
+		<view class="u-form-item__message" v-if="validateState === 'error' && showError('message')" :style="{
+			paddingLeft: elLabelPosition == 'left' ? $u.addUnit(elLabelWidth) : '0',
+		}">{{validateMessage}}</view>
+	</view>
+</template>
+
+<script>
+	import Emitter from '../../libs/util/emitter.js';
+	import schema from '../../libs/util/async-validator';
+	// 去除警告信息
+	schema.warning = function() {};
+
+	/**
+	 * form-item 表单item
+	 * @description 此组件一般用于表单场景,可以配置Input输入框,Select弹出框,进行表单验证等。
+	 * @tutorial http://uviewui.com/components/form.html
+	 * @property {String} label 左侧提示文字
+	 * @property {Object} prop 表单域model对象的属性名,在使用 validate、resetFields 方法的情况下,该属性是必填的
+	 * @property {Boolean} border-bottom 是否显示表单域的下划线边框
+	 * @property {String} label-position 表单域提示文字的位置,left-左侧,top-上方
+	 * @property {String Number} label-width 提示文字的宽度,单位rpx(默认90)
+	 * @property {Object} label-style lable的样式,对象形式
+	 * @property {String} label-align lable的对齐方式
+	 * @property {String} right-icon 右侧自定义字体图标(限uView内置图标)或图片地址
+	 * @property {String} left-icon 左侧自定义字体图标(限uView内置图标)或图片地址
+	 * @property {Object} left-icon-style 左侧图标的样式,对象形式
+	 * @property {Object} right-icon-style 右侧图标的样式,对象形式
+	 * @property {Boolean} required 是否显示左边的"*"号,这里仅起展示作用,如需校验必填,请通过rules配置必填规则(默认false)
+	 * @example <u-form-item label="姓名"><u-input v-model="form.name" /></u-form-item>
+	 */
+
+	export default {
+		name: 'u-form-item',
+		mixins: [Emitter],
+		inject: {
+			uForm: {
+				default () {
+					return null
+				}
+			}
+		},
+		props: {
+			// input的label提示语
+			label: {
+				type: String,
+				default: ''
+			},
+			// 绑定的值
+			prop: {
+				type: String,
+				default: ''
+			},
+			// 是否显示表单域的下划线边框
+			borderBottom: {
+				type: [String, Boolean],
+				default: ''
+			},
+			// label的位置,left-左边,top-上边
+			labelPosition: {
+				type: String,
+				default: ''
+			},
+			// label的宽度,单位rpx
+			labelWidth: {
+				type: [String, Number],
+				default: ''
+			},
+			// lable的样式,对象形式
+			labelStyle: {
+				type: Object,
+				default () {
+					return {}
+				}
+			},
+			// lable字体的对齐方式
+			labelAlign: {
+				type: String,
+				default: ''
+			},
+			// 右侧图标
+			rightIcon: {
+				type: String,
+				default: ''
+			},
+			// 左侧图标
+			leftIcon: {
+				type: String,
+				default: ''
+			},
+			// 左侧图标的样式
+			leftIconStyle: {
+				type: Object,
+				default () {
+					return {}
+				}
+			},
+			// 左侧图标的样式
+			rightIconStyle: {
+				type: Object,
+				default () {
+					return {}
+				}
+			},
+			// 是否显示左边的必填星号,只作显示用,具体校验必填的逻辑,请在rules中配置
+			required: {
+				type: Boolean,
+				default: false
+			}
+		},
+		data() {
+			return {
+				initialValue: '', // 存储的默认值
+				// isRequired: false, // 是否必填,由于人性化考虑,必填"*"号通过props的required配置,不再通过rules的规则自动生成
+				validateState: '', // 是否校验成功
+				validateMessage: '', // 校验失败的提示语
+				// 有错误时的提示方式,message-提示信息,border-如果input设置了边框,变成呈红色,
+				errorType: ['message'],
+				fieldValue: '', // 获取当前子组件input的输入的值
+				// 父组件的参数,在computed计算中,无法得知this.parent发生变化,故将父组件的参数值,放到data中
+				parentData: {
+					borderBottom: true,
+					labelWidth: 90,
+					labelPosition: 'left',
+					labelStyle: {},
+					labelAlign: 'left',
+				}
+			};
+		},
+		watch: {
+			validateState(val) {
+				this.broadcastInputError();
+			},
+			// 监听u-form组件的errorType的变化
+			"uForm.errorType"(val) {
+				this.errorType = val;
+				this.broadcastInputError();
+			},
+		},
+		computed: {
+			// 计算后的label宽度,由于需要多个判断,故放到computed中
+			uLabelWidth() {
+				// 如果用户设置label为空字符串(微信小程序空字符串最终会变成字符串的'true'),意味着要将label的位置宽度设置为auto
+				return this.elLabelPosition == 'left' ? (this.label === 'true' || this.label === '' ? 'auto' : this.$u.addUnit(this
+					.elLabelWidth)) : '100%';
+			},
+			showError() {
+				return type => {
+					// 如果errorType数组中含有none,或者toast提示类型
+					if (this.errorType.indexOf('none') >= 0) return false;
+					else if (this.errorType.indexOf(type) >= 0) return true;
+					else return false;
+				}
+			},
+			// label的宽度
+			elLabelWidth() {
+				// label默认宽度为90,优先使用本组件的值,如果没有(如果设置为0,也算是配置了值,依然起效),则用u-form的值
+				return (this.labelWidth != 0 || this.labelWidth != '') ? this.labelWidth : (this.parentData.labelWidth ? this.parentData
+					.labelWidth :
+					90);
+			},
+			// label的样式
+			elLabelStyle() {
+				return Object.keys(this.labelStyle).length ? this.labelStyle : (this.parentData.labelStyle ? this.parentData.labelStyle :
+					{});
+			},
+			// label的位置,左侧或者上方
+			elLabelPosition() {
+				return this.labelPosition ? this.labelPosition : (this.parentData.labelPosition ? this.parentData.labelPosition :
+					'left');
+			},
+			// label的对齐方式
+			elLabelAlign() {
+				return this.labelAlign ? this.labelAlign : (this.parentData.labelAlign ? this.parentData.labelAlign : 'left');
+			},
+			// label的下划线
+			elBorderBottom() {
+				// 子组件的borderBottom默认为空字符串,如果不等于空字符串,意味着子组件设置了值,优先使用子组件的值
+				return this.borderBottom !== '' ? this.borderBottom : this.parentData.borderBottom ? this.parentData.borderBottom :
+					true;
+			}
+		},
+		methods: {
+			broadcastInputError() {
+				// 子组件发出事件,第三个参数为true或者false,true代表有错误
+				this.broadcast('u-input', 'on-form-item-error', this.validateState === 'error' && this.showError('border'));
+			},
+			// 判断是否需要required校验
+			setRules() {
+				let that = this;
+				// 由于人性化考虑,必填"*"号通过props的required配置,不再通过rules的规则自动生成
+				// 从父组件u-form拿到当前u-form-item需要验证 的规则
+				// let rules = this.getRules();
+				// if (rules.length) {
+				// 	this.isRequired = rules.some(rule => {
+				// 		// 如果有必填项,就返回,没有的话,就是undefined
+				// 		return rule.required;
+				// 	});
+				// }
+
+				// blur事件
+				this.$on('on-form-blur', that.onFieldBlur);
+				// change事件
+				this.$on('on-form-change', that.onFieldChange);
+			},
+
+			// 从u-form的rules属性中,取出当前u-form-item的校验规则
+			getRules() {
+				// 父组件的所有规则
+				let rules = this.parent.rules;
+				rules = rules ? rules[this.prop] : [];
+				// 保证返回的是一个数组形式
+				return [].concat(rules || []);
+			},
+
+			// blur事件时进行表单校验
+			onFieldBlur() {
+				this.validation('blur');
+			},
+
+			// change事件进行表单校验
+			onFieldChange() {
+				this.validation('change');
+			},
+
+			// 过滤出符合要求的rule规则
+			getFilteredRule(triggerType = '') {
+				let rules = this.getRules();
+				// 整体验证表单时,triggerType为空字符串,此时返回所有规则进行验证
+				if (!triggerType) return rules;
+				// 历遍判断规则是否有对应的事件,比如blur,change触发等的事件
+				// 使用indexOf判断,是因为某些时候设置的验证规则的trigger属性可能为多个,比如['blur','change']
+				// 某些场景可能的判断规则,可能不存在trigger属性,故先判断是否存在此属性
+				return rules.filter(res => res.trigger && res.trigger.indexOf(triggerType) !== -1);
+			},
+
+			// 校验数据
+			validation(trigger, callback = () => {}) {
+				// 检验之间,先获取需要校验的值
+				this.fieldValue = this.parent.model[this.prop];
+				// blur和change是否有当前方式的校验规则
+				let rules = this.getFilteredRule(trigger);
+				// 判断是否有验证规则,如果没有规则,也调用回调方法,否则父组件u-form会因为
+				// 对count变量的统计错误而无法进入上一层的回调
+				if (!rules || rules.length === 0) {
+					return callback('');
+				}
+				// 设置当前的装填,标识为校验中
+				this.validateState = 'validating';
+				// 调用async-validator的方法
+				let validator = new schema({
+					[this.prop]: rules
+				});
+				validator.validate({
+					[this.prop]: this.fieldValue
+				}, {
+					firstFields: true
+				}, (errors, fields) => {
+					// 记录状态和报错信息
+					this.validateState = !errors ? 'success' : 'error';
+					this.validateMessage = errors ? errors[0].message : '';
+					// 调用回调方法
+					callback(this.validateMessage);
+				});
+			},
+
+			// 清空当前的u-form-item
+			resetField() {
+				this.parent.model[this.prop] = this.initialValue;
+				// 设置为`success`状态,只是为了清空错误标记
+				this.validateState = 'success';
+			}
+		},
+
+		// 组件创建完成时,将当前实例保存到u-form中
+		mounted() {
+			// 支付宝、头条小程序不支持provide/inject,所以使用这个方法获取整个父组件,在created定义,避免循环应用
+			this.parent = this.$u.$parent.call(this, 'u-form');
+			if (this.parent) {
+				// 历遍parentData中的属性,将parent中的同名属性赋值给parentData
+				Object.keys(this.parentData).map(key => {
+					this.parentData[key] = this.parent[key];
+				});
+				// 如果没有传入prop,或者uForm为空(如果u-form-input单独使用,就不会有uForm注入),就不进行校验
+				if (this.prop) {
+					// 将本实例添加到父组件中
+					this.parent.fields.push(this);
+					this.errorType = this.parent.errorType;
+					// 设置初始值
+					this.initialValue = this.fieldValue;
+					// 添加表单校验,这里必须要写在$nextTick中,因为u-form的rules是通过ref手动传入的
+					// 不在$nextTick中的话,可能会造成执行此处代码时,父组件还没通过ref把规则给u-form,导致规则为空
+					this.$nextTick(() => {
+						this.setRules();
+					})
+				}
+			}
+		},
+
+		// 组件销毁前,将实例从u-form的缓存中移除
+		beforeDestroy() {
+			// 如果当前没有prop的话表示当前不要进行删除(因为没有注入)
+			if (this.parent && this.prop) {
+				this.parent.fields.map((item, index) => {
+					if (item === this) this.parent.fields.splice(index, 1);
+				})
+			}
+		},
+	};
+</script>
+
+<style lang="scss" scoped>
+	@import "../../libs/css/style.components.scss";
+
+	.u-form-item {
+		@include vue-flex;
+		// align-items: flex-start;
+		padding: 20rpx 0;
+		font-size: 28rpx;
+		color: $u-main-color;
+		box-sizing: border-box;
+		line-height: $u-form-item-height;
+		flex-direction: column;
+
+		&__border-bottom--error:after {
+			border-color: $u-type-error;
+		}
+
+		&__body {
+			@include vue-flex;
+		}
+
+		&--left {
+			@include vue-flex;
+			align-items: center;
+
+			&__content {
+				position: relative;
+				@include vue-flex;
+				align-items: center;
+				padding-right: 10rpx;
+				flex: 1;
+
+				&__icon {
+					margin-right: 8rpx;
+				}
+
+				&--required {
+					position: absolute;
+					left: -16rpx;
+					vertical-align: middle;
+					color: $u-type-error;
+					padding-top: 6rpx;
+				}
+
+				&__label {
+					@include vue-flex;
+					align-items: center;
+					flex: 1;
+				}
+			}
+		}
+
+		&--right {
+			flex: 1;
+
+			&__content {
+				@include vue-flex;
+				align-items: center;
+				flex: 1;
+
+				&__slot {
+					flex: 1;
+					/* #ifndef MP */
+					@include vue-flex;
+					align-items: center;
+					/* #endif */
+				}
+
+				&__icon {
+					margin-left: 10rpx;
+					color: $u-light-color;
+					font-size: 30rpx;
+				}
+			}
+		}
+
+		&__message {
+			font-size: 24rpx;
+			line-height: 24rpx;
+			color: $u-type-error;
+			margin-top: 12rpx;
+		}
+	}
+</style>

+ 134 - 0
uview-ui/components/u-form/u-form.vue

@@ -0,0 +1,134 @@
+<template>
+	<view class="u-form"><slot /></view>
+</template>
+
+<script>
+	/**
+	 * form 表单
+	 * @description 此组件一般用于表单场景,可以配置Input输入框,Select弹出框,进行表单验证等。
+	 * @tutorial http://uviewui.com/components/form.html
+	 * @property {Object} model 表单数据对象
+	 * @property {Boolean} border-bottom 是否显示表单域的下划线边框
+	 * @property {String} label-position 表单域提示文字的位置,left-左侧,top-上方
+	 * @property {String Number} label-width 提示文字的宽度,单位rpx(默认90)
+	 * @property {Object} label-style lable的样式,对象形式
+	 * @property {String} label-align lable的对齐方式
+	 * @property {Object} rules 通过ref设置,见官网说明
+	 * @property {Array} error-type 错误的提示方式,数组形式,见上方说明(默认['message'])
+	 * @example <u-form :model="form" ref="uForm"></u-form>
+	 */
+
+export default {
+	name: 'u-form',
+	props: {
+		// 当前form的需要验证字段的集合
+		model: {
+			type: Object,
+			default() {
+				return {};
+			}
+		},
+		// 验证规则
+		// rules: {
+		// 	type: [Object, Function, Array],
+		// 	default() {
+		// 		return {};
+		// 	}
+		// },
+		// 有错误时的提示方式,message-提示信息,border-如果input设置了边框,变成呈红色,
+		// border-bottom-下边框呈现红色,none-无提示
+		errorType: {
+			type: Array,
+			default() {
+				return ['message', 'toast']
+			}
+		},
+		// 是否显示表单域的下划线边框
+		borderBottom: {
+			type: Boolean,
+			default: true
+		},
+		// label的位置,left-左边,top-上边
+		labelPosition: {
+			type: String,
+			default: 'left'
+		},
+		// label的宽度,单位rpx
+		labelWidth: {
+			type: [String, Number],
+			default: 90
+		},
+		// lable字体的对齐方式
+		labelAlign: {
+			type: String,
+			default: 'left'
+		},
+		// lable的样式,对象形式
+		labelStyle: {
+			type: Object,
+			default() {
+				return {}
+			}
+		},
+	},
+	provide() {
+		return {
+			uForm: this
+		};
+	},
+	data() {
+		return {
+			rules: {}
+		};
+	},
+	created() {
+		// 存储当前form下的所有u-form-item的实例
+		// 不能定义在data中,否则微信小程序会造成循环引用而报错
+		this.fields = [];
+	},
+	methods: {
+		setRules(rules) {
+			this.rules = rules;
+		},
+		// 清空所有u-form-item组件的内容,本质上是调用了u-form-item组件中的resetField()方法
+		resetFields() {
+			this.fields.map(field => {
+				field.resetField();
+			});
+		},
+		// 校验全部数据
+		validate(callback) {
+			return new Promise(resolve => {
+				// 对所有的u-form-item进行校验
+				let valid = true; // 默认通过
+				let count = 0; // 用于标记是否检查完毕
+				let errorArr = []; // 存放错误信息
+				this.fields.map(field => {
+					// 调用每一个u-form-item实例的validation的校验方法
+					field.validation('', error => {
+						// 如果任意一个u-form-item校验不通过,就意味着整个表单不通过
+						if (error) {
+							valid = false;
+							errorArr.push(error);
+						}
+						// 当历遍了所有的u-form-item时,调用promise的then方法
+						if (++count === this.fields.length) {
+							resolve(valid); // 进入promise的then方法
+							// 判断是否设置了toast的提示方式,只提示最前面的表单域的第一个错误信息
+							if(this.errorType.indexOf('none') === -1 && this.errorType.indexOf('toast') >= 0 && errorArr.length) {
+								this.$u.toast(errorArr[0]);
+							}
+							// 调用回调方法
+							if (typeof callback == 'function') callback(valid);
+						}
+					});
+				});
+			});
+		}
+	}
+};
+</script>
+
+<style scoped lang="scss">
+@import "../../libs/css/style.components.scss";
+</style>

+ 336 - 0
uview-ui/components/u-icon/u-icon.vue

@@ -0,0 +1,336 @@
+<template>
+	<view :style="[customStyle]" class="u-icon" @tap="click" :class="['u-icon--' + labelPos]">
+		<image class="u-icon__img" v-if="isImg" :src="name" :mode="imgMode" :style="[imgStyle]"></image>
+		<text v-else class="u-icon__icon" :class="customClass" :style="[iconStyle]" :hover-class="hoverClass"
+			  @touchstart="touchstart">
+			<text v-if="showDecimalIcon" :style="[decimalIconStyle]" :class="decimalIconClass" :hover-class="hoverClass"
+				  class="u-icon__decimal">
+			</text>
+		</text>
+		<!-- 这里进行空字符串判断,如果仅仅是v-if="label",可能会出现传递0的时候,结果也无法显示 -->
+		<text v-if="label !== ''" class="u-icon__label" :style="{
+			color: labelColor,
+			fontSize: $u.addUnit(labelSize),
+			marginLeft: labelPos == 'right' ? $u.addUnit(marginLeft) : 0,
+			marginTop: labelPos == 'bottom' ? $u.addUnit(marginTop) : 0,
+			marginRight: labelPos == 'left' ? $u.addUnit(marginRight) : 0,
+			marginBottom: labelPos == 'top' ? $u.addUnit(marginBottom) : 0,
+		}">{{ label }}
+		</text>
+	</view>
+</template>
+
+<script>
+/**
+ * icon 图标
+ * @description 基于字体的图标集,包含了大多数常见场景的图标。
+ * @tutorial https://www.uviewui.com/components/icon.html
+ * @property {String} name 图标名称,见示例图标集
+ * @property {String} color 图标颜色(默认inherit)
+ * @property {String | Number} size 图标字体大小,单位rpx(默认32)
+ * @property {String | Number} label-size label字体大小,单位rpx(默认28)
+ * @property {String} label 图标右侧的label文字(默认28)
+ * @property {String} label-pos label文字相对于图标的位置,只能right或bottom(默认right)
+ * @property {String} label-color label字体颜色(默认#606266)
+ * @property {Object} custom-style icon的样式,对象形式
+ * @property {String} custom-prefix 自定义字体图标库时,需要写上此值
+ * @property {String | Number} margin-left label在右侧时与图标的距离,单位rpx(默认6)
+ * @property {String | Number} margin-top label在下方时与图标的距离,单位rpx(默认6)
+ * @property {String | Number} margin-bottom label在上方时与图标的距离,单位rpx(默认6)
+ * @property {String | Number} margin-right label在左侧时与图标的距离,单位rpx(默认6)
+ * @property {String} label-pos label相对于图标的位置,只能right或bottom(默认right)
+ * @property {String} index 一个用于区分多个图标的值,点击图标时通过click事件传出
+ * @property {String} hover-class 图标按下去的样式类,用法同uni的view组件的hover-class参数,详情见官网
+ * @property {String} width 显示图片小图标时的宽度
+ * @property {String} height 显示图片小图标时的高度
+ * @property {String} top 图标在垂直方向上的定位
+ * @property {String} top 图标在垂直方向上的定位
+ * @property {String} top 图标在垂直方向上的定位
+ * @property {Boolean} show-decimal-icon 是否为DecimalIcon
+ * @property {String} inactive-color 背景颜色,可接受主题色,仅Decimal时有效
+ * @property {String | Number} percent 显示的百分比,仅Decimal时有效
+ * @event {Function} click 点击图标时触发
+ * @example <u-icon name="photo" color="#2979ff" size="28"></u-icon>
+ */
+export default {
+	name: 'u-icon',
+	props: {
+		// 图标类名
+		name: {
+			type: String,
+			default: ''
+		},
+		// 图标颜色,可接受主题色
+		color: {
+			type: String,
+			default: ''
+		},
+		// 字体大小,单位rpx
+		size: {
+			type: [Number, String],
+			default: 'inherit'
+		},
+		// 是否显示粗体
+		bold: {
+			type: Boolean,
+			default: false
+		},
+		// 点击图标的时候传递事件出去的index(用于区分点击了哪一个)
+		index: {
+			type: [Number, String],
+			default: ''
+		},
+		// 触摸图标时的类名
+		hoverClass: {
+			type: String,
+			default: ''
+		},
+		// 自定义扩展前缀,方便用户扩展自己的图标库
+		customPrefix: {
+			type: String,
+			default: 'uicon'
+		},
+		// 图标右边或者下面的文字
+		label: {
+			type: [String, Number],
+			default: ''
+		},
+		// label的位置,只能右边或者下边
+		labelPos: {
+			type: String,
+			default: 'right'
+		},
+		// label的大小
+		labelSize: {
+			type: [String, Number],
+			default: '28'
+		},
+		// label的颜色
+		labelColor: {
+			type: String,
+			default: '#606266'
+		},
+		// label与图标的距离(横向排列)
+		marginLeft: {
+			type: [String, Number],
+			default: '6'
+		},
+		// label与图标的距离(竖向排列)
+		marginTop: {
+			type: [String, Number],
+			default: '6'
+		},
+		// label与图标的距离(竖向排列)
+		marginRight: {
+			type: [String, Number],
+			default: '6'
+		},
+		// label与图标的距离(竖向排列)
+		marginBottom: {
+			type: [String, Number],
+			default: '6'
+		},
+		// 图片的mode
+		imgMode: {
+			type: String,
+			default: 'widthFix'
+		},
+		// 自定义样式
+		customStyle: {
+			type: Object,
+			default() {
+				return {}
+			}
+		},
+		// 用于显示图片小图标时,图片的宽度
+		width: {
+			type: [String, Number],
+			default: ''
+		},
+		// 用于显示图片小图标时,图片的高度
+		height: {
+			type: [String, Number],
+			default: ''
+		},
+		// 用于解决某些情况下,让图标垂直居中的用途
+		top: {
+			type: [String, Number],
+			default: 0
+		},
+		// 是否为DecimalIcon
+		showDecimalIcon: {
+			type: Boolean,
+			default: false
+		},
+		// 背景颜色,可接受主题色,仅Decimal时有效
+		inactiveColor: {
+			type: String,
+			default: '#ececec'
+		},
+		// 显示的百分比,仅Decimal时有效
+		percent: {
+			type: [Number, String],
+			default: '50'
+		}
+	},
+	computed: {
+		customClass() {
+			let classes = []
+			classes.push(this.customPrefix + '-' + this.name)
+			// uView的自定义图标类名为u-iconfont
+			if (this.customPrefix == 'uicon') {
+				classes.push('u-iconfont')
+			} else {
+				classes.push(this.customPrefix)
+			}
+			// 主题色,通过类配置
+			if (this.showDecimalIcon && this.inactiveColor && this.$u.config.type.includes(this.inactiveColor)) {
+				classes.push('u-icon__icon--' + this.inactiveColor)
+			} else if (this.color && this.$u.config.type.includes(this.color)) classes.push('u-icon__icon--' + this.color)
+			// 阿里,头条,百度小程序通过数组绑定类名时,无法直接使用[a, b, c]的形式,否则无法识别
+			// 故需将其拆成一个字符串的形式,通过空格隔开各个类名
+			//#ifdef MP-ALIPAY || MP-TOUTIAO || MP-BAIDU
+			classes = classes.join(' ')
+			//#endif
+			return classes
+		},
+		iconStyle() {
+			let style = {}
+			style = {
+				fontSize: this.size == 'inherit' ? 'inherit' : this.$u.addUnit(this.size),
+				fontWeight: this.bold ? 'bold' : 'normal',
+				// 某些特殊情况需要设置一个到顶部的距离,才能更好的垂直居中
+				top: this.$u.addUnit(this.top)
+			}
+			// 非主题色值时,才当作颜色值
+			if (this.showDecimalIcon && this.inactiveColor && !this.$u.config.type.includes(this.inactiveColor)) {
+				style.color = this.inactiveColor
+			} else if (this.color && !this.$u.config.type.includes(this.color)) style.color = this.color
+
+			return style
+		},
+		// 判断传入的name属性,是否图片路径,只要带有"/"均认为是图片形式
+		isImg() {
+			return this.name.indexOf('/') !== -1
+		},
+		imgStyle() {
+			let style = {}
+			// 如果设置width和height属性,则优先使用,否则使用size属性
+			style.width = this.width ? this.$u.addUnit(this.width) : this.$u.addUnit(this.size)
+			style.height = this.height ? this.$u.addUnit(this.height) : this.$u.addUnit(this.size)
+			return style
+		},
+		decimalIconStyle() {
+			let style = {}
+			style = {
+				fontSize: this.size == 'inherit' ? 'inherit' : this.$u.addUnit(this.size),
+				fontWeight: this.bold ? 'bold' : 'normal',
+				// 某些特殊情况需要设置一个到顶部的距离,才能更好的垂直居中
+				top: this.$u.addUnit(this.top),
+				width: this.percent + '%'
+			}
+			// 非主题色值时,才当作颜色值
+			if (this.color && !this.$u.config.type.includes(this.color)) style.color = this.color
+			return style
+		},
+		decimalIconClass() {
+			let classes = []
+			classes.push(this.customPrefix + '-' + this.name)
+			// uView的自定义图标类名为u-iconfont
+			if (this.customPrefix == 'uicon') {
+				classes.push('u-iconfont')
+			} else {
+				classes.push(this.customPrefix)
+			}
+			// 主题色,通过类配置
+			if (this.color && this.$u.config.type.includes(this.color)) classes.push('u-icon__icon--' + this.color)
+			else classes.push('u-icon__icon--primary')
+			// 阿里,头条,百度小程序通过数组绑定类名时,无法直接使用[a, b, c]的形式,否则无法识别
+			// 故需将其拆成一个字符串的形式,通过空格隔开各个类名
+			//#ifdef MP-ALIPAY || MP-TOUTIAO || MP-BAIDU
+			classes = classes.join(' ')
+			//#endif
+			return classes
+		}
+	},
+	methods: {
+		click() {
+			this.$emit('click', this.index)
+		},
+		touchstart() {
+			this.$emit('touchstart', this.index)
+		}
+	}
+}
+</script>
+
+<style scoped lang="scss">
+@import "../../libs/css/style.components.scss";
+@import '../../iconfont.css';
+
+.u-icon {
+	display: inline-flex;
+	align-items: center;
+
+	&--left {
+		flex-direction: row-reverse;
+		align-items: center;
+	}
+
+	&--right {
+		flex-direction: row;
+		align-items: center;
+	}
+
+	&--top {
+		flex-direction: column-reverse;
+		justify-content: center;
+	}
+
+	&--bottom {
+		flex-direction: column;
+		justify-content: center;
+	}
+
+	&__icon {
+		position: relative;
+
+		&--primary {
+			color: $u-type-primary;
+		}
+
+		&--success {
+			color: $u-type-success;
+		}
+
+		&--error {
+			color: $u-type-error;
+		}
+
+		&--warning {
+			color: $u-type-warning;
+		}
+
+		&--info {
+			color: $u-type-info;
+		}
+	}
+
+	&__decimal {
+		position: absolute;
+		top: 0;
+		left: 0;
+		display: inline-block;
+		overflow: hidden;
+	}
+
+	&__img {
+		height: auto;
+		will-change: transform;
+	}
+
+	&__label {
+		line-height: 1;
+	}
+}
+</style>

+ 387 - 0
uview-ui/components/u-input/u-input.vue

@@ -0,0 +1,387 @@
+<template>
+	<view
+		class="u-input"
+		:class="{
+			'u-input--border': border,
+			'u-input--error': validateState
+		}"
+		:style="{
+			padding: `0 ${border ? 20 : 0}rpx`,
+			borderColor: borderColor,
+			textAlign: inputAlign
+		}"
+		@tap.stop="inputClick"
+	>
+		<textarea
+			v-if="type == 'textarea'"
+			class="u-input__input u-input__textarea"
+			:style="[getStyle]"
+			:value="defaultValue"
+			:placeholder="placeholder"
+			:placeholderStyle="placeholderStyle"
+			:disabled="disabled"
+			:maxlength="inputMaxlength"
+			:fixed="fixed"
+			:focus="focus"
+			:autoHeight="autoHeight"
+			:selection-end="uSelectionEnd"
+			:selection-start="uSelectionStart"
+			:cursor-spacing="getCursorSpacing"
+			:show-confirm-bar="showConfirmbar"
+			@input="handleInput"
+			@blur="handleBlur"
+			@focus="onFocus"
+			@confirm="onConfirm"
+		/>
+		<input
+			v-else
+			class="u-input__input"
+			:type="type == 'password' ? 'text' : type"
+			:style="[getStyle]"
+			:value="defaultValue"
+			:password="type == 'password' && !showPassword"
+			:placeholder="placeholder"
+			:placeholderStyle="placeholderStyle"
+			:disabled="disabled || type === 'select'"
+			:maxlength="inputMaxlength"
+			:focus="focus"
+			:confirmType="confirmType"
+			:cursor-spacing="getCursorSpacing"
+			:selection-end="uSelectionEnd"
+			:selection-start="uSelectionStart"
+			:show-confirm-bar="showConfirmbar"
+			@focus="onFocus"
+			@blur="handleBlur"
+			@input="handleInput"
+			@confirm="onConfirm"
+		/>
+		<view class="u-input__right-icon u-flex">
+			<view class="u-input__right-icon__clear u-input__right-icon__item" @tap="onClear" v-if="clearable && value != '' && focused">
+				<u-icon size="32" name="close-circle-fill" color="#c0c4cc"/>
+			</view>
+			<view class="u-input__right-icon__clear u-input__right-icon__item" v-if="passwordIcon && type == 'password'">
+				<u-icon size="32" :name="!showPassword ? 'eye' : 'eye-fill'" color="#c0c4cc" @click="showPassword = !showPassword"/>
+			</view>
+			<view class="u-input__right-icon--select u-input__right-icon__item" v-if="type == 'select'" :class="{
+				'u-input__right-icon--select--reverse': selectOpen
+			}">
+				<u-icon name="arrow-down-fill" size="26" color="#c0c4cc"></u-icon>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+import Emitter from '../../libs/util/emitter.js';
+
+/**
+ * input 输入框
+ * @description 此组件为一个输入框,默认没有边框和样式,是专门为配合表单组件u-form而设计的,利用它可以快速实现表单验证,输入内容,下拉选择等功能。
+ * @tutorial http://uviewui.com/components/input.html
+ * @property {String} type 模式选择,见官网说明
+ * @property {Boolean} clearable 是否显示右侧的清除图标(默认true)
+ * @property {} v-model 用于双向绑定输入框的值
+ * @property {String} input-align 输入框文字的对齐方式(默认left)
+ * @property {String} placeholder placeholder显示值(默认 '请输入内容')
+ * @property {Boolean} disabled 是否禁用输入框(默认false)
+ * @property {String Number} maxlength 输入框的最大可输入长度(默认140)
+ * @property {String Number} selection-start 光标起始位置,自动聚焦时有效,需与selection-end搭配使用(默认-1)
+ * @property {String Number} maxlength 光标结束位置,自动聚焦时有效,需与selection-start搭配使用(默认-1)
+ * @property {String Number} cursor-spacing 指定光标与键盘的距离,单位px(默认0)
+ * @property {String} placeholderStyle placeholder的样式,字符串形式,如"color: red;"(默认 "color: #c0c4cc;")
+ * @property {String} confirm-type 设置键盘右下角按钮的文字,仅在type为text时生效(默认done)
+ * @property {Object} custom-style 自定义输入框的样式,对象形式
+ * @property {Boolean} focus 是否自动获得焦点(默认false)
+ * @property {Boolean} fixed 如果type为textarea,且在一个"position:fixed"的区域,需要指明为true(默认false)
+ * @property {Boolean} password-icon type为password时,是否显示右侧的密码查看图标(默认true)
+ * @property {Boolean} border 是否显示边框(默认false)
+ * @property {String} border-color 输入框的边框颜色(默认#dcdfe6)
+ * @property {Boolean} auto-height 是否自动增高输入区域,type为textarea时有效(默认true)
+ * @property {String Number} height 高度,单位rpx(text类型时为70,textarea时为100)
+ * @example <u-input v-model="value" :type="type" :border="border" />
+ */
+export default {
+	name: 'u-input',
+	mixins: [Emitter],
+	props: {
+		value: {
+			type: [String, Number],
+			default: ''
+		},
+		// 输入框的类型,textarea,text,number
+		type: {
+			type: String,
+			default: 'text'
+		},
+		inputAlign: {
+			type: String,
+			default: 'left'
+		},
+		placeholder: {
+			type: String,
+			default: '请输入内容'
+		},
+		disabled: {
+			type: Boolean,
+			default: false
+		},
+		maxlength: {
+			type: [Number, String],
+			default: 140
+		},
+		placeholderStyle: {
+			type: String,
+			default: 'color: #c0c4cc;'
+		},
+		confirmType: {
+			type: String,
+			default: 'done'
+		},
+		// 输入框的自定义样式
+		customStyle: {
+			type: Object,
+			default() {
+				return {};
+			}
+		},
+		// 如果 textarea 是在一个 position:fixed 的区域,需要显示指定属性 fixed 为 true
+		fixed: {
+			type: Boolean,
+			default: false
+		},
+		// 是否自动获得焦点
+		focus: {
+			type: Boolean,
+			default: false
+		},
+		// 密码类型时,是否显示右侧的密码图标
+		passwordIcon: {
+			type: Boolean,
+			default: true
+		},
+		// input|textarea是否显示边框
+		border: {
+			type: Boolean,
+			default: false
+		},
+		// 输入框的边框颜色
+		borderColor: {
+			type: String,
+			default: '#dcdfe6'
+		},
+		autoHeight: {
+			type: Boolean,
+			default: true
+		},
+		// type=select时,旋转右侧的图标,标识当前处于打开还是关闭select的状态
+		// open-打开,close-关闭
+		selectOpen: {
+			type: Boolean,
+			default: false
+		},
+		// 高度,单位rpx
+		height: {
+			type: [Number, String],
+			default: ''
+		},
+		// 是否可清空
+		clearable: {
+			type: Boolean,
+			default: true
+		},
+		// 指定光标与键盘的距离,单位 px
+		cursorSpacing: {
+			type: [Number, String],
+			default: 0
+		},
+		// 光标起始位置,自动聚焦时有效,需与selection-end搭配使用
+		selectionStart: {
+			type: [Number, String],
+			default: -1
+		},
+		// 光标结束位置,自动聚焦时有效,需与selection-start搭配使用
+		selectionEnd: {
+			type: [Number, String],
+			default: -1
+		},
+		// 是否自动去除两端的空格
+		trim: {
+			type: Boolean,
+			default: true
+		},
+		// 是否显示键盘上方带有”完成“按钮那一栏
+		showConfirmbar:{
+			type:Boolean,
+			default:true
+		}
+	},
+	data() {
+		return {
+			defaultValue: this.value,
+			inputHeight: 70, // input的高度
+			textareaHeight: 100, // textarea的高度
+			validateState: false, // 当前input的验证状态,用于错误时,边框是否改为红色
+			focused: false, // 当前是否处于获得焦点的状态
+			showPassword: false, // 是否预览密码
+			lastValue: '', // 用于头条小程序,判断@input中,前后的值是否发生了变化,因为头条中文下,按下键没有输入内容,也会触发@input时间
+		};
+	},
+	watch: {
+		value(nVal, oVal) {
+			this.defaultValue = nVal;
+			// 当值发生变化,且为select类型时(此时input被设置为disabled,不会触发@input事件),模拟触发@input事件
+			if(nVal != oVal && this.type == 'select') this.handleInput({
+				detail: {
+					value: nVal
+				}
+			})
+		},
+	},
+	computed: {
+		// 因为uniapp的input组件的maxlength组件必须要数值,这里转为数值,给用户可以传入字符串数值
+		inputMaxlength() {
+			return Number(this.maxlength);
+		},
+		getStyle() {
+			let style = {};
+			// 如果没有自定义高度,就根据type为input还是textare来分配一个默认的高度
+			style.minHeight = this.height ? this.height + 'rpx' : this.type == 'textarea' ?
+				this.textareaHeight + 'rpx' : this.inputHeight + 'rpx';
+			style = Object.assign(style, this.customStyle);
+			return style;
+		},
+		//
+		getCursorSpacing() {
+			return Number(this.cursorSpacing);
+		},
+		// 光标起始位置
+		uSelectionStart() {
+			return String(this.selectionStart);
+		},
+		// 光标结束位置
+		uSelectionEnd() {
+			return String(this.selectionEnd);
+		}
+	},
+	created() {
+		// 监听u-form-item发出的错误事件,将输入框边框变红色
+		this.$on('on-form-item-error', this.onFormItemError);
+	},
+	methods: {
+		/**
+		 * change 事件
+		 * @param event
+		 */
+		handleInput(event) {
+			let value = event.detail.value;
+			// 判断是否去除空格
+			if(this.trim) value = this.$u.trim(value);
+			// vue 原生的方法 return 出去
+			this.$emit('input', value);
+			// 当前model 赋值
+			this.defaultValue = value;
+			// 过一个生命周期再发送事件给u-form-item,否则this.$emit('input')更新了父组件的值,但是微信小程序上
+			// 尚未更新到u-form-item,导致获取的值为空,从而校验混论
+			// 这里不能延时时间太短,或者使用this.$nextTick,否则在头条上,会造成混乱
+			setTimeout(() => {
+				// 头条小程序由于自身bug,导致中文下,每按下一个键(尚未完成输入),都会触发一次@input,导致错误,这里进行判断处理
+				// #ifdef MP-TOUTIAO
+				if(this.$u.trim(value) == this.lastValue) return ;
+				this.lastValue = value;
+				// #endif
+				// 将当前的值发送到 u-form-item 进行校验
+				this.dispatch('u-form-item', 'on-form-change', value);
+			}, 40)
+		},
+		/**
+		 * blur 事件
+		 * @param event
+		 */
+		handleBlur(event) {
+			// 最开始使用的是监听图标@touchstart事件,自从hx2.8.4后,此方法在微信小程序出错
+			// 这里改为监听点击事件,手点击清除图标时,同时也发生了@blur事件,导致图标消失而无法点击,这里做一个延时
+			setTimeout(() => {
+				this.focused = false;
+			}, 100)
+			// vue 原生的方法 return 出去
+			this.$emit('blur', event.detail.value);
+			setTimeout(() => {
+				// 头条小程序由于自身bug,导致中文下,每按下一个键(尚未完成输入),都会触发一次@input,导致错误,这里进行判断处理
+				// #ifdef MP-TOUTIAO
+				if(this.$u.trim(value) == this.lastValue) return ;
+				this.lastValue = value;
+				// #endif
+				// 将当前的值发送到 u-form-item 进行校验
+				this.dispatch('u-form-item', 'on-form-blur', event.detail.value);
+			}, 40)
+		},
+		onFormItemError(status) {
+			this.validateState = status;
+		},
+		onFocus(event) {
+			this.focused = true;
+			this.$emit('focus');
+		},
+		onConfirm(e) {
+			this.$emit('confirm', e.detail.value);
+		},
+		onClear(event) {
+			this.$emit('input', '');
+		},
+		inputClick() {
+			this.$emit('click');
+		}
+	}
+};
+</script>
+
+<style lang="scss" scoped>
+@import "../../libs/css/style.components.scss";
+
+.u-input {
+	position: relative;
+	flex: 1;
+	@include vue-flex;
+
+	&__input {
+		//height: $u-form-item-height;
+		font-size: 28rpx;
+		color: $u-main-color;
+		flex: 1;
+	}
+
+	&__textarea {
+		width: auto;
+		font-size: 28rpx;
+		color: $u-main-color;
+		padding: 10rpx 0;
+		line-height: normal;
+		flex: 1;
+	}
+
+	&--border {
+		border-radius: 6rpx;
+		border-radius: 4px;
+		border: 1px solid $u-form-item-border-color;
+	}
+
+	&--error {
+		border-color: $u-type-error!important;
+	}
+
+	&__right-icon {
+
+		&__item {
+			margin-left: 10rpx;
+		}
+
+		&--select {
+			transition: transform .4s;
+
+			&--reverse {
+				transform: rotate(-180deg);
+			}
+		}
+	}
+}
+</style>

+ 147 - 0
uview-ui/components/u-line-progress/u-line-progress.vue

@@ -0,0 +1,147 @@
+<template>
+	<view class="u-progress" :style="{
+		borderRadius: round ? '100rpx' : 0,
+		height: height + 'rpx',
+		backgroundColor: inactiveColor
+	}">
+		<view :class="[
+			type ? `u-type-${type}-bg` : '',
+			striped ? 'u-striped' : '',
+			striped && stripedActive ? 'u-striped-active' : ''
+		]" class="u-active" :style="[progressStyle]">
+			<slot v-if="$slots.default || $slots.$default" />
+			<block v-else-if="showPercent">
+				{{percent + '%'}}
+			</block>
+		</view>
+	</view>
+</template>
+
+<script>
+	/**
+	 * lineProgress 线型进度条
+	 * @description 展示操作或任务的当前进度,比如上传文件,是一个线形的进度条。
+	 * @tutorial https://www.uviewui.com/components/lineProgress.html
+	 * @property {String Number} percent 进度条百分比值,为数值类型,0-100
+	 * @property {Boolean} round 进度条两端是否为半圆(默认true)
+	 * @property {String} type 如设置,active-color值将会失效
+	 * @property {String} active-color 进度条激活部分的颜色(默认#19be6b)
+	 * @property {String} inactive-color 进度条的底色(默认#ececec)
+	 * @property {Boolean} show-percent 是否在进度条内部显示当前的百分比值数值(默认true)
+	 * @property {String Number} height 进度条的高度,单位rpx(默认28)
+	 * @property {Boolean} striped 是否显示进度条激活部分的条纹(默认false)
+	 * @property {Boolean} striped-active 条纹是否具有动态效果(默认false)
+	 * @example <u-line-progress :percent="70" :show-percent="true"></u-line-progress>
+	 */
+	export default {
+		name: "u-line-progress",
+		props: {
+			// 两端是否显示半圆形
+			round: {
+				type: Boolean,
+				default: true
+			},
+			// 主题颜色
+			type: {
+				type: String,
+				default: ''
+			},
+			// 激活部分的颜色
+			activeColor: {
+				type: String,
+				default: '#19be6b'
+			},
+			inactiveColor: {
+				type: String,
+				default: '#ececec'
+			},
+			// 进度百分比,数值
+			percent: {
+				type: Number,
+				default: 0
+			},
+			// 是否在进度条内部显示百分比的值
+			showPercent: {
+				type: Boolean,
+				default: true
+			},
+			// 进度条的高度,单位rpx
+			height: {
+				type: [Number, String],
+				default: 28
+			},
+			// 是否显示条纹
+			striped: {
+				type: Boolean,
+				default: false
+			},
+			// 条纹是否显示活动状态
+			stripedActive: {
+				type: Boolean,
+				default: false
+			}
+		},
+		data() {
+			return {
+
+			}
+		},
+		computed: {
+			progressStyle() {
+				let style = {};
+				style.width = this.percent + '%';
+				if(this.activeColor) style.backgroundColor = this.activeColor;
+				return style;
+			}
+		},
+		methods: {
+
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	@import "../../libs/css/style.components.scss";
+	
+	.u-progress {
+		overflow: hidden;
+		height: 15px;
+		/* #ifndef APP-NVUE */
+		display: inline-flex;
+		/* #endif */
+		align-items: center;
+		width: 100%;
+		border-radius: 100rpx;
+	}
+
+	.u-active {
+		width: 0;
+		height: 100%;
+		align-items: center;
+		@include vue-flex;
+		justify-items: flex-end;
+		justify-content: space-around;
+		font-size: 20rpx;
+		color: #ffffff;
+		transition: all 0.4s ease;
+	}
+
+	.u-striped {
+		background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+		background-size: 39px 39px;
+	}
+
+	.u-striped-active {
+		animation: progress-stripes 2s linear infinite;
+	}
+
+	@keyframes progress-stripes {
+		0% {
+			background-position: 0 0;
+		}
+
+		100% {
+			background-position: 39px 0;
+		}
+	}
+</style>

+ 123 - 0
uview-ui/components/u-mask/u-mask.vue

@@ -0,0 +1,123 @@
+<template>
+	<view class="u-mask" hover-stop-propagation :style="[maskStyle, zoomStyle]" @tap="click" @touchmove.stop.prevent="() => {}" :class="{
+		'u-mask-zoom': zoom,
+		'u-mask-show': show
+	}">
+		<slot />
+	</view>
+</template>
+
+<script>
+	/**
+	 * mask 遮罩
+	 * @description 创建一个遮罩层,用于强调特定的页面元素,并阻止用户对遮罩下层的内容进行操作,一般用于弹窗场景
+	 * @tutorial https://www.uviewui.com/components/mask.html
+	 * @property {Boolean} show 是否显示遮罩(默认false)
+	 * @property {String Number} z-index z-index 层级(默认1070)
+	 * @property {Object} custom-style 自定义样式对象,见上方说明
+	 * @property {String Number} duration 动画时长,单位毫秒(默认300)
+	 * @property {Boolean} zoom 是否使用scale对遮罩进行缩放(默认true)
+	 * @property {Boolean} mask-click-able 遮罩是否可点击,为false时点击不会发送click事件(默认true)
+	 * @event {Function} click mask-click-able为true时,点击遮罩发送此事件
+	 * @example <u-mask :show="show" @click="show = false"></u-mask>
+	 */
+	export default {
+		name: "u-mask",
+		props: {
+			// 是否显示遮罩
+			show: {
+				type: Boolean,
+				default: false
+			},
+			// 层级z-index
+			zIndex: {
+				type: [Number, String],
+				default: ''
+			},
+			// 用户自定义样式
+			customStyle: {
+				type: Object,
+				default () {
+					return {}
+				}
+			},
+			// 遮罩的动画样式, 是否使用使用zoom进行scale进行缩放
+			zoom: {
+				type: Boolean,
+				default: true
+			},
+			// 遮罩的过渡时间,单位为ms
+			duration: {
+				type: [Number, String],
+				default: 300
+			},
+			// 是否可以通过点击遮罩进行关闭
+			maskClickAble: {
+				type: Boolean,
+				default: true
+			}
+		},
+		data() {
+			return {
+				zoomStyle: {
+					transform: ''
+				},
+				scale: 'scale(1.2, 1.2)'
+			}
+		},
+		watch: {
+			show(n) {
+				if(n && this.zoom) {
+					// 当展示遮罩的时候,设置scale为1,达到缩小(原来为1.2)的效果
+					this.zoomStyle.transform = 'scale(1, 1)';
+				} else if(!n && this.zoom) {
+					// 当隐藏遮罩的时候,设置scale为1.2,达到放大(因为显示遮罩时已重置为1)的效果
+					this.zoomStyle.transform = this.scale;
+				}
+			}
+		},
+		computed: {
+			maskStyle() {
+				let style = {};
+				style.backgroundColor = "rgba(0, 0, 0, 0.6)";
+				if(this.show) style.zIndex = this.zIndex ? this.zIndex : this.$u.zIndex.mask;
+				else style.zIndex = -1;
+				style.transition = `all ${this.duration / 1000}s ease-in-out`;
+				// 判断用户传递的对象是否为空,不为空就进行合并
+				if (Object.keys(this.customStyle).length) style = { 
+					...style,
+					...this.customStyle
+				};
+				return style;
+			}
+		},
+		methods: {
+			click() {
+				if (!this.maskClickAble) return;
+				this.$emit('click');
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	@import "../../libs/css/style.components.scss";
+	
+	.u-mask {
+		position: fixed;
+		top: 0;
+		left: 0;
+		right: 0;
+		bottom: 0;
+		opacity: 0;
+		transition: transform 0.3s;
+	}
+
+	.u-mask-show {
+		opacity: 1;
+	}
+	
+	.u-mask-zoom {
+		transform: scale(1.2, 1.2);
+	}
+</style>

+ 7 - 0
uview-ui/components/u-navbar/style.components.scss

@@ -0,0 +1,7 @@
+// 定义混入指令,用于在非nvue环境下的flex定义,因为nvue没有display属性,会报错
+@mixin vue-flex($direction: row) {
+	/* #ifndef APP-NVUE */
+	display: flex;
+	flex-direction: $direction;
+	/* #endif */
+}

+ 339 - 0
uview-ui/components/u-navbar/u-navbar.vue

@@ -0,0 +1,339 @@
+<template>
+	<view class="">
+		<view class="u-navbar" :style="[navbarStyle]" :class="{ 'u-navbar-fixed': isFixed, 'u-border-bottom': borderBottom }">
+			<view class="u-status-bar" :style="{ height: statusBarHeight + 'px' }"></view>
+			<view class="u-navbar-inner" :style="[navbarInnerStyle]">
+				<view class="u-back-wrap" v-if="isBack" @tap="goBack">
+					<view class="u-icon-wrap">
+						<image src="/static/icon/aboutme.png" mode="" style="height: 50upx;width: 50upx;"></image>
+					</view>
+					<view class="u-icon-wrap u-back-text u-line-1" v-if="backText" :style="[backTextStyle]">{{ backText }}</view>
+				</view>
+				<view class="u-slot-left">
+					<slot name="left"></slot>
+				</view>
+				
+				<view class="u-navbar-content-title" v-if="title" :style="[titleStyle]">
+					<view class="u-title u-line-1" :style="{
+							color: titleColor,
+							fontSize: titleSize + 'rpx',
+							fontWeight: titleBold?'bold':'100'
+						}">
+						{{ title }}
+					</view>
+				</view>
+				<view class="u-slot-content">
+					<slot></slot>
+				</view>
+				<view class="u-slot-right">
+					<slot name="right"></slot>
+				</view>
+			</view>
+		</view>
+		<!-- 解决fixed定位后导航栏塌陷的问题 -->
+		<view class="u-navbar-placeholder" v-if="isFixed && !immersive" :style="{ width: '100%', height: Number(navbarHeight) + statusBarHeight + 'px' }"></view>
+	</view>
+</template>
+
+<script>
+	// 获取系统状态栏的高度
+	let systemInfo = uni.getSystemInfoSync();
+	let menuButtonInfo = {};
+	// 如果是小程序,获取右上角胶囊的尺寸信息,避免导航栏右侧内容与胶囊重叠(支付宝小程序非本API,尚未兼容)
+	// #ifdef MP-WEIXIN || MP-BAIDU || MP-TOUTIAO || MP-QQ
+	menuButtonInfo = uni.getMenuButtonBoundingClientRect();
+	// #endif
+	/**
+	 * navbar 自定义导航栏
+	 * @description 此组件一般用于在特殊情况下,需要自定义导航栏的时候用到,一般建议使用uniapp自带的导航栏。
+	 * @tutorial https://www.uviewui.com/components/navbar.html
+	 * @property {String Number} height 导航栏高度(不包括状态栏高度在内,内部自动加上),注意这里的单位是px(默认44)
+	 * @property {String} back-icon-color 左边返回图标的颜色(默认#606266)
+	 * @property {String} back-icon-name 左边返回图标的名称,只能为uView自带的图标(默认arrow-left)
+	 * @property {String Number} back-icon-size 左边返回图标的大小,单位rpx(默认30)
+	 * @property {String} back-text 返回图标右边的辅助提示文字
+	 * @property {Object} back-text-style 返回图标右边的辅助提示文字的样式,对象形式(默认{ color: '#606266' })
+	 * @property {String} title 导航栏标题,如设置为空字符,将会隐藏标题占位区域
+	 * @property {String Number} title-width 导航栏标题的最大宽度,内容超出会以省略号隐藏,单位rpx(默认250)
+	 * @property {String} title-color 标题的颜色(默认#606266)
+	 * @property {String Number} title-size 导航栏标题字体大小,单位rpx(默认32)
+	 * @property {Function} custom-back 自定义返回逻辑方法
+	 * @property {String Number} z-index 固定在顶部时的z-index值(默认980)
+	 * @property {Boolean} is-back 是否显示导航栏左边返回图标和辅助文字(默认true)
+	 * @property {Object} background 导航栏背景设置,见官网说明(默认{ background: '#ffffff' })
+	 * @property {Boolean} is-fixed 导航栏是否固定在顶部(默认true)
+	 * @property {Boolean} immersive 沉浸式,允许fixed定位后导航栏塌陷,仅fixed定位下生效(默认false)
+	 * @property {Boolean} border-bottom 导航栏底部是否显示下边框,如定义了较深的背景颜色,可取消此值(默认true)
+	 * @example <u-navbar back-text="返回" title="剑未配妥,出门已是江湖"></u-navbar>
+	 */
+	export default {
+		name: "u-navbar",
+		props: {
+			// 导航栏高度,单位px,非rpx
+			height: {
+				type: [String, Number],
+				default: ''
+			},
+			// 返回箭头的颜色
+			backIconColor: {
+				type: String,
+				default: '#606266'
+			},
+			// 左边返回的图标
+			backIconName: {
+				type: String,
+				default: 'nav-back'
+			},
+			// 左边返回图标的大小,rpx
+			backIconSize: {
+				type: [String, Number],
+				default: '44'
+			},
+			// 返回的文字提示
+			backText: {
+				type: String,
+				default: ''
+			},
+			// 返回的文字的 样式
+			backTextStyle: {
+				type: Object,
+				default () {
+					return {
+						color: '#606266'
+					}
+				}
+			},
+			// 导航栏标题
+			title: {
+				type: String,
+				default: ''
+			},
+			// 标题的宽度,如果需要自定义右侧内容,且右侧内容很多时,可能需要减少这个宽度,单位rpx
+			titleWidth: {
+				type: [String, Number],
+				default: '300'
+			},
+			// 标题的颜色
+			titleColor: {
+				type: String,
+				default: '#606266'
+			},
+			// 标题字体是否加粗
+			titleBold: {
+				type: Boolean,
+				default: false
+			},
+			// 标题的字体大小
+			titleSize: {
+				type: [String, Number],
+				default: 36
+			},
+			isBack: {
+				type: [Boolean, String],
+				default: true
+			},
+			// 对象形式,因为用户可能定义一个纯色,或者线性渐变的颜色
+			background: {
+				type: Object,
+				default () {
+					return {
+						background: '#ffffff'
+					}
+				}
+			},
+			// 导航栏是否固定在顶部
+			isFixed: {
+				type: Boolean,
+				default: true
+			},
+			// 是否沉浸式,允许fixed定位后导航栏塌陷,仅fixed定位下生效
+			immersive: {
+				type: Boolean,
+				default: false
+			},
+			// 是否显示导航栏的下边框
+			borderBottom: {
+				type: Boolean,
+				default: true
+			},
+			zIndex: {
+				type: [String, Number],
+				default: ''
+			},
+			// 自定义返回逻辑
+			customBack: {
+				type: Function,
+				default: null
+			},
+		},
+		data() {
+			return {
+				menuButtonInfo: menuButtonInfo,
+				statusBarHeight: systemInfo.statusBarHeight
+			};
+		},
+		computed: {
+			// 导航栏内部盒子的样式
+			navbarInnerStyle() {
+				let style = {};
+				// 导航栏宽度,如果在小程序下,导航栏宽度为胶囊的左边到屏幕左边的距离
+				style.height = this.navbarHeight + 'px';
+				// // 如果是各家小程序,导航栏内部的宽度需要减少右边胶囊的宽度
+				// #ifdef MP
+				let rightButtonWidth = systemInfo.windowWidth - menuButtonInfo.left;
+				style.marginRight = rightButtonWidth + 'px';
+				// #endif
+				return style;
+			},
+			// 整个导航栏的样式
+			navbarStyle() {
+				let style = {};
+				style.zIndex = this.zIndex ? this.zIndex : 980;
+				// 合并用户传递的背景色对象
+				Object.assign(style, this.background);
+				return style;
+			},
+			// 导航中间的标题的样式
+			titleStyle() {
+				let style = {};
+				// #ifndef MP
+				style.left = (systemInfo.windowWidth - uni.upx2px(this.titleWidth)) / 2 + 'px';
+				style.right = (systemInfo.windowWidth - uni.upx2px(this.titleWidth)) / 2 + 'px';
+				// #endif
+				// #ifdef MP
+				// 此处是为了让标题显示区域即使在小程序有右侧胶囊的情况下也能处于屏幕的中间,是通过绝对定位实现的
+				let rightButtonWidth = systemInfo.windowWidth - menuButtonInfo.left;
+				style.left = (systemInfo.windowWidth - uni.upx2px(this.titleWidth)) / 2 + 'px';
+				style.right = rightButtonWidth - (systemInfo.windowWidth - uni.upx2px(this.titleWidth)) / 2 + rightButtonWidth +
+					'px';
+				// #endif
+				style.width = uni.upx2px(this.titleWidth) + 'px';
+				return style;
+			},
+			// 转换字符数值为真正的数值
+			navbarHeight() {
+				// #ifdef APP-PLUS || H5
+				return this.height ? this.height : 44;
+				// #endif
+				// #ifdef MP
+				// 小程序特别处理,让导航栏高度 = 胶囊高度 + 两倍胶囊顶部与状态栏底部的距离之差(相当于同时获得了导航栏底部与胶囊底部的距离)
+				// 此方法有缺陷,暂不用(会导致少了几个px),采用直接固定值的方式
+				// return menuButtonInfo.height + (menuButtonInfo.top - this.statusBarHeight) * 2;//导航高度
+				let height = systemInfo.platform == 'ios' ? 44 : 48;
+				return this.height ? this.height : height;
+				// #endif
+			}
+		},
+		created() {},
+		methods: {
+			goBack() {
+				uni.navigateBack();
+				// 如果自定义了点击返回按钮的函数,则执行,否则执行返回逻辑
+				// if (typeof this.customBack === 'function') {
+				// 	// 在微信,支付宝等环境(H5正常),会导致父组件定义的customBack()函数体中的this变成子组件的this
+				// 	// 通过bind()方法,绑定父组件的this,让this.customBack()的this为父组件的上下文
+				// 	this.customBack.bind(this.$u.$parent.call(this))();
+				// } else {
+				// 	uni.navigateBack();
+				// }
+			}
+		}
+	};
+</script>
+
+<style scoped lang="scss">
+	@mixin vue-flex($direction: row) {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		flex-direction: $direction;
+		/* #endif */
+	}
+
+	.u-navbar {
+		width: 100%;
+	}
+
+	.u-navbar-fixed {
+		position: fixed;
+		left: 0;
+		right: 0;
+		top: 0;
+		z-index: 991;
+	}
+
+	.u-status-bar {
+		width: 100%;
+	}
+
+	.u-navbar-inner {
+		@include vue-flex;
+		justify-content: space-between;
+		position: relative;
+		align-items: center;
+	}
+
+	.u-back-wrap {
+		@include vue-flex;
+		align-items: center;
+		flex: 1;
+		flex-grow: 0;
+		padding: 0rpx 12rpx 0rpx 20rpx;
+	}
+
+	.u-back-text {
+		padding-left: 4rpx;
+		font-size: 30rpx;
+	}
+
+	.u-navbar-content-title {
+		@include vue-flex;
+		align-items: center;
+		justify-content: center;
+		flex: 1;
+		position: absolute;
+		left: 0;
+		right: 0;
+		height: 60rpx;
+		text-align: center;
+		flex-shrink: 0;
+	}
+
+	.u-navbar-centent-slot {
+		flex: 1;
+	}
+
+	.u-title {
+		line-height: 60rpx;
+		font-size: 32rpx;
+		flex: 1;
+		overflow: hidden;
+		text-overflow: ellipsis;
+		-webkit-line-clamp: 1;
+		word-wrap: break-word;
+		-webkit-box-orient: vertical;
+		display: -webkit-box;
+	}
+
+	.u-navbar-right {
+		flex: 1;
+		@include vue-flex;
+		align-items: center;
+		justify-content: flex-end;
+	}
+	
+	.u-navbar-left {
+		flex: 1;
+		@include vue-flex;
+		align-items: center;
+		justify-content: flex-start;
+	}
+	
+	.u-slot-content {
+		flex: 1;
+		@include vue-flex;
+		align-items: center;
+	}
+	.u-slot-right{
+		
+	}
+</style>

+ 676 - 0
uview-ui/components/u-picker/u-picker.vue

@@ -0,0 +1,676 @@
+<template>
+	<u-popup :maskCloseAble="maskCloseAble" mode="bottom" :popup="false" v-model="value" length="auto" :safeAreaInsetBottom="safeAreaInsetBottom" @close="close" :z-index="uZIndex">
+		<view class="u-datetime-picker">
+			<view class="u-picker-header" @touchmove.stop.prevent="">
+				<view class="u-btn-picker u-btn-picker--tips" 
+					:style="{ color: cancelColor }" 
+					hover-class="u-opacity" 
+					:hover-stay-time="150" 
+					@tap="getResult('cancel')"
+				>{{cancelText}}</view>
+				<view class="u-picker__title">{{ title }}</view>
+				<view
+					class="u-btn-picker u-btn-picker--primary"
+					:style="{ color: moving ? cancelColor : confirmColor }"
+					hover-class="u-opacity"
+					:hover-stay-time="150"
+					@touchmove.stop=""
+					@tap.stop="getResult('confirm')"
+				>
+					{{confirmText}}
+				</view>
+			</view>
+			<view class="u-picker-body">
+				<picker-view v-if="mode == 'region'" :value="valueArr" @change="change" class="u-picker-view" @pickstart="pickstart" @pickend="pickend">
+					<picker-view-column v-if="!reset && params.province">
+						<view class="u-column-item" v-for="(item, index) in provinces" :key="index">
+							<view class="u-line-1">{{ item.label }}</view>
+						</view>
+					</picker-view-column>
+					<picker-view-column v-if="!reset && params.city">
+						<view class="u-column-item" v-for="(item, index) in citys" :key="index">
+							<view class="u-line-1">{{ item.label }}</view>
+						</view>
+					</picker-view-column>
+					<picker-view-column v-if="!reset && params.area">
+						<view class="u-column-item" v-for="(item, index) in areas" :key="index">
+							<view class="u-line-1">{{ item.label }}</view>
+						</view>
+					</picker-view-column>
+				</picker-view>
+				<picker-view v-else-if="mode == 'time'" :value="valueArr" @change="change" class="u-picker-view" @pickstart="pickstart" @pickend="pickend">
+					<picker-view-column v-if="!reset && params.year">
+						<view class="u-column-item" v-for="(item, index) in years" :key="index">
+							{{ item }}
+							<text class="u-text" v-if="showTimeTag">年</text>
+						</view>
+					</picker-view-column>
+					<picker-view-column v-if="!reset && params.month">
+						<view class="u-column-item" v-for="(item, index) in months" :key="index">
+							{{ formatNumber(item) }}
+							<text class="u-text" v-if="showTimeTag">月</text>
+						</view>
+					</picker-view-column>
+					<picker-view-column v-if="!reset && params.day">
+						<view class="u-column-item" v-for="(item, index) in days" :key="index">
+							{{ formatNumber(item) }}
+							<text class="u-text" v-if="showTimeTag">日</text>
+						</view>
+					</picker-view-column>
+					<picker-view-column v-if="!reset && params.hour">
+						<view class="u-column-item" v-for="(item, index) in hours" :key="index">
+							{{ formatNumber(item) }}
+							<text class="u-text" v-if="showTimeTag">时</text>
+						</view>
+					</picker-view-column>
+					<picker-view-column v-if="!reset && params.minute">
+						<view class="u-column-item" v-for="(item, index) in minutes" :key="index">
+							{{ formatNumber(item) }}
+							<text class="u-text" v-if="showTimeTag">分</text>
+						</view>
+					</picker-view-column>
+					<picker-view-column v-if="!reset && params.second">
+						<view class="u-column-item" v-for="(item, index) in seconds" :key="index">
+							{{ formatNumber(item) }}
+							<text class="u-text" v-if="showTimeTag">秒</text>
+						</view>
+					</picker-view-column>
+				</picker-view>
+				<picker-view v-else-if="mode == 'selector'" :value="valueArr" @change="change" class="u-picker-view" @pickstart="pickstart" @pickend="pickend">
+					<picker-view-column v-if="!reset">
+						<view class="u-column-item" v-for="(item, index) in range" :key="index">
+							<view class="u-line-1">{{ getItemValue(item, 'selector') }}</view>
+						</view>
+					</picker-view-column>
+				</picker-view>
+				<picker-view v-else-if="mode == 'multiSelector'" :value="valueArr" @change="change" class="u-picker-view" @pickstart="pickstart" @pickend="pickend">
+					<picker-view-column v-if="!reset" v-for="(item, index) in range" :key="index">
+						<view class="u-column-item" v-for="(item1, index1) in item" :key="index1">
+							<view class="u-line-1">{{ getItemValue(item1, 'multiSelector') }}</view>
+						</view>
+					</picker-view-column>
+				</picker-view>
+			</view>
+		</view>
+	</u-popup>
+</template>
+
+<script>
+import provinces from '../../libs/util/province.js';
+import citys from '../../libs/util/city.js';
+import areas from '../../libs/util/area.js';
+
+/**
+ * picker picker弹出选择器
+ * @description 此选择器有两种弹出模式:一是时间模式,可以配置年,日,月,时,分,秒参数 二是地区模式,可以配置省,市,区参数
+ * @tutorial https://www.uviewui.com/components/picker.html
+ * @property {Object} params 需要显示的参数,见官网说明
+ * @property {String} mode 模式选择,region-地区类型,time-时间类型(默认time)
+ * @property {String Number} start-year 可选的开始年份,mode=time时有效(默认1950)
+ * @property {String Number} end-year 可选的结束年份,mode=time时有效(默认2050)
+ * @property {Boolean} safe-area-inset-bottom 是否开启底部安全区适配(默认false)
+ * @property {Boolean} show-time-tag 时间模式时,是否显示后面的年月日中文提示
+ * @property {String} cancel-color 取消按钮的颜色(默认#606266)
+ * @property {String} confirm-color 确认按钮的颜色(默认#2979ff)
+ * @property {String} default-time 默认选中的时间,mode=time时有效
+ * @property {String} confirm-text 确认按钮的文字
+ * @property {String} cancel-text 取消按钮的文字
+ * @property {String} default-region 默认选中的地区,中文形式,mode=region时有效
+ * @property {String} default-code 默认选中的地区,编号形式,mode=region时有效
+ * @property {Boolean} mask-close-able 是否允许通过点击遮罩关闭Picker(默认true)
+ * @property {String Number} z-index 弹出时的z-index值(默认1075)
+ * @property {Array} default-selector 数组形式,其中每一项表示选择了range对应项中的第几个
+ * @property {Array} range 自定义选择的数据,mode=selector或mode=multiSelector时有效
+ * @property {String} range-key 当range参数的元素为对象时,指定Object中的哪个key的值作为选择器显示内容
+ * @event {Function} confirm 点击确定按钮,返回当前选择的值
+ * @event {Function} cancel 点击取消按钮,返回当前选择的值
+ * @example <u-picker v-model="show" mode="time"></u-picker>
+ */
+export default {
+	name: 'u-picker',
+	props: {
+		// picker中需要显示的参数
+		params: {
+			type: Object,
+			default() {
+				return {
+					year: true,
+					month: true,
+					day: true,
+					hour: false,
+					minute: false,
+					second: false,
+					province: true,
+					city: true,
+					area: true,
+					timestamp: true,
+				};
+			}
+		},
+		// 当mode=selector或者mode=multiSelector时,提供的数组
+		range: {
+			type: Array,
+			default() {
+				return [];
+			}
+		},
+		// 当mode=selector或者mode=multiSelector时,提供的默认选中的下标
+		defaultSelector: {
+			type: Array,
+			default() {
+				return [0];
+			}
+		},
+		// 当 range 是一个 Array<Object> 时,通过 range-key 来指定 Object 中 key 的值作为选择器显示内容
+		rangeKey: {
+			type: String,
+			default: ''
+		},
+		// 模式选择,region-地区类型,time-时间类型,selector-单列模式,multiSelector-多列模式
+		mode: {
+			type: String,
+			default: 'time'
+		},
+		// 年份开始时间
+		startYear: {
+			type: [String, Number],
+			default: 1950
+		},
+		// 年份结束时间
+		endYear: {
+			type: [String, Number],
+			default: 2050
+		},
+		// "取消"按钮的颜色
+		cancelColor: {
+			type: String,
+			default: '#606266'
+		},
+		// "确定"按钮的颜色
+		confirmColor: {
+			type: String,
+			default: '#2979ff'
+		},
+		// 默认显示的时间,2025-07-02 || 2025-07-02 13:01:00 || 2025/07/02
+		defaultTime: {
+			type: String,
+			default: ''
+		},
+		// 默认显示的地区,可传类似["河北省", "秦皇岛市", "北戴河区"]
+		defaultRegion: {
+			type: Array,
+			default() {
+				return [];
+			}
+		},
+		// 时间模式时,是否显示后面的年月日中文提示
+		showTimeTag: {
+			type: Boolean,
+			default: true
+		},
+		// 默认显示地区的编码,defaultRegion和areaCode同时存在,areaCode优先,可传类似["13", "1303", "130304"]
+		areaCode: {
+			type: Array,
+			default() {
+				return [];
+			}
+		},
+		safeAreaInsetBottom: {
+			type: Boolean,
+			default: false
+		},
+		// 是否允许通过点击遮罩关闭Picker
+		maskCloseAble: {
+			type: Boolean,
+			default: true
+		},
+		// 通过双向绑定控制组件的弹出与收起
+		value: {
+			type: Boolean,
+			default: false
+		},
+		// 弹出的z-index值
+		zIndex: {
+			type: [String, Number],
+			default: 0
+		},
+		// 顶部标题
+		title: {
+			type: String,
+			default: ''
+		},
+		// 取消按钮的文字
+		cancelText: {
+			type: String,
+			default: '取消'
+		},
+		// 确认按钮的文字
+		confirmText: {
+			type: String,
+			default: '确认'
+		}
+	},
+	data() {
+		return {
+			years: [],
+			months: [],
+			days: [],
+			hours: [],
+			minutes: [],
+			seconds: [],
+			year: 0,
+			month: 0,
+			day: 0,
+			hour: 0,
+			minute: 0,
+			second: 0,
+			reset: false,
+			startDate: '',
+			endDate: '',
+			valueArr: [],
+			provinces: provinces,
+			citys: citys[0],
+			areas: areas[0][0],
+			province: 0,
+			city: 0,
+			area: 0,
+			moving: false // 列是否还在滑动中,微信小程序如果在滑动中就点确定,结果可能不准确
+		};
+	},
+	mounted() {
+		this.init();
+	},
+	computed: {
+		propsChange() {
+			// 引用这几个变量,是为了监听其变化
+			return `${this.mode}-${this.defaultTime}-${this.startYear}-${this.endYear}-${this.defaultRegion}-${this.areaCode}`;
+		},
+		regionChange() {
+			// 引用这几个变量,是为了监听其变化
+			return `${this.province}-${this.city}`;
+		},
+		yearAndMonth() {
+			return `${this.year}-${this.month}`;
+		},
+		uZIndex() {
+			// 如果用户有传递z-index值,优先使用
+			return this.zIndex ? this.zIndex : this.$u.zIndex.popup;
+		}
+	},
+	watch: {
+		propsChange() {
+			this.reset = true;
+			setTimeout(() => this.init(), 10);
+		},
+		// 如果地区发生变化,为了让picker联动起来,必须重置this.citys和this.areas
+		regionChange(val) {
+			this.citys = citys[this.province];
+			this.areas = areas[this.province][this.city];
+		},
+		// watch监听月份的变化,实时变更日的天数,因为不同月份,天数不一样
+		// 一个月可能有30,31天,甚至闰年2月的29天,平年2月28天
+		yearAndMonth(val) {
+			if (this.params.year) this.setDays();
+		},
+		// 微信和QQ小程序由于一些奇怪的原因(故同时对所有平台均初始化一遍),需要重新初始化才能显示正确的值
+		value(n) {
+			if (n) {
+				this.reset = true;
+				setTimeout(() => this.init(), 10);
+			}
+		}
+	},
+	methods: {
+		// 标识滑动开始,只有微信小程序才有这样的事件
+		pickstart() {
+			// #ifdef MP-WEIXIN
+			this.moving = true;
+			// #endif
+		},
+		// 标识滑动结束
+		pickend() {
+			// #ifdef MP-WEIXIN
+			this.moving = false;
+			// #endif
+		},
+		// 对单列和多列形式的判断是否有传入变量的情况
+		getItemValue(item, mode) {
+			// 目前(2020-05-25)uni-app对微信小程序编译有错误,导致v-if为false中的内容也执行,错误导致
+			// 单列模式或者多列模式中的getItemValue同时被执行,故在这里再加一层判断
+			if (this.mode == mode) {
+				return typeof item == 'object' ? item[this.rangeKey] : item;
+			}
+		},
+		// 小于10前面补0,用于月份,日期,时分秒等
+		formatNumber(num) {
+			return +num < 10 ? '0' + num : String(num);
+		},
+		// 生成递进的数组
+		generateArray: function(start, end) {
+			// 转为数值格式,否则用户给end-year等传递字符串值时,下面的end+1会导致字符串拼接,而不是相加
+			start = Number(start);
+			end = Number(end);
+			end = end > start ? end : start;
+			// 生成数组,获取其中的索引,并剪出来
+			return [...Array(end + 1).keys()].slice(start);
+		},
+		getIndex: function(arr, val) {
+			let index = arr.indexOf(val);
+			// 如果index为-1(即找不到index值),~(-1)=-(-1)-1=0,导致条件不成立
+			return ~index ? index : 0;
+		},
+		//日期时间处理
+		initTimeValue() {
+			// 格式化时间,在IE浏览器(uni不存在此情况),无法识别日期间的"-"间隔符号
+			let fdate = this.defaultTime.replace(/\-/g, '/');
+			fdate = fdate && fdate.indexOf('/') == -1 ? `2020/01/01 ${fdate}` : fdate;
+			let time = null;
+			if (fdate) time = new Date(fdate);
+			else time = new Date();
+			// 获取年日月时分秒
+			this.year = time.getFullYear();
+			this.month = Number(time.getMonth()) + 1;
+			this.day = time.getDate();
+			this.hour = time.getHours();
+			this.minute = time.getMinutes();
+			this.second = time.getSeconds();
+		},
+		init() {
+			this.valueArr = [];
+			this.reset = false;
+			if (this.mode == 'time') {
+				this.initTimeValue();
+				if (this.params.year) {
+					this.valueArr.push(0);
+					this.setYears();
+				}
+				if (this.params.month) {
+					this.valueArr.push(0);
+					this.setMonths();
+				}
+				if (this.params.day) {
+					this.valueArr.push(0);
+					this.setDays();
+				}
+				if (this.params.hour) {
+					this.valueArr.push(0);
+					this.setHours();
+				}
+				if (this.params.minute) {
+					this.valueArr.push(0);
+					this.setMinutes();
+				}
+				if (this.params.second) {
+					this.valueArr.push(0);
+					this.setSeconds();
+				}
+			} else if (this.mode == 'region') {
+				if (this.params.province) {
+					this.valueArr.push(0);
+					this.setProvinces();
+				}
+				if (this.params.city) {
+					this.valueArr.push(0);
+					this.setCitys();
+				}
+				if (this.params.area) {
+					this.valueArr.push(0);
+					this.setAreas();
+				}
+			} else if (this.mode == 'selector') {
+				this.valueArr = this.defaultSelector;
+			} else if (this.mode == 'multiSelector') {
+				this.valueArr = this.defaultSelector;
+				this.multiSelectorValue = this.defaultSelector;
+			}
+			this.$forceUpdate();
+		},
+		// 设置picker的某一列值
+		setYears() {
+			// 获取年份集合
+			this.years = this.generateArray(this.startYear, this.endYear);
+			// 设置this.valueArr某一项的值,是为了让picker预选中某一个值
+			this.valueArr.splice(this.valueArr.length - 1, 1, this.getIndex(this.years, this.year));
+		},
+		setMonths() {
+			this.months = this.generateArray(1, 12);
+			this.valueArr.splice(this.valueArr.length - 1, 1, this.getIndex(this.months, this.month));
+		},
+		setDays() {
+			let totalDays = new Date(this.year, this.month, 0).getDate();
+			this.days = this.generateArray(1, totalDays);
+			let index = 0;
+			// 这里不能使用类似setMonths()中的this.valueArr.splice(this.valueArr.length - 1, xxx)做法
+			// 因为this.month和this.year变化时,会触发watch中的this.setDays(),导致this.valueArr.length计算有误
+			if (this.params.year && this.params.month) index = 2;
+			else if (this.params.month) index = 1;
+			else if (this.params.year) index = 1;
+			else index = 0;
+			// 当月份变化时,会导致日期的天数也会变化,如果原来选的天数大于变化后的天数,则重置为变化后的最大值
+			// 比如原来选中3月31日,调整为2月后,日期变为最大29,这时如果day值继续为31显然不合理,于是将其置为29(picker-column从1开始)
+			if(this.day > this.days.length) this.day = this.days.length;
+			this.valueArr.splice(index, 1, this.getIndex(this.days, this.day));
+		},
+		setHours() {
+			this.hours = this.generateArray(0, 23);
+			this.valueArr.splice(this.valueArr.length - 1, 1, this.getIndex(this.hours, this.hour));
+		},
+		setMinutes() {
+			this.minutes = this.generateArray(0, 59);
+			this.valueArr.splice(this.valueArr.length - 1, 1, this.getIndex(this.minutes, this.minute));
+		},
+		setSeconds() {
+			this.seconds = this.generateArray(0, 59);
+			this.valueArr.splice(this.valueArr.length - 1, 1, this.getIndex(this.seconds, this.second));
+		},
+		setProvinces() {
+			// 判断是否需要province参数
+			if (!this.params.province) return;
+			let tmp = '';
+			let useCode = false;
+			// 如果同时配置了defaultRegion和areaCode,优先使用areaCode参数
+			if (this.areaCode.length) {
+				tmp = this.areaCode[0];
+				useCode = true;
+			} else if (this.defaultRegion.length) tmp = this.defaultRegion[0];
+			else tmp = 0;
+			// 历遍省份数组匹配
+			provinces.map((v, k) => {
+				if (useCode ? v.value == tmp : v.label == tmp) {
+					tmp = k;
+				}
+			});
+			this.province = tmp;
+			this.provinces = provinces;
+			// 设置默认省份的值
+			this.valueArr.splice(0, 1, this.province);
+		},
+		setCitys() {
+			if (!this.params.city) return;
+			let tmp = '';
+			let useCode = false;
+			if (this.areaCode.length) {
+				tmp = this.areaCode[1];
+				useCode = true;
+			} else if (this.defaultRegion.length) tmp = this.defaultRegion[1];
+			else tmp = 0;
+			citys[this.province].map((v, k) => {
+				if (useCode ? v.value == tmp : v.label == tmp) {
+					tmp = k;
+				}
+			});
+			this.city = tmp;
+			this.citys = citys[this.province];
+			this.valueArr.splice(1, 1, this.city);
+		},
+		setAreas() {
+			if (!this.params.area) return;
+			let tmp = '';
+			let useCode = false;
+			if (this.areaCode.length) {
+				tmp = this.areaCode[2];
+				useCode = true;
+			} else if (this.defaultRegion.length) tmp = this.defaultRegion[2];
+			else tmp = 0;
+			areas[this.province][this.city].map((v, k) => {
+				if (useCode ? v.value == tmp : v.label == tmp) {
+					tmp = k;
+				}
+			});
+			this.area = tmp;
+			this.areas = areas[this.province][this.city];
+			this.valueArr.splice(2, 1, this.area);
+		},
+		close() {
+			this.$emit('input', false);
+		},
+		// 用户更改picker的列选项
+		change(e) {
+			this.valueArr = e.detail.value;
+			let i = 0;
+			if (this.mode == 'time') {
+				// 这里使用i++,是因为this.valueArr数组的长度是不确定长度的,它根据this.params的值来配置长度
+				// 进入if规则,i会加1,保证了能获取准确的值
+				if (this.params.year) this.year = this.years[this.valueArr[i++]];
+				if (this.params.month) this.month = this.months[this.valueArr[i++]];
+				if (this.params.day) this.day = this.days[this.valueArr[i++]];
+				if (this.params.hour) this.hour = this.hours[this.valueArr[i++]];
+				if (this.params.minute) this.minute = this.minutes[this.valueArr[i++]];
+				if (this.params.second) this.second = this.seconds[this.valueArr[i++]];
+			} else if (this.mode == 'region') {
+				if (this.params.province) this.province = this.valueArr[i++];
+				if (this.params.city) this.city = this.valueArr[i++];
+				if (this.params.area) this.area = this.valueArr[i++];
+			} else if (this.mode == 'multiSelector') {
+				let index = null;
+				// 对比前后两个数组,寻找变更的是哪一列,如果某一个元素不同,即可判定该列发生了变化
+				this.defaultSelector.map((val, idx) => {
+					if (val != e.detail.value[idx]) index = idx;
+				});
+				// 为了让用户对多列变化时,对动态设置其他列的变更
+				if (index != null) {
+					this.$emit('columnchange', {
+						column: index,
+						index: e.detail.value[index]
+					});
+				}
+			}
+		},
+		// 用户点击确定按钮
+		getResult(event = null) {
+			// #ifdef MP-WEIXIN
+			if (this.moving) return;
+			// #endif
+			let result = {};
+			// 只返回用户在this.params中配置了为true的字段
+			if (this.mode == 'time') {
+				if (this.params.year) result.year = this.formatNumber(this.year || 0);
+				if (this.params.month) result.month = this.formatNumber(this.month || 0);
+				if (this.params.day) result.day = this.formatNumber(this.day || 0);
+				if (this.params.hour) result.hour = this.formatNumber(this.hour || 0);
+				if (this.params.minute) result.minute = this.formatNumber(this.minute || 0);
+				if (this.params.second) result.second = this.formatNumber(this.second || 0);
+				if (this.params.timestamp) result.timestamp = this.getTimestamp();
+			} else if (this.mode == 'region') {
+				if (this.params.province) result.province = provinces[this.province];
+				if (this.params.city) result.city = citys[this.province][this.city];
+				if (this.params.area) result.area = areas[this.province][this.city][this.area];
+			} else if (this.mode == 'selector') {
+				result = this.valueArr;
+			} else if (this.mode == 'multiSelector') {
+				result = this.valueArr;
+			}
+			if (event) this.$emit(event, result);
+			this.close();
+		},
+		// 获取时间戳
+		getTimestamp() {
+			// yyyy-mm-dd为安卓写法,不支持iOS,需要使用"/"分隔,才能二者兼容
+			let time = this.year + '/' + this.month + '/' + this.day + ' ' + this.hour + ':' + this.minute + ':' + this.second;
+			return new Date(time).getTime() / 1000;
+		}
+	}
+};
+</script>
+
+<style lang="scss" scoped>
+@import '../../libs/css/style.components.scss';
+
+.u-datetime-picker {
+	position: relative;
+	z-index: 999;
+}
+
+.u-picker-view {
+	height: 100%;
+	box-sizing: border-box;
+}
+
+.u-picker-header {
+	width: 100%;
+	height: 90rpx;
+	padding: 0 40rpx;
+	@include vue-flex;
+	justify-content: space-between;
+	align-items: center;
+	box-sizing: border-box;
+	font-size: 30rpx;
+	background: #fff;
+	position: relative;
+}
+
+.u-picker-header::after {
+	content: '';
+	position: absolute;
+	border-bottom: 1rpx solid #eaeef1;
+	-webkit-transform: scaleY(0.5);
+	transform: scaleY(0.5);
+	bottom: 0;
+	right: 0;
+	left: 0;
+}
+
+.u-picker__title {
+	color: $u-content-color;
+}
+
+.u-picker-body {
+	width: 100%;
+	height: 500rpx;
+	overflow: hidden;
+	background-color: #fff;
+}
+
+.u-column-item {
+	@include vue-flex;
+	align-items: center;
+	justify-content: center;
+	font-size: 32rpx;
+	color: $u-main-color;
+	padding: 0 8rpx;
+}
+
+.u-text {
+	font-size: 24rpx;
+	padding-left: 8rpx;
+}
+
+.u-btn-picker {
+	padding: 16rpx;
+	box-sizing: border-box;
+	text-align: center;
+	text-decoration: none;
+}
+
+.u-opacity {
+	opacity: 0.5;
+}
+
+.u-btn-picker--primary {
+	color: $u-type-primary;
+}
+
+.u-btn-picker--tips {
+	color: $u-tips-color;
+}
+</style>

+ 456 - 0
uview-ui/components/u-popup/u-popup.vue

@@ -0,0 +1,456 @@
+<template>
+	<view v-if="visibleSync" :style="[customStyle, {
+		zIndex: uZindex - 1
+	}]" class="u-drawer" hover-stop-propagation>
+		<u-mask :duration="duration" :custom-style="maskCustomStyle" :maskClickAble="maskCloseAble" :z-index="uZindex - 2" :show="showDrawer && mask" @click="maskClick"></u-mask>
+		<view
+			class="u-drawer-content"
+			@tap="modeCenterClose(mode)"
+			:class="[
+				safeAreaInsetBottom ? 'safe-area-inset-bottom' : '',
+				'u-drawer-' + mode,
+				showDrawer ? 'u-drawer-content-visible' : '',
+				zoom && mode == 'center' ? 'u-animation-zoom' : ''
+			]"
+			@touchmove.stop.prevent
+			@tap.stop.prevent
+			:style="[style]"
+		>
+			<view class="u-mode-center-box" @tap.stop.prevent @touchmove.stop.prevent v-if="mode == 'center'" :style="[centerStyle]">
+				<u-icon
+					@click="close"
+					v-if="closeable"
+					class="u-close"
+					:class="['u-close--' + closeIconPos]"
+					:name="closeIcon"
+					:color="closeIconColor"
+					:size="closeIconSize"
+				></u-icon>
+				<scroll-view class="u-drawer__scroll-view" scroll-y="true">
+					<slot />
+				</scroll-view>
+			</view>
+			<scroll-view class="u-drawer__scroll-view" scroll-y="true" v-else>
+				<slot />
+			</scroll-view>
+			<view @tap="close" class="u-close" :class="['u-close--' + closeIconPos]">
+				<u-icon
+					v-if="mode != 'center' && closeable"
+					:name="closeIcon"
+					:color="closeIconColor"
+					:size="closeIconSize"
+				></u-icon>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+/**
+ * popup 弹窗
+ * @description 弹出层容器,用于展示弹窗、信息提示等内容,支持上、下、左、右和中部弹出。组件只提供容器,内部内容由用户自定义
+ * @tutorial https://www.uviewui.com/components/popup.html
+ * @property {String} mode 弹出方向(默认left)
+ * @property {Boolean} mask 是否显示遮罩(默认true)
+ * @property {Stringr | Number} length mode=left | 见官网说明(默认auto)
+ * @property {Boolean} zoom 是否开启缩放动画,只在mode为center时有效(默认true)
+ * @property {Boolean} safe-area-inset-bottom 是否开启底部安全区适配(默认false)
+ * @property {Boolean} mask-close-able 点击遮罩是否可以关闭弹出层(默认true)
+ * @property {Object} custom-style 用户自定义样式
+ * @property {Stringr | Number} negative-top 中部弹出时,往上偏移的值
+ * @property {Numberr | String} border-radius 弹窗圆角值(默认0)
+ * @property {Numberr | String} z-index 弹出内容的z-index值(默认1075)
+ * @property {Boolean} closeable 是否显示关闭图标(默认false)
+ * @property {String} close-icon 关闭图标的名称,只能uView的内置图标
+ * @property {String} close-icon-pos 自定义关闭图标位置(默认top-right)
+ * @property {String} close-icon-color 关闭图标的颜色(默认#909399)
+ * @property {Number | String} close-icon-size 关闭图标的大小,单位rpx(默认30)
+ * @event {Function} open 弹出层打开
+ * @event {Function} close 弹出层收起
+ * @example <u-popup v-model="show"><view>出淤泥而不染,濯清涟而不妖</view></u-popup>
+ */
+export default {
+	name: 'u-popup',
+	props: {
+		/**
+		 * 显示状态
+		 */
+		show: {
+			type: Boolean,
+			default: false
+		},
+		/**
+		 * 弹出方向,left|right|top|bottom|center
+		 */
+		mode: {
+			type: String,
+			default: 'left'
+		},
+		/**
+		 * 是否显示遮罩
+		 */
+		mask: {
+			type: Boolean,
+			default: true
+		},
+		// 抽屉的宽度(mode=left|right),或者高度(mode=top|bottom),单位rpx,或者"auto"
+		// 或者百分比"50%",表示由内容撑开高度或者宽度
+		length: {
+			type: [Number, String],
+			default: 'auto'
+		},
+		// 是否开启缩放动画,只在mode=center时有效
+		zoom: {
+			type: Boolean,
+			default: true
+		},
+		// 是否开启底部安全区适配,开启的话,会在iPhoneX机型底部添加一定的内边距
+		safeAreaInsetBottom: {
+			type: Boolean,
+			default: false
+		},
+		// 是否可以通过点击遮罩进行关闭
+		maskCloseAble: {
+			type: Boolean,
+			default: true
+		},
+		// 用户自定义样式
+		customStyle: {
+			type: Object,
+			default() {
+				return {};
+			}
+		},
+		value: {
+			type: Boolean,
+			default: false
+		},
+		// 此为内部参数,不在文档对外使用,为了解决Picker和keyboard等融合了弹窗的组件
+		// 对v-model双向绑定多层调用造成报错不能修改props值的问题
+		popup: {
+			type: Boolean,
+			default: true
+		},
+		// 显示显示弹窗的圆角,单位rpx
+		borderRadius: {
+			type: [Number, String],
+			default: 0
+		},
+		zIndex: {
+			type: [Number, String],
+			default: ''
+		},
+		// 是否显示关闭图标
+		closeable: {
+			type: Boolean,
+			default: false
+		},
+		// 关闭图标的名称,只能uView的内置图标
+		closeIcon: {
+			type: String,
+			default: 'close'
+		},
+		// 自定义关闭图标位置,top-left为左上角,top-right为右上角,bottom-left为左下角,bottom-right为右下角
+		closeIconPos: {
+			type: String,
+			default: 'top-right'
+		},
+		// 关闭图标的颜色
+		closeIconColor: {
+			type: String,
+			default: '#909399'
+		},
+		// 关闭图标的大小,单位rpx
+		closeIconSize: {
+			type: [String, Number],
+			default: '30'
+		},
+		// 宽度,只对左,右,中部弹出时起作用,单位rpx,或者"auto"
+		// 或者百分比"50%",表示由内容撑开高度或者宽度,优先级高于length参数
+		width: {
+			type: String,
+			default: ''
+		},
+		// 高度,只对上,下,中部弹出时起作用,单位rpx,或者"auto"
+		// 或者百分比"50%",表示由内容撑开高度或者宽度,优先级高于length参数
+		height: {
+			type: String,
+			default: ''
+		},
+		// 给一个负的margin-top,往上偏移,避免和键盘重合的情况,仅在mode=center时有效
+		negativeTop: {
+			type: [String, Number],
+			default: 0
+		},
+		// 遮罩的样式,一般用于修改遮罩的透明度
+		maskCustomStyle: {
+			type: Object,
+			default() {
+				return {}
+			}
+		},
+		// 遮罩打开或收起的动画过渡时间,单位ms
+		duration: {
+			type: [String, Number],
+			default: 250
+		}
+	},
+	data() {
+		return {
+			visibleSync: false,
+			showDrawer: false,
+			timer: null,
+			closeFromInner: false, // value的值改变,是发生在内部还是外部
+		};
+	},
+	computed: {
+		// 根据mode的位置,设定其弹窗的宽度(mode = left|right),或者高度(mode = top|bottom)
+		style() {
+			let style = {};
+			// 如果是左边或者上边弹出时,需要给translate设置为负值,用于隐藏
+			if (this.mode == 'left' || this.mode == 'right') {
+				style = {
+					width: this.width ? this.getUnitValue(this.width) : this.getUnitValue(this.length),
+					height: '100%',
+					transform: `translate3D(${this.mode == 'left' ? '-100%' : '100%'},0px,0px)`
+				};
+			} else if (this.mode == 'top' || this.mode == 'bottom') {
+				style = {
+					width: '100%',
+					height: this.height ? this.getUnitValue(this.height) : this.getUnitValue(this.length),
+					transform: `translate3D(0px,${this.mode == 'top' ? '-100%' : '100%'},0px)`
+				};
+			}
+			style.zIndex = this.uZindex;
+			// 如果用户设置了borderRadius值,添加弹窗的圆角
+			if (this.borderRadius) {
+				switch (this.mode) {
+					case 'left':
+						style.borderRadius = `0 ${this.borderRadius}rpx ${this.borderRadius}rpx 0`;
+						break;
+					case 'top':
+						style.borderRadius = `0 0 ${this.borderRadius}rpx ${this.borderRadius}rpx`;
+						break;
+					case 'right':
+						style.borderRadius = `${this.borderRadius}rpx 0 0 ${this.borderRadius}rpx`;
+						break;
+					case 'bottom':
+						style.borderRadius = `${this.borderRadius}rpx ${this.borderRadius}rpx 0 0`;
+						break;
+					default:
+				}
+				// 不加可能圆角无效
+				style.overflow = 'hidden';
+			}
+			if(this.duration) style.transition = `all ${this.duration / 1000}s linear`;
+			return style;
+		},
+		// 中部弹窗的特有样式
+		centerStyle() {
+			let style = {};
+			style.width = this.width ? this.getUnitValue(this.width) : this.getUnitValue(this.length);
+			// 中部弹出的模式,如果没有设置高度,就用auto值,由内容撑开高度
+			style.height = this.height ? this.getUnitValue(this.height) : 'auto';
+			style.zIndex = this.uZindex;
+			style.marginTop = `-${this.$u.addUnit(this.negativeTop)}`;
+			if (this.borderRadius) {
+				style.borderRadius = `${this.borderRadius}rpx`;
+				// 不加可能圆角无效
+				style.overflow = 'hidden';
+			}
+			return style;
+		},
+		// 计算整理后的z-index值
+		uZindex() {
+			return this.zIndex ? this.zIndex : this.$u.zIndex.popup;
+		}
+	},
+	watch: {
+		value(val) {
+			if (val) {
+				this.open();
+			} else if(!this.closeFromInner) {
+				this.close();
+			}
+			this.closeFromInner = false;
+		}
+	},
+	mounted() {
+		// 组件渲染完成时,检查value是否为true,如果是,弹出popup
+		this.value && this.open();
+	},
+    methods: {
+		// 判断传入的值,是否带有单位,如果没有,就默认用rpx单位
+		getUnitValue(val) {
+			if(/(%|px|rpx|auto)$/.test(val)) return val;
+			else return val + 'rpx'
+		},
+		// 遮罩被点击
+		maskClick() {
+			this.close();
+		},
+		close() {
+			// 标记关闭是内部发生的,否则修改了value值,导致watch中对value检测,导致再执行一遍close
+			// 造成@close事件触发两次
+			this.closeFromInner = true;
+			this.change('showDrawer', 'visibleSync', false);
+		},
+		// 中部弹出时,需要.u-drawer-content将居中内容,此元素会铺满屏幕,点击需要关闭弹窗
+		// 让其只在mode=center时起作用
+		modeCenterClose(mode) {
+			if (mode != 'center' || !this.maskCloseAble) return;
+			this.close();
+		},
+		open() {
+			this.change('visibleSync', 'showDrawer', true);
+		},
+		// 此处的原理是,关闭时先通过动画隐藏弹窗和遮罩,再移除整个组件
+		// 打开时,先渲染组件,延时一定时间再让遮罩和弹窗的动画起作用
+		change(param1, param2, status) {
+			// 如果this.popup为false,意味着为picker,actionsheet等组件调用了popup组件
+			if (this.popup == true) {
+				this.$emit('input', status);
+			}
+			this[param1] = status;
+			if(status) {
+				// #ifdef H5 || MP
+				this.timer = setTimeout(() => {
+					this[param2] = status;
+					this.$emit(status ? 'open' : 'close');
+				}, 50);
+				// #endif
+				// #ifndef H5 || MP
+				this.$nextTick(() => {
+					this[param2] = status;
+					this.$emit(status ? 'open' : 'close');
+				})
+				// #endif
+			} else {
+				this.timer = setTimeout(() => {
+					this[param2] = status;
+					this.$emit(status ? 'open' : 'close');
+				}, this.duration);
+			}
+		}
+	}
+};
+</script>
+
+<style scoped lang="scss">
+@import "../../libs/css/style.components.scss";
+
+.u-drawer {
+	/* #ifndef APP-NVUE */
+	display: block;
+	/* #endif */
+	position: fixed;
+	top: 0;
+	left: 0;
+	right: 0;
+	bottom: 0;
+	overflow: hidden;
+}
+
+.u-drawer-content {
+	/* #ifndef APP-NVUE */
+	display: block;
+	/* #endif */
+	position: absolute;
+	z-index: 1003;
+	transition: all 0.25s linear;
+}
+
+.u-drawer__scroll-view {
+	width: 100%;
+	height: 100%;
+}
+
+.u-drawer-left {
+	top: 0;
+	bottom: 0;
+	left: 0;
+	background-color: #ffffff;
+}
+
+.u-drawer-right {
+	right: 0;
+	top: 0;
+	bottom: 0;
+	background-color: #ffffff;
+}
+
+.u-drawer-top {
+	top: 0;
+	left: 0;
+	right: 0;
+	background-color: #ffffff;
+}
+
+.u-drawer-bottom {
+	bottom: 0;
+	left: 0;
+	right: 0;
+	background-color: #ffffff;
+}
+
+.u-drawer-center {
+	@include vue-flex;
+	flex-direction: column;
+	bottom: 0;
+	left: 0;
+	right: 0;
+	top: 0;
+	justify-content: center;
+	align-items: center;
+	opacity: 0;
+	z-index: 99999;
+}
+
+.u-mode-center-box {
+	min-width: 100rpx;
+	min-height: 100rpx;
+	/* #ifndef APP-NVUE */
+	display: block;
+	/* #endif */
+	position: relative;
+	background-color: #ffffff;
+}
+
+.u-drawer-content-visible.u-drawer-center {
+	transform: scale(1);
+	opacity: 1;
+}
+
+.u-animation-zoom {
+	transform: scale(1.15);
+}
+
+.u-drawer-content-visible {
+	transform: translate3D(0px, 0px, 0px) !important;
+}
+
+.u-close {
+	position: absolute;
+	z-index: 3;
+}
+
+.u-close--top-left {
+	top: 30rpx;
+	left: 30rpx;
+}
+
+.u-close--top-right {
+	top: 30rpx;
+	right: 30rpx;
+}
+
+.u-close--bottom-left {
+	bottom: 30rpx;
+	left: 30rpx;
+}
+
+.u-close--bottom-right {
+	right: 30rpx;
+	bottom: 30rpx;
+}
+</style>

+ 342 - 0
uview-ui/components/u-search/u-search.vue

@@ -0,0 +1,342 @@
+<template>
+	<view class="u-search" @tap="clickHandler" :style="{
+		margin: margin,
+	}">
+		<view
+			class="u-content"
+			:style="{
+				backgroundColor: bgColor,
+				borderRadius: shape == 'round' ? '100rpx' : '10rpx',
+				border: borderStyle,
+				height: height + 'rpx'
+			}"
+		>
+			<view class="u-icon-wrap">
+				<u-icon class="u-clear-icon" :size="30" :name="searchIcon" :color="searchIconColor ? searchIconColor : color"></u-icon>
+			</view>
+			<input
+				confirm-type="search"
+				@blur="blur"
+				:value="value"
+				@confirm="search"
+				@input="inputChange"
+				:disabled="disabled"
+				@focus="getFocus"
+				:focus="focus"
+				:maxlength="maxlength"
+				placeholder-class="u-placeholder-class"
+				:placeholder="placeholder"
+				:placeholder-style="`color: ${placeholderColor}`"
+				class="u-input"
+				type="text"
+				:style="[{
+					textAlign: inputAlign,
+					color: color,
+					backgroundColor: bgColor,
+				}, inputStyle]"
+			/>
+			<view class="u-close-wrap" v-if="keyword && clearabled && focused" @tap="clear">
+				<u-icon class="u-clear-icon" name="close-circle-fill" size="34" color="#c0c4cc"></u-icon>
+			</view>
+		</view>
+		<view :style="[actionStyle]" class="u-action" 
+			:class="[showActionBtn || show ? 'u-action-active' : '']" 
+			@tap.stop.prevent="custom"
+		>{{ actionText }}</view>
+	</view>
+</template>
+
+<script>
+/**
+ * search 搜索框
+ * @description 搜索组件,集成了常见搜索框所需功能,用户可以一键引入,开箱即用。
+ * @tutorial https://www.uviewui.com/components/search.html
+ * @property {String} shape 搜索框形状,round-圆形,square-方形(默认round)
+ * @property {String} bg-color 搜索框背景颜色(默认#f2f2f2)
+ * @property {String} border-color 边框颜色,配置了颜色,才会有边框
+ * @property {String} placeholder 占位文字内容(默认“请输入关键字”)
+ * @property {Boolean} clearabled 是否启用清除控件(默认true)
+ * @property {Boolean} focus 是否自动获得焦点(默认false)
+ * @property {Boolean} show-action 是否显示右侧控件(默认true)
+ * @property {String} action-text 右侧控件文字(默认“搜索”)
+ * @property {Object} action-style 右侧控件的样式,对象形式
+ * @property {String} input-align 输入框内容水平对齐方式(默认left)
+ * @property {Object} input-style 自定义输入框样式,对象形式
+ * @property {Boolean} disabled 是否启用输入框(默认false)
+ * @property {String} search-icon-color 搜索图标的颜色,默认同输入框字体颜色
+ * @property {String} color 输入框字体颜色(默认#606266)
+ * @property {String} placeholder-color placeholder的颜色(默认#909399)
+ * @property {String} search-icon 输入框左边的图标,可以为uView图标名称或图片路径
+ * @property {String} margin 组件与其他上下左右元素之间的距离,带单位的字符串形式,如"30rpx"
+ * @property {Boolean} animation 是否开启动画,见上方说明(默认false)
+ * @property {String} value 输入框初始值
+ * @property {String | Number} maxlength 输入框最大能输入的长度,-1为不限制长度
+ * @property {Boolean} input-style input输入框的样式,可以定义文字颜色,大小等,对象形式
+ * @property {String | Number} height 输入框高度,单位rpx(默认64)
+ * @event {Function} change 输入框内容发生变化时触发
+ * @event {Function} search 用户确定搜索时触发,用户按回车键,或者手机键盘右下角的"搜索"键时触发
+ * @event {Function} custom 用户点击右侧控件时触发
+ * @event {Function} clear 用户点击清除按钮时触发
+ * @example <u-search placeholder="日照香炉生紫烟" v-model="keyword"></u-search>
+ */
+export default {
+	name: "u-search",
+	props: {
+		// 搜索框形状,round-圆形,square-方形
+		shape: {
+			type: String,
+			default: 'round'
+		},
+		// 搜索框背景色,默认值#f2f2f2
+		bgColor: {
+			type: String,
+			default: '#f2f2f2'
+		},
+		// 占位提示文字
+		placeholder: {
+			type: String,
+			default: '请输入关键字'
+		},
+		// 是否启用清除控件
+		clearabled: {
+			type: Boolean,
+			default: true
+		},
+		// 是否自动聚焦
+		focus: {
+			type: Boolean,
+			default: false
+		},
+		// 是否在搜索框右侧显示取消按钮
+		showAction: {
+			type: Boolean,
+			default: true
+		},
+		// 右边控件的样式
+		actionStyle: {
+			type: Object,
+			default() {
+				return {};
+			}
+		},
+		// 取消按钮文字
+		actionText: {
+			type: String,
+			default: '搜索'
+		},
+		// 输入框内容对齐方式,可选值为 left|center|right
+		inputAlign: {
+			type: String,
+			default: 'left'
+		},
+		// 是否启用输入框
+		disabled: {
+			type: Boolean,
+			default: false
+		},
+		// 开启showAction时,是否在input获取焦点时才显示
+		animation: {
+			type: Boolean,
+			default: false
+		},
+		// 边框颜色,只要配置了颜色,才会有边框
+		borderColor: {
+			type: String,
+			default: 'none'
+		},
+		// 输入框的初始化内容
+		value: {
+			type: String,
+			default: ''
+		},
+		// 搜索框高度,单位rpx
+		height: {
+			type: [Number, String],
+			default: 64
+		},
+		// input输入框的样式,可以定义文字颜色,大小等,对象形式
+		inputStyle: {
+			type: Object,
+			default() {
+				return {}
+			}
+		},
+		// 输入框最大能输入的长度,-1为不限制长度(来自uniapp文档)
+		maxlength: {
+			type: [Number, String],
+			default: '-1'
+		},
+		// 搜索图标的颜色,默认同输入框字体颜色
+		searchIconColor: {
+			type: String,
+			default: ''
+		},
+		// 输入框字体颜色
+		color: {
+			type: String,
+			default: '#606266'
+		},
+		// placeholder的颜色
+		placeholderColor: {
+			type: String,
+			default: '#909399'
+		},
+		// 组件与其他上下左右元素之间的距离,带单位的字符串形式,如"30rpx"、"30rpx 20rpx"等写法
+		margin: {
+			type: String,
+			default: '0'
+		},
+		// 左边输入框的图标,可以为uView图标名称或图片路径
+		searchIcon: {
+			type: String,
+			default: 'search'
+		}
+	},
+	data() {
+		return {
+			keyword: '',
+			showClear: false, // 是否显示右边的清除图标
+			show: false,
+			// 标记input当前状态是否处于聚焦中,如果是,才会显示右侧的清除控件
+			focused: this.focus
+			// 绑定输入框的值
+			// inputValue: this.value
+		};
+	},
+	watch: {
+		keyword(nVal) {
+			// 双向绑定值,让v-model绑定的值双向变化
+			this.$emit('input', nVal);
+			// 触发change事件,事件效果和v-model双向绑定的效果一样,让用户多一个选择
+			this.$emit('change', nVal);
+		},
+		value: {
+			immediate: true,
+			handler(nVal) {
+				this.keyword = nVal;
+			}
+		}
+	},
+	computed: {
+		showActionBtn() {
+			if (!this.animation && this.showAction) return true;
+			else return false;
+		},
+		// 样式,根据用户传入的颜色值生成,如果不传入,默认为none
+		borderStyle() {
+			if (this.borderColor) return `1px solid ${this.borderColor}`;
+			else return 'none';
+		},
+	},
+	methods: {
+		// 目前HX2.6.9 v-model双向绑定无效,故监听input事件获取输入框内容的变化
+		inputChange(e) {
+			this.keyword = e.detail.value;
+		},
+		// 清空输入
+		// 也可以作为用户通过this.$refs形式调用清空输入框内容
+		clear() {
+			this.keyword = '';
+			// 延后发出事件,避免在父组件监听clear事件时,value为更新前的值(不为空)
+			this.$nextTick(() => {
+				this.$emit('clear');
+			})
+		},
+		// 确定搜索
+		search(e) {
+			this.$emit('search', e.detail.value);
+			try{
+				// 收起键盘
+				uni.hideKeyboard();
+			}catch(e){}
+		},
+		// 点击右边自定义按钮的事件
+		custom() {
+			this.$emit('custom', this.keyword);
+			try{
+				// 收起键盘
+				uni.hideKeyboard();
+			}catch(e){}
+		},
+		// 获取焦点
+		getFocus() {
+			this.focused = true;
+			// 开启右侧搜索按钮展开的动画效果
+			if (this.animation && this.showAction) this.show = true;
+			this.$emit('focus', this.keyword);
+		},
+		// 失去焦点
+		blur() {
+			// 最开始使用的是监听图标@touchstart事件,自从hx2.8.4后,此方法在微信小程序出错
+			// 这里改为监听点击事件,手点击清除图标时,同时也发生了@blur事件,导致图标消失而无法点击,这里做一个延时
+			setTimeout(() => {
+				this.focused = false;
+			}, 100)
+			this.show = false;
+			this.$emit('blur', this.keyword);
+		},
+		// 点击搜索框,只有disabled=true时才发出事件,因为禁止了输入,意味着是想跳转真正的搜索页
+		clickHandler() {
+			if(this.disabled) this.$emit('click');
+		}
+	}
+};
+</script>
+
+<style lang="scss" scoped>
+@import "../../libs/css/style.components.scss";
+
+.u-search {
+	@include vue-flex;
+	align-items: center;
+	flex: 1;
+}
+
+.u-content {
+	@include vue-flex;
+	align-items: center;
+	padding: 0 18rpx;
+	flex: 1;
+}
+
+.u-clear-icon {
+	@include vue-flex;
+	align-items: center;
+}
+
+.u-input {
+	flex: 1;
+	font-size: 28rpx;
+	line-height: 1;
+	margin: 0 10rpx;
+	color: $u-tips-color;
+}
+
+.u-close-wrap {
+	width: 40rpx;
+	height: 100%;
+	@include vue-flex;
+	align-items: center;
+	justify-content: center;
+	border-radius: 50%;
+}
+
+.u-placeholder-class {
+	color: $u-tips-color;
+}
+
+.u-action {
+	font-size: 28rpx;
+	color: $u-main-color;
+	width: 0;
+	overflow: hidden;
+	transition: all 0.3s;
+	white-space: nowrap;
+	text-align: center;
+}
+
+.u-action-active {
+	width: 80rpx;
+	margin-left: 10rpx;
+}
+</style>

+ 417 - 0
uview-ui/components/u-select/u-select.vue

@@ -0,0 +1,417 @@
+<template>
+	<view class="u-select">
+		<!-- <view class="u-select__action" :class="{
+			'u-select--border': border
+		}" @tap.stop="selectHandler">
+			<view class="u-select__action__icon" :class="{
+				'u-select__action__icon--reverse': value == true
+			}">
+				<u-icon name="arrow-down-fill" size="26" color="#c0c4cc"></u-icon>
+			</view>
+		</view> -->
+		<u-popup :maskCloseAble="maskCloseAble" mode="bottom" :popup="false" v-model="value" length="auto" :safeAreaInsetBottom="safeAreaInsetBottom" @close="close" :z-index="uZIndex">
+			<view class="u-select">
+				<view class="u-select__header" @touchmove.stop.prevent="">
+					<view
+						class="u-select__header__cancel u-select__header__btn"
+						:style="{ color: cancelColor }"
+						hover-class="u-hover-class"
+						:hover-stay-time="150"
+						@tap="getResult('cancel')"
+					>
+						{{cancelText}}
+					</view>
+					<view class="u-select__header__title">
+						{{title}}
+					</view>
+					<view
+						class="u-select__header__confirm u-select__header__btn"
+						:style="{ color: moving ? cancelColor : confirmColor }"
+						hover-class="u-hover-class"
+						:hover-stay-time="150"
+						@touchmove.stop=""
+						@tap.stop="getResult('confirm')"
+					>
+						{{confirmText}}
+					</view>
+				</view>
+				<view class="u-select__body">
+					<picker-view @change="columnChange" class="u-select__body__picker-view" :value="defaultSelector" @pickstart="pickstart" @pickend="pickend">
+						<picker-view-column v-for="(item, index) in columnData" :key="index">
+							<view class="u-select__body__picker-view__item" v-for="(item1, index1) in item" :key="index1">
+								<view class="u-line-1">{{ item1[labelName] }}</view>
+							</view>
+						</picker-view-column>
+					</picker-view>
+				</view>
+			</view>
+		</u-popup>
+	</view>
+</template>
+
+<script>
+	/**
+	 * select 列选择器
+	 * @description 此选择器用于单列,多列,多列联动的选择场景。(从1.3.0版本起,不建议使用Picker组件的单列和多列模式,Select组件是专门为列选择而构造的组件,更简单易用。)
+	 * @tutorial http://uviewui.com/components/select.html
+	 * @property {String} mode 模式选择,"single-column"-单列模式,"mutil-column"-多列模式,"mutil-column-auto"-多列联动模式
+	 * @property {Array} list 列数据,数组形式,见官网说明
+	 * @property {Boolean} v-model 布尔值变量,用于控制选择器的弹出与收起
+	 * @property {Boolean} safe-area-inset-bottom 是否开启底部安全区适配(默认false)
+	 * @property {String} cancel-color 取消按钮的颜色(默认#606266)
+	 * @property {String} confirm-color 确认按钮的颜色(默认#2979ff)
+	 * @property {String} confirm-text 确认按钮的文字
+	 * @property {String} cancel-text 取消按钮的文字
+	 * @property {String} default-value 提供的默认选中的下标,见官网说明
+	 * @property {Boolean} mask-close-able 是否允许通过点击遮罩关闭Picker(默认true)
+	 * @property {String Number} z-index 弹出时的z-index值(默认10075)
+	 * @property {String} value-name 自定义list数据的value属性名 1.3.6
+	 * @property {String} label-name 自定义list数据的label属性名 1.3.6
+	 * @property {String} child-name 自定义list数据的children属性名,只对多列联动模式有效 1.3.7
+	 * @event {Function} confirm 点击确定按钮,返回当前选择的值
+	 * @example <u-select v-model="show" :list="list"></u-select>
+	 */
+
+export default {
+	props: {
+		// 列数据
+		list: {
+			type: Array,
+			default() {
+				return [];
+			}
+		},
+		// 是否显示边框
+		border: {
+			type: Boolean,
+			default: true
+		},
+		// 通过双向绑定控制组件的弹出与收起
+		value: {
+			type: Boolean,
+			default: false
+		},
+		// "取消"按钮的颜色
+		cancelColor: {
+			type: String,
+			default: '#606266'
+		},
+		// "确定"按钮的颜色
+		confirmColor: {
+			type: String,
+			default: '#2979ff'
+		},
+		// 弹出的z-index值
+		zIndex: {
+			type: [String, Number],
+			default: 0
+		},
+		safeAreaInsetBottom: {
+			type: Boolean,
+			default: false
+		},
+		// 是否允许通过点击遮罩关闭Picker
+		maskCloseAble: {
+			type: Boolean,
+			default: true
+		},
+		// 提供的默认选中的下标
+		defaultValue: {
+			type: Array,
+			default() {
+				return [0];
+			}
+		},
+		// 模式选择,single-column-单列,mutil-column-多列,mutil-column-auto-多列联动
+		mode: {
+			type: String,
+			default: 'single-column'
+		},
+		// 自定义value属性名
+		valueName: {
+			type: String,
+			default: 'value'
+		},
+		// 自定义label属性名
+		labelName: {
+			type: String,
+			default: 'label'
+		},
+		// 自定义多列联动模式的children属性名
+		childName: {
+			type: String,
+			default: 'children'
+		},
+		// 顶部标题
+		title: {
+			type: String,
+			default: ''
+		},
+		// 取消按钮的文字
+		cancelText: {
+			type: String,
+			default: '取消'
+		},
+		// 确认按钮的文字
+		confirmText: {
+			type: String,
+			default: '确认'
+		}
+	},
+	data() {
+		return {
+			// 用于列改变时,保存当前的索引,下一次变化时比较得出是哪一列发生了变化
+			defaultSelector: [0],
+			// picker-view的数据
+			columnData: [],
+			// 每次队列发生变化时,保存选择的结果
+			selectValue: [],
+			// 上一次列变化时的index
+			lastSelectIndex: [],
+			// 列数
+			columnNum: 0,
+			// 列是否还在滑动中,微信小程序如果在滑动中就点确定,结果可能不准确
+			moving: false
+		};
+	},
+	watch: {
+		// 在select弹起的时候,重新初始化所有数据
+		value: {
+			immediate: true,
+			handler(val) {
+				if(val) setTimeout(() => this.init(), 10);
+			}
+		},
+	},
+	computed: {
+		uZIndex() {
+			// 如果用户有传递z-index值,优先使用
+			return this.zIndex ? this.zIndex : this.$u.zIndex.popup;
+		},
+	},
+	methods: {
+		// 标识滑动开始,只有微信小程序才有这样的事件
+		pickstart() {
+			// #ifdef MP-WEIXIN
+			this.moving = true;
+			// #endif
+		},
+		// 标识滑动结束
+		pickend() {
+			// #ifdef MP-WEIXIN
+			this.moving = false;
+			// #endif
+		},
+		init() {
+			this.setColumnNum();
+			this.setDefaultSelector();
+			this.setColumnData();
+			this.setSelectValue();
+		},
+		// 获取默认选中列下标
+		setDefaultSelector() {
+			// 如果没有传入默认选中的值,生成长度为columnNum,用0填充的数组
+			this.defaultSelector = this.defaultValue.length == this.columnNum ? this.defaultValue : Array(this.columnNum).fill(0);
+			this.lastSelectIndex = this.$u.deepClone(this.defaultSelector);
+		},
+		// 计算列数
+		setColumnNum() {
+			// 单列的列数为1
+			if(this.mode == 'single-column') this.columnNum = 1;
+			// 多列时,this.list数组长度就是列数
+			else if(this.mode == 'mutil-column') this.columnNum = this.list.length;
+			// 多列联动时,通过历遍this.list的第一个元素,得出有多少列
+			else if(this.mode == 'mutil-column-auto') {
+				let num = 1;
+				let column = this.list;
+				// 只要有元素并且第一个元素有children属性,继续历遍
+				while(column[0][this.childName]) {
+					column = column[0] ? column[0][this.childName] : {};
+					num ++;
+				}
+				this.columnNum = num;
+			}
+		},
+		// 获取需要展示在picker中的列数据
+		setColumnData() {
+			let data = [];
+			this.selectValue = [];
+			if(this.mode == 'mutil-column-auto') {
+				// 获得所有数据中的第一个元素
+				let column = this.list[this.defaultSelector.length ? this.defaultSelector[0] : 0];
+				// 通过循环所有的列数,再根据设定列的数组,得出当前需要渲染的整个列数组
+				for (let i = 0; i < this.columnNum; i++) {
+					// 第一列默认为整个list数组
+					if (i == 0) {
+						data[i] = this.list;
+						column = column[this.childName];
+					} else {
+						// 大于第一列时,判断是否有默认选中的,如果没有就用该列的第一项
+						data[i] = column;
+						column = column[this.defaultSelector[i]][this.childName];
+					}
+				}
+			} else if(this.mode == 'single-column') {
+				data[0] = this.list;
+			} else {
+				data = this.list;
+			}
+			this.columnData = data;
+		},
+		// 获取默认选中的值,如果没有设置defaultValue,就默认选中每列的第一个
+		setSelectValue() {
+			let tmp = null;
+			for(let i = 0; i < this.columnNum; i++) {
+				tmp = this.columnData[i][this.defaultSelector[i]];
+				let data = {
+					value: tmp ? tmp[this.valueName] : null,
+					label: tmp ? tmp[this.labelName] : null
+				};
+				// 判断是否存在额外的参数,如果存在,就返回
+				if(tmp && tmp.extra) data.extra = tmp.extra;
+				this.selectValue.push(data)
+			}
+		},
+		// 列选项
+		columnChange(e) {
+			let index = null;
+			let columnIndex = e.detail.value;
+			// 由于后面是需要push进数组的,所以需要先清空数组
+			this.selectValue = [];
+			if(this.mode == 'mutil-column-auto') {
+				// 对比前后两个数组,寻找变更的是哪一列,如果某一个元素不同,即可判定该列发生了变化
+				this.lastSelectIndex.map((val, idx) => {
+					if (val != columnIndex[idx]) index = idx;
+				});
+				this.defaultSelector = columnIndex;
+				for (let i = index + 1; i < this.columnNum; i++) {
+					// 当前变化列的下一列的数据,需要获取上一列的数据,同时需要指定是上一列的第几个的children,再往后的
+					// 默认是队列的第一个为默认选项
+					this.columnData[i] = this.columnData[i - 1][i - 1 == index ? columnIndex[index] : 0][this.childName];
+					// 改变的列之后的所有列,默认选中第一个
+					this.defaultSelector[i] = 0;
+				}
+				// 在历遍的过程中,可能由于上一步修改this.columnData,导致产生连锁反应,程序触发columnChange,会有多次调用
+				// 只有在最后一次数据稳定后的结果是正确的,此前的历遍中,可能会产生undefined,故需要判断
+				columnIndex.map((item, index) => {
+					let data = this.columnData[index][columnIndex[index]];
+					let tmp = {
+						value: data ? data[this.valueName] : null,
+						label: data ? data[this.labelName] : null,
+					};
+					// 判断是否有需要额外携带的参数
+					if(data && data.extra !== undefined) tmp.extra = data.extra;
+					this.selectValue.push(tmp);
+
+				})
+				// 保存这一次的结果,用于下次列发生变化时作比较
+				this.lastSelectIndex = columnIndex;
+			} else if(this.mode == 'single-column') {
+				let data = this.columnData[0][columnIndex[0]];
+				// 初始默认选中值
+				let tmp = {
+					value: data ? data[this.valueName] : null,
+					label: data ? data[this.labelName] : null,
+				};
+				// 判断是否有需要额外携带的参数
+				if(data && data.extra !== undefined) tmp.extra = data.extra;
+				this.selectValue.push(tmp);
+			} else if(this.mode == 'mutil-column') {
+				// 初始默认选中值
+				columnIndex.map((item, index) => {
+					let data = this.columnData[index][columnIndex[index]];
+					// 初始默认选中值
+					let tmp = {
+						value: data ? data[this.valueName] : null,
+						label: data ? data[this.labelName] : null,
+					};
+					// 判断是否有需要额外携带的参数
+					if(data && data.extra !== undefined) tmp.extra = data.extra;
+					this.selectValue.push(tmp);
+				})
+			}
+		},
+		close() {
+			this.$emit('input', false);
+		},
+		// 点击确定或者取消
+		getResult(event = null) {
+			// #ifdef MP-WEIXIN
+			if (this.moving) return;
+			// #endif
+			if (event) this.$emit(event, this.selectValue);
+			this.close();
+		},
+		selectHandler() {
+			this.$emit('click');
+		}
+	}
+};
+</script>
+
+<style scoped lang="scss">
+@import "../../libs/css/style.components.scss";
+
+.u-select {
+
+	&__action {
+		position: relative;
+		line-height: $u-form-item-height;
+		height: $u-form-item-height;
+
+		&__icon {
+			position: absolute;
+			right: 20rpx;
+			top: 50%;
+			transition: transform .4s;
+			transform: translateY(-50%);
+			z-index: 1;
+
+			&--reverse {
+				transform: rotate(-180deg) translateY(50%);
+			}
+		}
+	}
+
+	&__hader {
+		&__title {
+			color: $u-content-color;
+		}
+	}
+
+	&--border {
+		border-radius: 6rpx;
+		border-radius: 4px;
+		border: 1px solid $u-form-item-border-color;
+	}
+
+	&__header {
+		@include vue-flex;
+		align-items: center;
+		justify-content: space-between;
+		height: 80rpx;
+		padding: 0 40rpx;
+	}
+
+	&__body {
+		width: 100%;
+		height: 500rpx;
+		overflow: hidden;
+		background-color: #fff;
+
+		&__picker-view {
+			height: 100%;
+			box-sizing: border-box;
+
+			&__item {
+				@include vue-flex;
+				align-items: center;
+				justify-content: center;
+				font-size: 50rpx;
+				color: $u-main-color;
+				padding: 0 8rpx;
+			}
+		}
+	}
+}
+</style>

+ 330 - 0
uview-ui/components/u-tabbar/u-tabbar.vue

@@ -0,0 +1,330 @@
+<template>
+	<view v-if="show" class="u-tabbar" @touchmove.stop.prevent="() => {}">
+		<view class="u-tabbar__content safe-area-inset-bottom" :style="{
+			height: $u.addUnit(height),
+			backgroundColor: bgColor,
+		}" :class="{
+			'u-border-top': borderTop
+		}">
+			<view class="u-tabbar__content__item" v-for="(item, index) in list" :key="index" :class="{
+				'u-tabbar__content__circle': midButton &&item.midButton
+			}" @tap.stop="clickHandler(index)" :style="{
+				backgroundColor: bgColor
+			}">
+				<view :class="[
+					midButton && item.midButton ? 'u-tabbar__content__circle__button' : 'u-tabbar__content__item__button'
+				]">
+					<u-icon
+						:size="midButton && item.midButton ? midButtonSize : iconSize"
+						:name="elIconPath(index)"
+						img-mode="scaleToFill"
+						:color="elColor(index)"
+						:custom-prefix="item.customIcon ? 'custom-icon' : 'uicon'"
+					></u-icon>
+					<u-badge :count="item.count" :is-dot="item.isDot"
+						v-if="item.count || item.isDot"
+						:offset="[-2, getOffsetRight(item.count, item.isDot)]"
+					></u-badge>
+				</view>
+				<view class="u-tabbar__content__item__text" :style="{
+					color: elColor(index)
+				}">
+					<text class="u-line-1">{{item.text}}</text>
+				</view>
+			</view>
+			<view v-if="midButton" class="u-tabbar__content__circle__border" :class="{
+				'u-border': borderTop,
+			}" :style="{
+				backgroundColor: bgColor,
+				left: midButtonLeft
+			}">
+			</view>
+		</view>
+		<!-- 这里加上一个48rpx的高度,是为了增高有凸起按钮时的防塌陷高度(也即按钮凸出来部分的高度) -->
+		<view class="u-fixed-placeholder safe-area-inset-bottom" :style="{
+				height: `calc(${$u.addUnit(height)} + ${midButton ? 48 : 0}rpx)`,
+			}"></view>
+	</view>
+</template>
+
+<script>
+	export default {
+		props: {
+			// 显示与否
+			show: {
+				type: Boolean,
+				default: true
+			},
+			// 通过v-model绑定current值
+			value: {
+				type: [String, Number],
+				default: 0
+			},
+			// 整个tabbar的背景颜色
+			bgColor: {
+				type: String,
+				default: '#ffffff'
+			},
+			// tabbar的高度,默认50px,单位任意,如果为数值,则为rpx单位
+			height: {
+				type: [String, Number],
+				default: '50px'
+			},
+			// 非凸起图标的大小,单位任意,数值默认rpx
+			iconSize: {
+				type: [String, Number],
+				default: 40
+			},
+			// 凸起的图标的大小,单位任意,数值默认rpx
+			midButtonSize: {
+				type: [String, Number],
+				default: 90
+			},
+			// 激活时的演示,包括字体图标,提示文字等的演示
+			activeColor: {
+				type: String,
+				default: '#303133'
+			},
+			// 未激活时的颜色
+			inactiveColor: {
+				type: String,
+				default: '#606266'
+			},
+			// 是否显示中部的凸起按钮
+			midButton: {
+				type: Boolean,
+				default: false
+			},
+			// 配置参数
+			list: {
+				type: Array,
+				default () {
+					return []
+				}
+			},
+			// 切换前的回调
+			beforeSwitch: {
+				type: Function,
+				default: null
+			},
+			// 是否显示顶部的横线
+			borderTop: {
+				type: Boolean,
+				default: true
+			},
+			// 是否隐藏原生tabbar
+			hideTabBar: {
+				type: Boolean,
+				default: true
+			},
+		},
+		data() {
+			return {
+				// 由于安卓太菜了,通过css居中凸起按钮的外层元素有误差,故通过js计算将其居中
+				midButtonLeft: '50%',
+				pageUrl: '', // 当前页面URL
+			}
+		},
+		created() {
+			// 是否隐藏原生tabbar
+			if(this.hideTabBar) uni.hideTabBar();
+			// 获取引入了u-tabbar页面的路由地址,该地址没有路径前面的"/"
+			let pages = getCurrentPages();
+			// 页面栈中的最后一个即为项为当前页面,route属性为页面路径
+			this.pageUrl = pages[pages.length - 1].route;
+		},
+		computed: {
+			elIconPath() {
+				return (index) => {
+					// 历遍u-tabbar的每一项item时,判断是否传入了pagePath参数,如果传入了
+					// 和data中的pageUrl参数对比,如果相等,即可判断当前的item对应当前的tabbar页面,设置高亮图标
+					// 采用这个方法,可以无需使用v-model绑定的value值
+					let pagePath = this.list[index].pagePath;
+					// 如果定义了pagePath属性,意味着使用系统自带tabbar方案,否则使用一个页面用几个组件模拟tabbar页面的方案
+					// 这两个方案对处理tabbar item的激活与否方式不一样
+					if(pagePath) {
+						if(pagePath == this.pageUrl || pagePath == '/' + this.pageUrl) {
+							return this.list[index].selectedIconPath;
+						} else {
+							return this.list[index].iconPath;
+						}
+					} else {
+						// 普通方案中,索引等于v-model值时,即为激活项
+						return index == this.value ? this.list[index].selectedIconPath : this.list[index].iconPath
+					}
+				}
+			},
+			elColor() {
+				return (index) => {
+					// 判断方法同理于elIconPath
+					let pagePath = this.list[index].pagePath;
+					if(pagePath) {
+						if(pagePath == this.pageUrl || pagePath == '/' + this.pageUrl) return this.activeColor;
+						else return this.inactiveColor;
+					} else {
+						return index == this.value ? this.activeColor : this.inactiveColor;
+					}
+				}
+			}
+		},
+		mounted() {
+			this.midButton && this.getMidButtonLeft();
+		},
+		methods: {
+			async clickHandler(index) {
+				if(this.beforeSwitch && typeof(this.beforeSwitch) === 'function') {
+					// 执行回调,同时传入索引当作参数
+					// 在微信,支付宝等环境(H5正常),会导致父组件定义的customBack()函数体中的this变成子组件的this
+					// 通过bind()方法,绑定父组件的this,让this.customBack()的this为父组件的上下文
+					let beforeSwitch = this.beforeSwitch.bind(this.$u.$parent.call(this))(index);
+					// 判断是否返回了promise
+					if (!!beforeSwitch && typeof beforeSwitch.then === 'function') {
+						await beforeSwitch.then(res => {
+							// promise返回成功,
+							this.switchTab(index);
+						}).catch(err => {
+
+						})
+					} else if(beforeSwitch === true) {
+						// 如果返回true
+						this.switchTab(index);
+					}
+				} else {
+					this.switchTab(index);
+				}
+			},
+			// 切换tab
+			switchTab(index) {
+				// 发出事件和修改v-model绑定的值
+				this.$emit('change', index);
+				// 如果有配置pagePath属性,使用uni.switchTab进行跳转
+				if(this.list[index].pagePath) {
+					uni.switchTab({
+						url: this.list[index].pagePath
+					})
+				} else {
+					// 如果配置了papgePath属性,将不会双向绑定v-model传入的value值
+					// 因为这个模式下,不再需要v-model绑定的value值了,而是通过getCurrentPages()适配
+					this.$emit('input', index);
+				}
+			},
+			// 计算角标的right值
+			getOffsetRight(count, isDot) {
+				// 点类型,count大于9(两位数),分别设置不同的right值,避免位置太挤
+				if(isDot) {
+					return -20;
+				} else if(count > 9) {
+					return -40;
+				} else {
+					return -30;
+				}
+			},
+			// 获取凸起按钮外层元素的left值,让其水平居中
+			getMidButtonLeft() {
+				let windowWidth = this.$u.sys().windowWidth;
+				// 由于安卓中css计算left: 50%的结果不准确,故用js计算
+				this.midButtonLeft = (windowWidth / 2) + 'px';
+			}
+		}
+	}
+</script>
+
+<style scoped lang="scss">
+	@import "../../libs/css/style.components.scss";
+	.u-fixed-placeholder {
+		/* #ifndef APP-NVUE */
+		box-sizing: content-box;
+		/* #endif */
+	}
+
+	.u-tabbar {
+
+		&__content {
+			@include vue-flex;
+			align-items: center;
+			position: relative;
+			position: fixed;
+			bottom: 0;
+			left: 0;
+			width: 100%;
+			z-index: 998;
+			/* #ifndef APP-NVUE */
+			box-sizing: content-box;
+			/* #endif */
+
+			&__circle__border {
+				border-radius: 100%;
+				width: 110rpx;
+				height: 110rpx;
+				top: -48rpx;
+				position: absolute;
+				z-index: 4;
+				background-color: #ffffff;
+				// 由于安卓的无能,导致只有3个tabbar item时,此css计算方式有误差
+				// 故使用js计算的形式来定位,此处不注释,是因为js计算有延后,避免出现位置闪动
+				left: 50%;
+				transform: translateX(-50%);
+
+				&:after {
+					border-radius: 100px;
+				}
+			}
+
+			&__item {
+				flex: 1;
+				justify-content: center;
+				height: 100%;
+				padding: 12rpx 0;
+				@include vue-flex;
+				flex-direction: column;
+				align-items: center;
+				position: relative;
+
+				&__button {
+					position: absolute;
+					top: 14rpx;
+					left: 50%;
+					transform: translateX(-50%);
+				}
+
+				&__text {
+					color: $u-content-color;
+					font-size: 26rpx;
+					line-height: 28rpx;
+					position: absolute;
+					bottom: 14rpx;
+					left: 50%;
+					transform: translateX(-50%);
+					width: 100%;
+					text-align: center;
+				}
+			}
+
+			&__circle {
+				position: relative;
+				@include vue-flex;
+				flex-direction: column;
+				justify-content: space-between;
+				z-index: 10;
+				/* #ifndef APP-NVUE */
+				height: calc(100% - 1px);
+				/* #endif */
+
+				&__button {
+					width: 90rpx;
+					height: 90rpx;
+					border-radius: 100%;
+					@include vue-flex;
+					justify-content: center;
+					align-items: center;
+					position: absolute;
+					background-color: #ffffff;
+					top: -40rpx;
+					left: 50%;
+					z-index: 6;
+					transform: translateX(-50%);
+				}
+			}
+		}
+	}
+</style>

+ 220 - 0
uview-ui/components/u-toast/u-toast.vue

@@ -0,0 +1,220 @@
+<template>
+	<view class="u-toast" :class="[isShow ? 'u-show' : '', 'u-type-' + tmpConfig.type, 'u-position-' + tmpConfig.position]" :style="{
+		zIndex: uZIndex
+	}">
+		<view class="u-icon-wrap">
+			<u-icon v-if="tmpConfig.icon" class="u-icon" :name="iconName" :size="30" :color="tmpConfig.type"></u-icon>
+		</view>
+		<text class="u-text">{{tmpConfig.title}}</text>
+	</view>
+</template>
+
+<script>
+	/**
+	 * toast 消息提示
+	 * @description 此组件表现形式类似uni的uni.showToastAPI,但也有不同的地方。
+	 * @tutorial https://www.uviewui.com/components/toast.html
+	 * @property {String} z-index toast展示时的z-index值
+	 * @event {Function} show 显示toast,如需一进入页面就显示toast,请在onReady生命周期调用
+	 * @example <u-toast ref="uToast" />
+	 */
+	export default {
+		name: "u-toast",
+		props: {
+			// z-index值
+			zIndex: {
+				type: [Number, String],
+				default: ''
+			},
+		},
+		data() {
+			return {
+				isShow: false,
+				timer: null, // 定时器
+				config: {
+					params: {}, // URL跳转的参数,对象
+					title: '', // 显示文本
+					type: '', // 主题类型,primary,success,error,warning,black
+					duration: 2000, // 显示的时间,毫秒
+					isTab: false, // 是否跳转tab页面
+					url: '', // toast消失后是否跳转页面,有则跳转,优先级高于back参数
+					icon: true, // 显示的图标
+					position: 'center', // toast出现的位置
+					callback: null, // 执行完后的回调函数
+					back: false, // 结束toast是否自动返回上一页
+				},
+				tmpConfig: {}, // 将用户配置和内置配置合并后的临时配置变量
+			};
+		},
+		computed: {
+			iconName() {
+				// 只有不为none,并且type为error|warning|succes|info时候,才显示图标
+				if (['error', 'warning', 'success', 'info'].indexOf(this.tmpConfig.type) >= 0 && this.tmpConfig.icon) {
+					let icon = this.$u.type2icon(this.tmpConfig.type);
+					return icon;
+				}
+			},
+			uZIndex() {
+				// 显示toast时候,如果用户有传递z-index值,有限使用
+				return this.isShow ? (this.zIndex ? this.zIndex : this.$u.zIndex.toast) : '999999';
+			}
+		},
+		methods: {
+			// 显示toast组件,由父组件通过this.$refs.xxx.show(options)形式调用
+			show(options) {
+				// 不降结果合并到this.config变量,避免多次条用u-toast,前后的配置造成混论
+				this.tmpConfig = this.$u.deepMerge(this.config, options);
+				if (this.timer) {
+					// 清除定时器
+					clearTimeout(this.timer);
+					this.timer = null;
+				}
+				this.isShow = true;
+				this.timer = setTimeout(() => {
+					// 倒计时结束,清除定时器,隐藏toast组件
+					this.isShow = false;
+					clearTimeout(this.timer);
+					this.timer = null;
+					// 判断是否存在callback方法,如果存在就执行
+					typeof(this.tmpConfig.callback) === 'function' && this.tmpConfig.callback();
+					this.timeEnd();
+				}, this.tmpConfig.duration);
+			},
+			// 隐藏toast组件,由父组件通过this.$refs.xxx.hide()形式调用
+			hide() {
+				this.isShow = false;
+				if (this.timer) {
+					// 清除定时器
+					clearTimeout(this.timer);
+					this.timer = null;
+				}
+			},
+			// 倒计时结束之后,进行的一些操作
+			timeEnd() {
+				// 如果带有url值,根据isTab为true或者false进行跳转
+				if (this.tmpConfig.url) {
+					// 如果url没有"/"开头,添加上,因为uni的路由跳转需要"/"开头
+					if (this.tmpConfig.url[0] != '/') this.tmpConfig.url = '/' + this.tmpConfig.url;
+					// 判断是否有传递显式的参数
+					if (Object.keys(this.tmpConfig.params).length) {
+						// 判断用户传递的url中,是否带有参数
+						// 使用正则匹配,主要依据是判断是否有"/","?","="等,如“/page/index/index?name=mary"
+						// 如果有params参数,转换后无需带上"?"
+						let query = '';
+						if (/.*\/.*\?.*=.*/.test(this.tmpConfig.url)) {
+							// object对象转为get类型的参数
+							query = this.$u.queryParams(this.tmpConfig.params, false);
+							this.tmpConfig.url = this.tmpConfig.url + "&" + query;
+						} else {
+							query = this.$u.queryParams(this.tmpConfig.params);
+							this.tmpConfig.url += query;
+						}
+					}
+					// 如果是跳转tab页面,就使用uni.switchTab
+					if (this.tmpConfig.isTab) {
+						uni.switchTab({
+							url: this.tmpConfig.url
+						});
+					} else {
+						uni.navigateTo({
+							url: this.tmpConfig.url
+						});
+					}
+				} else if(this.tmpConfig.back) {
+					// 回退到上一页
+					this.$u.route({
+						type: 'back'
+					})
+				}
+			}
+		}
+	};
+</script>
+
+<style lang="scss" scoped>
+	@import "../../libs/css/style.components.scss";
+	
+	.u-toast {
+		position: fixed;
+		z-index: -1;
+		transition: opacity 0.3s;
+		text-align: center;
+		color: #fff;
+		border-radius: 8rpx;
+		background: #585858;
+		@include vue-flex;
+		align-items: center;
+		justify-content: center;
+		font-size: 28rpx;
+		opacity: 0;
+		pointer-events: none;
+		padding: 18rpx 40rpx;
+	}
+
+	.u-toast.u-show {
+		opacity: 1;
+	}
+
+	.u-icon {
+		margin-right: 10rpx;
+		@include vue-flex;
+		align-items: center;
+		line-height: normal;
+	}
+
+	.u-position-center {
+		left: 50%;
+		top: 50%;
+		transform: translate(-50%,-50%);
+		/* #ifndef APP-NVUE */
+		max-width: 70%;
+		/* #endif */
+	}
+
+	.u-position-top {
+		left: 50%;
+		top: 20%;
+		transform: translate(-50%,-50%);
+	}
+
+	.u-position-bottom {
+		left: 50%;
+		bottom: 20%;
+		transform: translate(-50%,-50%);
+	}
+
+	.u-type-primary {
+		color: $u-type-primary;
+		background-color: $u-type-primary-light;
+		border: 1px solid rgb(215, 234, 254);
+	}
+
+	.u-type-success {
+		color: $u-type-success;
+		background-color: $u-type-success-light;
+		border: 1px solid #BEF5C8;
+	}
+
+	.u-type-error {
+		color: $u-type-error;
+		background-color: $u-type-error-light;
+		border: 1px solid #fde2e2;
+	}
+
+	.u-type-warning {
+		color: $u-type-warning;
+		background-color: $u-type-warning-light;
+		border: 1px solid #faecd8;
+	}
+
+	.u-type-info {
+		color: $u-type-info;
+		background-color: $u-type-info-light;
+		border: 1px solid #ebeef5;
+	}
+
+	.u-type-default {
+		color: #fff;
+		background-color: #585858;
+	}
+</style>

+ 654 - 0
uview-ui/components/u-upload/u-upload.vue

@@ -0,0 +1,654 @@
+<template>
+	<view class="u-upload" v-if="!disabled">
+		<view
+			v-if="showUploadList"
+			class="u-list-item u-preview-wrap"
+			v-for="(item, index) in lists"
+			:key="index"
+			:style="{
+				width: $u.addUnit(width),
+				height: $u.addUnit(height)
+			}"
+		>
+			<view
+				v-if="deletable"
+				class="u-delete-icon"
+				@tap.stop="deleteItem(index)"
+				:style="{
+					background: delBgColor
+				}"
+			>
+				<u-icon class="u-icon" :name="delIcon" size="20" :color="delColor"></u-icon>
+			</view>
+			<u-line-progress
+				v-if="showProgress && item.progress > 0 && !item.error"
+				:show-percent="false"
+				height="16"
+				class="u-progress"
+				:percent="item.progress"
+			></u-line-progress>
+			<view @tap.stop="retry(index)" v-if="item.error" class="u-error-btn">点击重试</view>
+			<image @tap.stop="doPreviewImage(item.url || item.path, index)" class="u-preview-image" v-if="!item.isImage" :src="item.url || item.path" :mode="imageMode"></image>
+		</view>
+		<slot name="file" :file="lists"></slot>
+		<view style="display: inline-block;" @tap="selectFile" v-if="maxCount > lists.length">
+			<slot name="addBtn"></slot>
+			<view
+				v-if="!customBtn"
+				class="u-list-item u-add-wrap"
+				hover-class="u-add-wrap__hover"
+				hover-stay-time="150"
+				:style="{
+					width: $u.addUnit(width),
+					height: $u.addUnit(height)
+				}"
+			>
+				<u-icon name="plus" class="u-add-btn" size="40"></u-icon>
+				<view class="u-add-tips">{{ uploadText }}</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+/**
+ * upload 图片上传
+ * @description 该组件用于上传图片场景
+ * @tutorial https://www.uviewui.com/components/upload.html
+ * @property {String} action 服务器上传地址
+ * @property {String Number} max-count 最大选择图片的数量(默认99)
+ * @property {Boolean} custom-btn 如果需要自定义选择图片的按钮,设置为true(默认false)
+ * @property {Boolean} show-progress 是否显示进度条(默认true)
+ * @property {Boolean} disabled 是否启用(显示/移仓)组件(默认false)
+ * @property {String} image-mode 预览图片等显示模式,可选值为uni的image的mode属性值(默认aspectFill)
+ * @property {String} del-icon 右上角删除图标名称,只能为uView内置图标
+ * @property {String} del-bg-color 右上角关闭按钮的背景颜色
+ * @property {String | Number} index 在各个回调事件中的最后一个参数返回,用于区别是哪一个组件的事件
+ * @property {String} del-color 右上角关闭按钮图标的颜色
+ * @property {Object} header 上传携带的头信息,对象形式
+ * @property {Object} form-data 上传额外携带的参数
+ * @property {String} name 上传文件的字段名,供后端获取使用(默认file)
+ * @property {Array<String>} size-type original 原图,compressed 压缩图,默认二者都有(默认['original', 'compressed'])
+ * @property {Array<String>} source-type 选择图片的来源,album-从相册选图,camera-使用相机,默认二者都有(默认['album', 'camera'])
+ * @property {Boolean} preview-full-image	是否可以通过uni.previewImage预览已选择的图片(默认true)
+ * @property {Boolean} multiple	是否开启图片多选,部分安卓机型不支持(默认true)
+ * @property {Boolean} deletable 是否显示删除图片的按钮(默认true)
+ * @property {String Number} max-size 选择单个文件的最大大小,单位B(byte),默认不限制(默认Number.MAX_VALUE)
+ * @property {Array<Object>} file-list 默认显示的图片列表,数组元素为对象,必须提供url属性
+ * @property {Boolean} upload-text 选择图片按钮的提示文字(默认“选择图片”)
+ * @property {Boolean} auto-upload 选择完图片是否自动上传,见上方说明(默认true)
+ * @property {Boolean} show-tips 特殊情况下是否自动提示toast,见上方说明(默认true)
+ * @property {Boolean} show-upload-list 是否显示组件内部的图片预览(默认true)
+ * @event {Function} on-oversize 图片大小超出最大允许大小
+ * @event {Function} on-preview 全屏预览图片时触发
+ * @event {Function} on-remove 移除图片时触发
+ * @event {Function} on-success 图片上传成功时触发
+ * @event {Function} on-change 图片上传后,无论成功或者失败都会触发
+ * @event {Function} on-error 图片上传失败时触发
+ * @event {Function} on-progress 图片上传过程中的进度变化过程触发
+ * @event {Function} on-uploaded 所有图片上传完毕触发
+ * @event {Function} on-choose-complete 每次选择图片后触发,只是让外部可以得知每次选择后,内部的文件列表
+ * @example <u-upload :action="action" :file-list="fileList" ></u-upload>
+ */
+export default {
+	name: 'u-upload',
+	props: {
+		//是否显示组件自带的图片预览功能
+		showUploadList: {
+			type: Boolean,
+			default: true
+		},
+		// 后端地址
+		action: {
+			type: String,
+			default: ''
+		},
+		// 最大上传数量
+		maxCount: {
+			type: [String, Number],
+			default: 52
+		},
+		//  是否显示进度条
+		showProgress: {
+			type: Boolean,
+			default: true
+		},
+		// 是否启用
+		disabled: {
+			type: Boolean,
+			default: false
+		},
+		// 预览上传的图片时的裁剪模式,和image组件mode属性一致
+		imageMode: {
+			type: String,
+			default: 'aspectFill'
+		},
+		// 头部信息
+		header: {
+			type: Object,
+			default() {
+				return {};
+			}
+		},
+		// 额外携带的参数
+		formData: {
+			type: Object,
+			default() {
+				return {};
+			}
+		},
+		// 上传的文件字段名
+		name: {
+			type: String,
+			default: 'file'
+		},
+		// 所选的图片的尺寸, 可选值为original compressed
+		sizeType: {
+			type: Array,
+			default() {
+				return ['original', 'compressed'];
+			}
+		},
+		sourceType: {
+			type: Array,
+			default() {
+				return ['album', 'camera'];
+			}
+		},
+		// 是否在点击预览图后展示全屏图片预览
+		previewFullImage: {
+			type: Boolean,
+			default: true
+		},
+		// 是否开启图片多选,部分安卓机型不支持
+		multiple: {
+			type: Boolean,
+			default: true
+		},
+		// 是否展示删除按钮
+		deletable: {
+			type: Boolean,
+			default: true
+		},
+		// 文件大小限制,单位为byte
+		maxSize: {
+			type: [String, Number],
+			default: Number.MAX_VALUE
+		},
+		// 显示已上传的文件列表
+		fileList: {
+			type: Array,
+			default() {
+				return [];
+			}
+		},
+		// 上传区域的提示文字
+		uploadText: {
+			type: String,
+			default: '选择图片'
+		},
+		// 是否自动上传
+		autoUpload: {
+			type: Boolean,
+			default: true
+		},
+		// 是否显示toast消息提示
+		showTips: {
+			type: Boolean,
+			default: true
+		},
+		// 是否通过slot自定义传入选择图标的按钮
+		customBtn: {
+			type: Boolean,
+			default: false
+		},
+		// 内部预览图片区域和选择图片按钮的区域宽度
+		width: {
+			type: [String, Number],
+			default: 200
+		},
+		// 内部预览图片区域和选择图片按钮的区域高度
+		height: {
+			type: [String, Number],
+			default: 200
+		},
+		// 右上角关闭按钮的背景颜色
+		delBgColor: {
+			type: String,
+			default: '#fa3534'
+		},
+		// 右上角关闭按钮的叉号图标的颜色
+		delColor: {
+			type: String,
+			default: '#ffffff'
+		},
+		// 右上角删除图标名称,只能为uView内置图标
+		delIcon: {
+			type: String,
+			default: 'close'
+		},
+		// 如果上传后的返回值为json字符串,是否自动转json
+		toJson: {
+			type: Boolean,
+			default: true
+		},
+		// 上传前的钩子,每个文件上传前都会执行
+		beforeUpload: {
+			type: Function,
+			default: null
+		},
+		// 移除文件前的钩子
+		beforeRemove: {
+			type: Function,
+			default: null
+		},
+		// 允许上传的图片后缀
+		limitType:{
+			type: Array,
+			default() {
+				// 支付宝小程序真机选择图片的后缀为"image"
+				// https://opendocs.alipay.com/mini/api/media-image
+				return ['png', 'jpg', 'jpeg', 'webp', 'gif', 'image'];
+			}
+		},
+		// 在各个回调事件中的最后一个参数返回,用于区别是哪一个组件的事件
+		index: {
+			type: [Number, String],
+			default: ''
+		}
+	},
+	mounted() {},
+	data() {
+		return {
+			lists: [],
+			isInCount: true,
+			uploading: false
+		};
+	},
+	watch: {
+		fileList: {
+			immediate: true,
+			handler(val) {
+				val.map(value => {
+					// 首先检查内部是否已经添加过这张图片,因为外部绑定了一个对象给fileList的话(对象引用),进行修改外部fileList
+					// 时,会触发watch,导致重新把原来的图片再次添加到this.lists
+					// 数组的some方法意思是,只要数组元素有任意一个元素条件符合,就返回true,而另一个数组的every方法的意思是数组所有元素都符合条件才返回true
+					let tmp = this.lists.some(val => {
+						return val.url == value.url;
+					})
+					// 如果内部没有这个图片(tmp为false),则添加到内部
+					!tmp && this.lists.push({ url: value.url, error: false, progress: 100 });
+				});
+			}
+		},
+		// 监听lists的变化,发出事件
+		lists(n) {
+			this.$emit('on-list-change', n, this.index);
+		}
+	},
+	methods: {
+		// 清除列表
+		clear() {
+			this.lists = [];
+		},
+		// 重新上传队列中上传失败的所有文件
+		reUpload() {
+			this.uploadFile();
+		},
+		// 选择图片
+		selectFile() {
+			if (this.disabled) return;
+			const { name = '', maxCount, multiple, maxSize, sizeType, lists, camera, compressed, maxDuration, sourceType } = this;
+			let chooseFile = null;
+			const newMaxCount = maxCount - lists.length;
+			// 设置为只选择图片的时候使用 chooseImage 来实现
+			chooseFile = new Promise((resolve, reject) => {
+				uni.chooseImage({
+					count: multiple ? (newMaxCount > 9 ? 9 : newMaxCount) : 1,
+					sourceType: sourceType,
+					sizeType,
+					success: resolve,
+					fail: reject
+				});
+			});
+			chooseFile
+				.then(res => {
+					let file = null;
+					let listOldLength = this.lists.length;
+					res.tempFiles.map((val, index) => {
+						// 检查文件后缀是否允许,如果不在this.limitType内,就会返回false
+						if(!this.checkFileExt(val)) return ;
+						
+						// 如果是非多选,index大于等于1或者超出最大限制数量时,不处理
+						if (!multiple && index >= 1) return;
+						if (val.size > maxSize) {
+							this.$emit('on-oversize', val, this.lists, this.index);
+							this.showToast('超出允许的文件大小');
+						} else {
+							if (maxCount <= lists.length) {
+								this.$emit('on-exceed', val, this.lists, this.index);
+								this.showToast('超出最大允许的文件个数');
+								return;
+							}
+							lists.push({
+								url: val.path,
+								progress: 0,
+								error: false,
+								file: val
+							});
+						}
+					});
+					// 每次图片选择完,抛出一个事件,并将当前内部选择的图片数组抛出去
+					this.$emit('on-choose-complete', this.lists, this.index);
+					if (this.autoUpload) this.uploadFile(listOldLength);
+				})
+				.catch(error => {
+					this.$emit('on-choose-fail', error);
+				});
+		},
+		// 提示用户消息
+		showToast(message, force = false) {
+			if (this.showTips || force) {
+				uni.showToast({
+					title: message,
+					icon: 'none'
+				});
+			}
+		},
+		// 该方法供用户通过ref调用,手动上传
+		upload() {
+			this.uploadFile();
+		},
+		// 对失败的图片重新上传
+		retry(index) {
+			this.lists[index].progress = 0;
+			this.lists[index].error = false;
+			this.lists[index].response = null;
+			uni.showLoading({
+				title: '重新上传'
+			});
+			this.uploadFile(index);
+		},
+		// 上传图片
+		async uploadFile(index = 0) {
+			if (this.disabled) return;
+			if (this.uploading) return;
+			// 全部上传完成
+			if (index >= this.lists.length) {
+				this.$emit('on-uploaded', this.lists, this.index);
+				return;
+			}
+			// 检查是否是已上传或者正在上传中
+			if (this.lists[index].progress == 100) {
+				if (this.autoUpload == false) this.uploadFile(index + 1);
+				return;
+			}
+			// 执行before-upload钩子
+			if(this.beforeUpload && typeof(this.beforeUpload) === 'function') {
+				// 执行回调,同时传入索引和文件列表当作参数
+				// 在微信,支付宝等环境(H5正常),会导致父组件定义的customBack()函数体中的this变成子组件的this
+				// 通过bind()方法,绑定父组件的this,让this.customBack()的this为父组件的上下文
+				// 因为upload组件可能会被嵌套在其他组件内,比如u-form,这时this.$parent其实为u-form的this,
+				// 非页面的this,所以这里需要往上历遍,一直寻找到最顶端的$parent,这里用了this.$u.$parent.call(this)
+				// 明白意思即可,无需纠结this.$u.$parent.call(this)的细节
+				let beforeResponse = this.beforeUpload.bind(this.$u.$parent.call(this))(index, this.lists);
+				// 判断是否返回了promise
+				if (!!beforeResponse && typeof beforeResponse.then === 'function') {
+					await beforeResponse.then(res => {
+						// promise返回成功,不进行动作,继续上传
+					}).catch(err => {
+						// 进入catch回调的话,继续下一张
+						return this.uploadFile(index + 1);
+					})
+				} else if(beforeResponse === false) {
+					// 如果返回false,继续下一张图片的上传
+					return this.uploadFile(index + 1);
+				} else {
+					// 此处为返回"true"的情形,这里不写代码,就跳过此处,继续执行当前的上传逻辑
+				}
+			}
+			// 检查上传地址
+			if (!this.action) {
+				this.showToast('请配置上传地址', true);
+				return;
+			}
+			this.lists[index].error = false;
+			this.uploading = true;
+			// 创建上传对象
+			const task = uni.uploadFile({
+				url: this.action,
+				filePath: this.lists[index].url,
+				name: this.name,
+				formData: this.formData,
+				header: this.header,
+				success: res => {
+					// 判断是否json字符串,将其转为json格式
+					let data = this.toJson && this.$u.test.jsonString(res.data) ? JSON.parse(res.data) : res.data;
+					if (![200, 201, 204].includes(res.statusCode)) {
+						this.uploadError(index, data);
+					} else {
+						// 上传成功
+						this.lists[index].response = data;
+						this.lists[index].progress = 100;
+						this.lists[index].error = false;
+						this.$emit('on-success', data, index, this.lists, this.index);
+					}
+				},
+				fail: e => {
+					this.uploadError(index, e);
+				},
+				complete: res => {
+					uni.hideLoading();
+					this.uploading = false;
+					this.uploadFile(index + 1);
+					this.$emit('on-change', res, index, this.lists, this.index);
+				}
+			});
+			task.onProgressUpdate(res => {
+				if (res.progress > 0) {
+					this.lists[index].progress = res.progress;
+					this.$emit('on-progress', res, index, this.lists, this.index);
+				}
+			});
+		},
+		// 上传失败
+		uploadError(index, err) {
+			this.lists[index].progress = 0;
+			this.lists[index].error = true;
+			this.lists[index].response = null;
+			this.$emit('on-error', err, index, this.lists, this.index);
+			this.showToast('上传失败,请重试');
+		},
+		// 删除一个图片
+		deleteItem(index) {
+			uni.showModal({
+				title: '提示',
+				content: '您确定要删除此项吗?',
+				success: async (res) => {
+					if (res.confirm) {
+						// 先检查是否有定义before-remove移除前钩子
+						// 执行before-remove钩子
+						if(this.beforeRemove && typeof(this.beforeRemove) === 'function') {
+							// 此处钩子执行 原理同before-remove参数,见上方注释
+							let beforeResponse = this.beforeRemove.bind(this.$u.$parent.call(this))(index, this.lists);
+							// 判断是否返回了promise
+							if (!!beforeResponse && typeof beforeResponse.then === 'function') {
+								await beforeResponse.then(res => {
+									// promise返回成功,不进行动作,继续上传
+									this.handlerDeleteItem(index);
+								}).catch(err => {
+									// 如果进入promise的reject,终止删除操作
+									this.showToast('已终止移除');
+								})
+							} else if(beforeResponse === false) {
+								// 返回false,终止删除
+								this.showToast('已终止移除');
+							} else {
+								// 如果返回true,执行删除操作
+								this.handlerDeleteItem(index);
+							}
+						} else {
+							// 如果不存在before-remove钩子,
+							this.handlerDeleteItem(index);
+						}
+					}
+				}
+			});
+		},
+		// 执行移除图片的动作,上方代码只是判断是否可以移除
+		handlerDeleteItem(index) {
+			// 如果文件正在上传中,终止上传任务,进度在0 < progress < 100则意味着正在上传
+			if (this.lists[index].process < 100 && this.lists[index].process > 0) {
+				typeof this.lists[index].uploadTask != 'undefined' && this.lists[index].uploadTask.abort();
+			}
+			this.lists.splice(index, 1);
+			this.$forceUpdate();
+			this.$emit('on-remove', index, this.lists, this.index);
+			this.showToast('移除成功');
+		},
+		// 用户通过ref手动的形式,移除一张图片
+		remove(index) {
+			// 判断索引的合法范围
+			if (index >= 0 && index < this.lists.length) {
+				this.lists.splice(index, 1);
+				this.$emit('on-list-change', this.lists, this.index);
+			}
+		},
+		// 预览图片
+		doPreviewImage(url, index) {
+			if (!this.previewFullImage) return;
+			const images = this.lists.map(item => item.url || item.path);
+			uni.previewImage({
+				urls: images,
+				current: url,
+				success: () => {
+					this.$emit('on-preview', url, this.lists, this.index);
+				},
+				fail: () => {
+					uni.showToast({
+						title: '预览图片失败',
+						icon: 'none'
+					});
+				}
+			});
+		},
+		// 判断文件后缀是否允许
+		checkFileExt(file) {
+			// 检查是否在允许的后缀中
+			let noArrowExt = false;
+			// 获取后缀名
+			let fileExt = '';
+			const reg = /.+\./;
+			// 如果是H5,需要从name中判断
+			// #ifdef H5
+			fileExt = file.name.replace(reg, "").toLowerCase();
+			// #endif
+			// 非H5,需要从path中读取后缀
+			// #ifndef H5
+			fileExt = file.path.replace(reg, "").toLowerCase();
+			// #endif
+			// 使用数组的some方法,只要符合limitType中的一个,就返回true
+			noArrowExt = this.limitType.some(ext => {
+				// 转为小写
+				return ext.toLowerCase() === fileExt;
+			})
+			if(!noArrowExt) this.showToast(`不允许选择${fileExt}格式的文件`);
+			return noArrowExt;
+		}
+	}
+};
+</script>
+
+<style lang="scss" scoped>
+@import '../../libs/css/style.components.scss';
+
+.u-upload {
+	@include vue-flex;
+	flex-wrap: wrap;
+	align-items: center;
+}
+
+.u-list-item {
+	width: 200rpx;
+	height: 200rpx;
+	overflow: hidden;
+	margin: 10rpx;
+	background: rgb(244, 245, 246);
+	position: relative;
+	border-radius: 10rpx;
+	/* #ifndef APP-NVUE */
+	display: flex;
+	/* #endif */
+	align-items: center;
+	justify-content: center;
+}
+
+.u-preview-wrap {
+	border: 1px solid rgb(235, 236, 238);
+}
+
+.u-add-wrap {
+	flex-direction: column;
+	color: $u-content-color;
+	font-size: 26rpx;
+}
+
+.u-add-tips {
+	margin-top: 20rpx;
+	line-height: 40rpx;
+}
+
+.u-add-wrap__hover {
+	background-color: rgb(235, 236, 238);
+}
+
+.u-preview-image {
+	display: block;
+	width: 100%;
+	height: 100%;
+	border-radius: 10rpx;
+}
+
+.u-delete-icon {
+	position: absolute;
+	top: 10rpx;
+	right: 10rpx;
+	z-index: 10;
+	background-color: $u-type-error;
+	border-radius: 100rpx;
+	width: 44rpx;
+	height: 44rpx;
+	@include vue-flex;
+	align-items: center;
+	justify-content: center;
+}
+
+.u-icon {
+	@include vue-flex;
+	align-items: center;
+	justify-content: center;
+}
+
+.u-progress {
+	position: absolute;
+	bottom: 10rpx;
+	left: 8rpx;
+	right: 8rpx;
+	z-index: 9;
+	width: auto;
+}
+
+.u-error-btn {
+	color: #ffffff;
+	background-color: $u-type-error;
+	font-size: 20rpx;
+	padding: 4px 0;
+	text-align: center;
+	position: absolute;
+	bottom: 0;
+	left: 0;
+	right: 0;
+	z-index: 9;
+	line-height: 1;
+}
+</style>

Dosya farkı çok büyük olduğundan ihmal edildi
+ 910 - 0
uview-ui/iconfont.css


+ 141 - 0
uview-ui/index.js

@@ -0,0 +1,141 @@
+// 引入全局mixin
+import mixin from './libs/mixin/mixin.js'
+// 引入关于是否mixin集成小程序分享的配置
+// import wxshare from './libs/mixin/mpShare.js'
+// 全局挂载引入http相关请求拦截插件
+import http from './libs/request'
+
+function wranning(str) {
+	// 开发环境进行信息输出,主要是一些报错信息
+	// 这个环境的来由是在程序编写时候,点击hx编辑器运行调试代码的时候,详见:
+	// 	https://uniapp.dcloud.io/frame?id=%e5%bc%80%e5%8f%91%e7%8e%af%e5%a2%83%e5%92%8c%e7%94%9f%e4%ba%a7%e7%8e%af%e5%a2%83
+	if (process.env.NODE_ENV === 'development') {
+		console.warn(str)
+	}
+}
+
+// 尝试判断在根目录的/store中是否有$u.mixin.js,此文件uView默认为需要挂在到全局的vuex的state变量
+// HX2.6.11版本,放到try中,控制台依然会警告,暂时不用此方式,
+// let vuexStore = {};
+// try {
+// 	vuexStore = require("@/store/$u.mixin.js");
+// } catch (e) {
+// 	//TODO handle the exception
+// }
+
+// post类型对象参数转为get类型url参数
+import queryParams from './libs/function/queryParams.js'
+// 路由封装
+import route from './libs/function/route.js'
+// 时间格式化
+import timeFormat from './libs/function/timeFormat.js'
+// 时间戳格式化,返回多久之前
+import timeFrom from './libs/function/timeFrom.js'
+// 颜色渐变相关,colorGradient-颜色渐变,hexToRgb-十六进制颜色转rgb颜色,rgbToHex-rgb转十六进制
+import colorGradient from './libs/function/colorGradient.js'
+// 生成全局唯一guid字符串
+import guid from './libs/function/guid.js'
+// 主题相关颜色,info|success|warning|primary|default|error,此颜色已在uview.scss中定义,但是为js中也能使用,故也定义一份
+import color from './libs/function/color.js'
+// 根据type获取图标名称
+import type2icon from './libs/function/type2icon.js'
+// 打乱数组的顺序
+import randomArray from './libs/function/randomArray.js'
+// 对象和数组的深度克隆
+import deepClone from './libs/function/deepClone.js'
+// 对象深度拷贝
+import deepMerge from './libs/function/deepMerge.js'
+// 添加单位
+import addUnit from './libs/function/addUnit.js'
+
+// 规则检验
+import test from './libs/function/test.js'
+// 随机数
+import random from './libs/function/random.js'
+// 去除空格
+import trim from './libs/function/trim.js'
+// toast提示,对uni.showToast的封装
+import toast from './libs/function/toast.js'
+// 获取父组件参数
+import getParent from './libs/function/getParent.js'
+// 获取整个父组件
+import $parent from './libs/function/$parent.js'
+// 获取sys()和os()工具方法
+// 获取设备信息,挂载到$u的sys()(system的缩写)属性中,
+// 同时把安卓和ios平台的名称"ios"和"android"挂到$u.os()中,方便取用
+import {sys, os} from './libs/function/sys.js'
+// 防抖方法
+import debounce from './libs/function/debounce.js'
+// 节流方法
+import throttle from './libs/function/throttle.js'
+
+
+// 配置信息
+import config from './libs/config/config.js'
+// 各个需要fixed的地方的z-index配置文件
+import zIndex from './libs/config/zIndex.js'
+
+const $u = {
+	queryParams: queryParams,
+	route: route,
+	timeFormat: timeFormat,
+	date: timeFormat, // 另名date
+	timeFrom,
+	colorGradient: colorGradient.colorGradient,
+	colorToRgba: colorGradient.colorToRgba,
+	guid,
+	color,
+	sys,
+	os,
+	type2icon,
+	randomArray,
+	wranning,
+	get: http.get,
+	post: http.post,
+	put: http.put,
+	'delete': http.delete,
+	hexToRgb: colorGradient.hexToRgb,
+	rgbToHex: colorGradient.rgbToHex,
+	test,
+	random,
+	deepClone,
+	deepMerge,
+	getParent,
+	$parent,
+	addUnit,
+	trim,
+	type: ['primary', 'success', 'error', 'warning', 'info'],
+	http,
+	toast,
+	config, // uView配置信息相关,比如版本号
+	zIndex,
+	debounce,
+	throttle,
+}
+
+// $u挂载到uni对象上
+uni.$u = $u
+
+const install = Vue => {
+	Vue.mixin(mixin) 
+	if (Vue.prototype.openShare) {
+		Vue.mixin(mpShare);
+	}
+	// Vue.mixin(vuexStore);
+	// 时间格式化,同时两个名称,date和timeFormat
+	Vue.filter('timeFormat', (timestamp, format) => {
+		return timeFormat(timestamp, format)
+	})
+	Vue.filter('date', (timestamp, format) => {
+		return timeFormat(timestamp, format)
+	})
+	// 将多久以前的方法,注入到全局过滤器
+	Vue.filter('timeFrom', (timestamp, format) => {
+		return timeFrom(timestamp, format)
+	})
+	Vue.prototype.$u = $u
+}
+
+export default {
+	install
+}

+ 23 - 0
uview-ui/index.scss

@@ -0,0 +1,23 @@
+// 引入公共基础类
+@import "./libs/css/common.scss";
+@import "./libs/css/color.scss";
+
+// 非nvue的样式
+/* #ifndef APP-NVUE */
+@import "./libs/css/style.vue.scss";
+/* #endif */
+
+// nvue的特有样式
+/* #ifdef APP-NVUE */
+@import "./libs/css/style.nvue.scss";
+/* #endif */
+
+// 小程序特有的样式
+/* #ifdef MP */
+@import "./libs/css/style.mp.scss";
+/* #endif */
+
+// H5特有的样式
+/* #ifdef H5 */
+@import "./libs/css/style.h5.scss";
+/* #endif */

+ 15 - 0
uview-ui/libs/config/config.js

@@ -0,0 +1,15 @@
+// 此版本发布于2020-03-17
+let version = '1.8.4';
+
+export default {
+	v: version,
+	version: version,
+	// 主题名称
+	type: [
+		'primary',
+		'success',
+		'info',
+		'error',
+		'warning'
+	]
+}

+ 20 - 0
uview-ui/libs/config/zIndex.js

@@ -0,0 +1,20 @@
+// uniapp在H5中各API的z-index值如下:
+/**
+ * actionsheet: 999
+ * modal: 999
+ * navigate: 998
+ * tabbar: 998
+ * toast: 999
+ */
+
+export default {
+	toast: 10090,
+	noNetwork: 10080,
+	// popup包含popup,actionsheet,keyboard,picker的值
+	popup: 10075,
+	mask: 10070,
+	navbar: 980,
+	topTips: 975,
+	sticky: 970,
+	indexListSticky: 965,
+}

+ 155 - 0
uview-ui/libs/css/color.scss

@@ -0,0 +1,155 @@
+.u-type-primary-light {
+	color: $u-type-primary-light;
+}
+
+.u-type-warning-light {
+	color: $u-type-warning-light;
+}
+
+.u-type-success-light {
+	color: $u-type-success-light;
+}
+
+.u-type-error-light {
+	color: $u-type-error-light;
+}
+
+.u-type-info-light {
+	color: $u-type-info-light;
+}
+
+.u-type-primary-light-bg {
+	background-color: $u-type-primary-light;
+}
+
+.u-type-warning-light-bg {
+	background-color: $u-type-warning-light;
+}
+
+.u-type-success-light-bg {
+	background-color: $u-type-success-light;
+}
+
+.u-type-error-light-bg {
+	background-color: $u-type-error-light;
+}
+
+.u-type-info-light-bg {
+	background-color: $u-type-info-light;
+}
+
+.u-type-primary-dark {
+	color: $u-type-primary-dark;
+}
+
+.u-type-warning-dark {
+	color: $u-type-warning-dark;
+}
+
+.u-type-success-dark {
+	color: $u-type-success-dark;
+}
+
+.u-type-error-dark {
+	color: $u-type-error-dark;
+}
+
+.u-type-info-dark {
+	color: $u-type-info-dark;
+}
+
+.u-type-primary-dark-bg {
+	background-color: $u-type-primary-dark;
+}
+
+.u-type-warning-dark-bg {
+	background-color: $u-type-warning-dark;
+}
+
+.u-type-success-dark-bg {
+	background-color: $u-type-success-dark;
+}
+
+.u-type-error-dark-bg {
+	background-color: $u-type-error-dark;
+}
+
+.u-type-info-dark-bg {
+	background-color: $u-type-info-dark;
+}
+
+.u-type-primary-disabled {
+	color: $u-type-primary-disabled;
+}
+
+.u-type-warning-disabled {
+	color: $u-type-warning-disabled;
+}
+
+.u-type-success-disabled {
+	color: $u-type-success-disabled;
+}
+
+.u-type-error-disabled {
+	color: $u-type-error-disabled;
+}
+
+.u-type-info-disabled {
+	color: $u-type-info-disabled;
+}
+
+.u-type-primary {
+	color: $u-type-primary;
+}
+
+.u-type-warning {
+	color: $u-type-warning;
+}
+
+.u-type-success {
+	color: $u-type-success;
+}
+
+.u-type-error {
+	color: $u-type-error;
+}
+
+.u-type-info {
+	color: $u-type-info;
+}
+
+.u-type-primary-bg {
+	background-color: $u-type-primary;
+}
+
+.u-type-warning-bg {
+	background-color: $u-type-warning;
+}
+
+.u-type-success-bg {
+	background-color: $u-type-success;
+}
+
+.u-type-error-bg {
+	background-color: $u-type-error;
+}
+
+.u-type-info-bg {
+	background-color: $u-type-info;
+}
+
+.u-main-color {
+	color: $u-main-color;
+}
+
+.u-content-color {
+	color: $u-content-color;
+}
+
+.u-tips-color {
+	color: $u-tips-color;
+}
+
+.u-light-color {
+	color: $u-light-color;
+}

+ 176 - 0
uview-ui/libs/css/common.scss

@@ -0,0 +1,176 @@
+.u-relative,
+.u-rela {
+	position: relative;
+}
+
+.u-absolute,
+.u-abso {
+	position: absolute;
+}
+
+// nvue不能用标签命名样式,不能放在微信组件中,否则微信开发工具会报警告,无法使用标签名当做选择器
+/* #ifndef APP-NVUE */
+image {
+	display: inline-block;
+}
+
+// 在weex,也即nvue中,所有元素默认为border-box
+view,
+text {
+	box-sizing: border-box;
+}
+/* #endif */
+
+.u-font-xs {
+	font-size: 22rpx;
+}
+
+.u-font-sm {
+	font-size: 26rpx;
+}
+
+.u-font-md {
+	font-size: 28rpx;
+}
+
+.u-font-lg {
+	font-size: 30rpx;
+}
+
+.u-font-xl {
+	font-size: 34rpx;
+}
+
+.u-flex {
+	/* #ifndef APP-NVUE */
+	display: flex;
+	/* #endif */
+	flex-direction: row;
+	align-items: center;
+}
+
+.u-flex-wrap {
+	flex-wrap: wrap;
+}
+
+.u-flex-nowrap {
+	flex-wrap: nowrap;
+}
+
+.u-col-center {
+	align-items: center;
+}
+
+.u-col-top {
+	align-items: flex-start;
+}
+
+.u-col-bottom {
+	align-items: flex-end;
+}
+
+.u-row-center {
+	justify-content: center;
+}
+
+.u-row-left {
+	justify-content: flex-start;
+}
+
+.u-row-right {
+	justify-content: flex-end;
+}
+
+.u-row-between {
+	justify-content: space-between;
+}
+
+.u-row-around {
+	justify-content: space-around;
+}
+
+.u-text-left {
+	text-align: left;
+}
+
+.u-text-center {
+	text-align: center;
+}
+
+.u-text-right {
+	text-align: right;
+}
+
+.u-flex-col {
+	/* #ifndef APP-NVUE */
+	display: flex;
+	/* #endif */
+	flex-direction: column;
+}
+
+// 定义flex等分
+@for $i from 0 through 12 {
+	.u-flex-#{$i} {
+		flex: $i;
+	}
+}
+
+// 定义字体(px)单位,小于20都为px单位字体
+@for $i from 9 to 20 {
+	.u-font-#{$i} {
+		font-size: $i + px;
+	}
+}
+
+// 定义字体(rpx)单位,大于或等于20的都为rpx单位字体
+@for $i from 20 through 40 {
+	.u-font-#{$i} {
+		font-size: $i + rpx;
+	}
+}
+
+// 定义内外边距,历遍1-80
+@for $i from 0 through 80 {
+	// 只要双数和能被5除尽的数
+	@if $i % 2 == 0 or $i % 5 == 0 {
+		// 得出:u-margin-30或者u-m-30
+		.u-margin-#{$i}, .u-m-#{$i} {
+			margin: $i + rpx!important;
+		}
+		
+		// 得出:u-padding-30或者u-p-30
+		.u-padding-#{$i}, .u-p-#{$i} {
+			padding: $i + rpx!important;
+		}
+		
+		@each $short, $long in l left, t top, r right, b bottom {
+			// 缩写版,结果如: u-m-l-30
+			// 定义外边距
+			.u-m-#{$short}-#{$i} {
+				margin-#{$long}: $i + rpx!important;
+			}
+			
+			// 定义内边距
+			.u-p-#{$short}-#{$i} {
+				padding-#{$long}: $i + rpx!important;
+			}
+			
+			// 完整版,结果如:u-margin-left-30
+			// 定义外边距
+			.u-margin-#{$long}-#{$i} {
+				margin-#{$long}: $i + rpx!important;
+			}
+			
+			// 定义内边距
+			.u-padding-#{$long}-#{$i} {
+				padding-#{$long}: $i + rpx!important;
+			}
+		}
+	}
+}
+
+// 重置nvue的默认关于flex的样式
+.u-reset-nvue {
+	flex-direction: row;
+	align-items: center;
+}

+ 7 - 0
uview-ui/libs/css/style.components.scss

@@ -0,0 +1,7 @@
+// 定义混入指令,用于在非nvue环境下的flex定义,因为nvue没有display属性,会报错
+@mixin vue-flex($direction: row) {
+	/* #ifndef APP-NVUE */
+	display: flex;
+	flex-direction: $direction;
+	/* #endif */
+}

+ 8 - 0
uview-ui/libs/css/style.h5.scss

@@ -0,0 +1,8 @@
+/* H5的时候,隐藏滚动条 */
+::-webkit-scrollbar {
+	display: none;  
+	width: 0 !important;  
+	height: 0 !important;  
+	-webkit-appearance: none;  
+	background: transparent;  
+}

+ 72 - 0
uview-ui/libs/css/style.mp.scss

@@ -0,0 +1,72 @@
+/* start--微信小程序编译后页面有组件名的元素,特别处理--start */
+/* #ifdef MP-WEIXIN || MP-QQ */
+u-td, u-th {
+	flex: 1;
+	align-self: stretch;
+}
+
+.u-td {
+	height: 100%;
+}
+
+u-icon {
+	display: inline-flex;
+	align-items: center;
+}
+
+// 各家小程序宫格组件外层设置为100%,避免受到父元素display: flex;的影响
+u-grid {
+	width: 100%;
+	flex: 0 0 100%;
+}
+
+// 避免小程序线条组件因为父组件display: flex;而失效
+u-line {
+	flex: 1;
+}
+
+u-switch {
+	display: inline-flex;
+	align-items: center;
+}
+
+u-dropdown {
+	flex: 1;
+}
+/* #endif */
+/* end-微信小程序编译后页面有组件名的元素,特别处理--end */
+
+
+/* #ifdef MP-QQ || MP-TOUTIAO */
+// 需要做这一切额外的兼容,都是因为TX的无能
+u-icon {
+	line-height: 0;
+}
+/* #endif */
+
+/* start--头条小程序编译后页面有组件名的元素,特别处理--start */
+// 由于头条小程序不支持直接组件名形式写样式,目前只能在写组件的时候给组件加上对应的类名
+/* #ifdef MP-TOUTIAO */
+.u-td, .u-th, .u-tr {
+	flex: 1;
+	align-self: stretch;
+}
+
+.u-row, .u-col {
+	flex: 1;
+	align-self: stretch;
+}
+
+// 避免小程序线条组件因为父组件display: flex;而失效
+.u-line {
+	flex: 1;
+}
+
+.u-dropdown {
+	flex: 1;
+}
+/* #endif */
+/* end-头条小程序编译后页面有组件名的元素,特别处理--end */
+
+
+

+ 3 - 0
uview-ui/libs/css/style.nvue.scss

@@ -0,0 +1,3 @@
+.nvue {
+	font-size: 24rpx;
+}

+ 175 - 0
uview-ui/libs/css/style.vue.scss

@@ -0,0 +1,175 @@
+page {
+	color: $u-main-color;
+	font-size: 28rpx;
+}
+
+/* start--去除webkit的默认样式--start */
+.u-fix-ios-appearance {
+	-webkit-appearance:none;
+}
+/* end--去除webkit的默认样式--end */
+
+/* start--icon图标外层套一个view,让其达到更好的垂直居中的效果--start */
+.u-icon-wrap {
+	display: flex;
+	align-items: center;
+}
+/* end-icon图标外层套一个view,让其达到更好的垂直居中的效果--end */
+
+/* start--iPhoneX底部安全区定义--start */
+.safe-area-inset-bottom {
+  padding-bottom: 0;  
+  padding-bottom: constant(safe-area-inset-bottom);  
+  padding-bottom: env(safe-area-inset-bottom);  
+} 
+/* end-iPhoneX底部安全区定义--end */
+
+/* start--各种hover点击反馈相关的类名-start */
+.u-hover-class {
+	// background-color: #f7f8f9!important;
+	opacity: 0.6;
+}
+
+.u-cell-hover {
+	background-color: #f7f8f9!important;
+}
+/* end--各种hover点击反馈相关的类名--end */
+
+/* start--文本行数限制--start */
+.u-line-1 {
+    overflow: hidden;
+    white-space: nowrap;
+    text-overflow: ellipsis;
+}
+
+.u-line-2 {
+    -webkit-line-clamp: 2;
+}
+
+.u-line-3 {
+    -webkit-line-clamp: 3;
+}
+
+.u-line-4 {
+    -webkit-line-clamp: 4;
+}
+
+.u-line-5 {
+    -webkit-line-clamp: 5;
+}
+
+.u-line-2, .u-line-3, .u-line-4, .u-line-5 {
+    overflow: hidden;
+	word-break: break-all;
+    text-overflow: ellipsis; 
+    display: -webkit-box; // 弹性伸缩盒
+    -webkit-box-orient: vertical; // 设置伸缩盒子元素排列方式
+}
+
+/* end--文本行数限制--end */
+
+
+/* start--Retina 屏幕下的 1px 边框--start */
+.u-border,
+.u-border-bottom,
+.u-border-left,
+.u-border-right,
+.u-border-top,
+.u-border-top-bottom {
+	position: relative
+}
+
+.u-border-bottom:after,
+.u-border-left:after,
+.u-border-right:after,
+.u-border-top-bottom:after,
+.u-border-top:after,
+.u-border:after {
+	/* #ifndef APP-NVUE */
+	content: ' ';
+	/* #endif */
+	position: absolute;
+	left: 0;
+	top: 0;
+	pointer-events: none;
+	box-sizing: border-box;
+	-webkit-transform-origin: 0 0;
+	transform-origin: 0 0;
+	// 多加0.1%,能解决有时候边框缺失的问题
+	width: 199.8%;
+	height: 199.7%;
+	transform: scale(0.5, 0.5);
+	border: 0 solid $u-border-color;
+	z-index: 2;
+}
+
+.u-border-top:after {
+	border-top-width: 1px
+}
+
+.u-border-left:after {
+	border-left-width: 1px
+}
+
+.u-border-right:after {
+	border-right-width: 1px
+}
+
+.u-border-bottom:after {
+	border-bottom-width: 1px
+}
+
+.u-border-top-bottom:after {
+	border-width: 1px 0
+}
+
+.u-border:after {
+	border-width: 1px
+}
+/* end--Retina 屏幕下的 1px 边框--end */
+
+
+/* start--clearfix--start */
+.u-clearfix:after,
+.clearfix:after {
+	/* #ifndef APP-NVUE */
+	content: '';
+	/* #endif */
+	display: table;
+	clear: both
+}
+/* end--clearfix--end */
+
+/* start--高斯模糊tabbar底部处理--start */
+.u-blur-effect-inset {
+	width: 750rpx;  
+	height: var(--window-bottom);   
+	background-color: #FFFFFF;  
+}
+/* end--高斯模糊tabbar底部处理--end */
+
+/* start--提升H5端uni.toast()的层级,避免被uView的modal等遮盖--start */
+/* #ifdef H5 */
+uni-toast {
+    z-index: 10090;
+}
+uni-toast .uni-toast {
+   z-index: 10090;
+}
+/* #endif */
+/* end--提升H5端uni.toast()的层级,避免被uView的modal等遮盖--end */
+
+/* start--去除button的所有默认样式--start */
+.u-reset-button {
+	padding: 0;
+	font-size: inherit;
+	line-height: inherit;
+	background-color: transparent;
+	color: inherit;
+}
+
+.u-reset-button::after {
+   border: none;
+}
+/* end--去除button的所有默认样式--end */
+

+ 18 - 0
uview-ui/libs/function/$parent.js

@@ -0,0 +1,18 @@
+// 获取父组件的参数,因为支付宝小程序不支持provide/inject的写法
+// this.$parent在非H5中,可以准确获取到父组件,但是在H5中,需要多次this.$parent.$parent.xxx
+// 这里默认值等于undefined有它的含义,因为最顶层元素(组件)的$parent就是undefined,意味着不传name
+// 值(默认为undefined),就是查找最顶层的$parent
+export default function $parent(name = undefined) {
+	let parent = this.$parent;
+	// 通过while历遍,这里主要是为了H5需要多层解析的问题
+	while (parent) {
+		// 父组件
+		if (parent.$options && parent.$options.name !== name) {
+			// 如果组件的name不相等,继续上一级寻找
+			parent = parent.$parent;
+		} else {
+			return parent;
+		}
+	}
+	return false;
+}

+ 8 - 0
uview-ui/libs/function/addUnit.js

@@ -0,0 +1,8 @@
+import validation from './test.js';
+
+// 添加单位,如果有rpx,%,px等单位结尾或者值为auto,直接返回,否则加上rpx单位结尾
+export default function addUnit(value = 'auto', unit = 'rpx') {
+    value = String(value);
+	// 用uView内置验证规则中的number判断是否为数值
+    return validation.number(value) ? `${value}${unit}` : value;
+}

+ 5 - 0
uview-ui/libs/function/bem.js

@@ -0,0 +1,5 @@
+function bem(name, conf) {
+  
+}
+
+module.exports.bem = bem;

+ 37 - 0
uview-ui/libs/function/color.js

@@ -0,0 +1,37 @@
+// 为了让用户能够自定义主题,会逐步弃用此文件,各颜色通过css提供
+// 为了给某些特殊场景使用和向后兼容,无需删除此文件(2020-06-20)
+let color = {
+	primary: "#2979ff",
+	primaryDark: "#2b85e4",
+	primaryDisabled: "#a0cfff",
+	primaryLight: "#ecf5ff",
+	bgColor: "#f3f4f6",
+	
+	info: "#909399",
+	infoDark: "#82848a",
+	infoDisabled: "#c8c9cc",
+	infoLight: "#f4f4f5",
+	
+	warning: "#ff9900",
+	warningDark: "#f29100",
+	warningDisabled: "#fcbd71",
+	warningLight: "#fdf6ec",
+	
+	error: "#fa3534",
+	errorDark: "#dd6161",
+	errorDisabled: "#fab6b6",
+	errorLight: "#fef0f0",
+	
+	success: "#19be6b",
+	successDark: "#18b566",
+	successDisabled: "#71d5a1",
+	successLight: "#dbf1e1",
+	
+	mainColor: "#303133",
+	contentColor: "#606266",
+	tipsColor: "#909399",
+	lightColor: "#c0c4cc",
+	borderColor: "#e4e7ed"
+}
+
+export default color;

+ 134 - 0
uview-ui/libs/function/colorGradient.js

@@ -0,0 +1,134 @@
+/**
+ * 求两个颜色之间的渐变值
+ * @param {string} startColor 开始的颜色
+ * @param {string} endColor 结束的颜色
+ * @param {number} step 颜色等分的份额
+ * */
+function colorGradient(startColor = 'rgb(0, 0, 0)', endColor = 'rgb(255, 255, 255)', step = 10) {
+	let startRGB = hexToRgb(startColor, false); //转换为rgb数组模式
+	let startR = startRGB[0];
+	let startG = startRGB[1];
+	let startB = startRGB[2];
+
+	let endRGB = hexToRgb(endColor, false);
+	let endR = endRGB[0];
+	let endG = endRGB[1];
+	let endB = endRGB[2];
+
+	let sR = (endR - startR) / step; //总差值
+	let sG = (endG - startG) / step;
+	let sB = (endB - startB) / step;
+	let colorArr = [];
+	for (let i = 0; i < step; i++) {
+		//计算每一步的hex值 
+		let hex = rgbToHex('rgb(' + Math.round((sR * i + startR)) + ',' + Math.round((sG * i + startG)) + ',' + Math.round((sB *
+			i + startB)) + ')');
+		colorArr.push(hex);
+	}
+	return colorArr;
+}
+
+// 将hex表示方式转换为rgb表示方式(这里返回rgb数组模式)
+function hexToRgb(sColor, str = true) {
+	let reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/;
+	sColor = sColor.toLowerCase();
+	if (sColor && reg.test(sColor)) {
+		if (sColor.length === 4) {
+			let sColorNew = "#";
+			for (let i = 1; i < 4; i += 1) {
+				sColorNew += sColor.slice(i, i + 1).concat(sColor.slice(i, i + 1));
+			}
+			sColor = sColorNew;
+		}
+		//处理六位的颜色值
+		let sColorChange = [];
+		for (let i = 1; i < 7; i += 2) {
+			sColorChange.push(parseInt("0x" + sColor.slice(i, i + 2)));
+		}
+		if(!str) {
+			return sColorChange;
+		} else {
+			return `rgb(${sColorChange[0]},${sColorChange[1]},${sColorChange[2]})`;
+		}
+	} else if (/^(rgb|RGB)/.test(sColor)) {
+		let arr = sColor.replace(/(?:\(|\)|rgb|RGB)*/g, "").split(",")
+		return arr.map(val => Number(val));
+	} else {
+		return sColor;
+	}
+};
+
+// 将rgb表示方式转换为hex表示方式
+function rgbToHex(rgb) {
+	let _this = rgb;
+	let reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/;
+	if (/^(rgb|RGB)/.test(_this)) {
+		let aColor = _this.replace(/(?:\(|\)|rgb|RGB)*/g, "").split(",");
+		let strHex = "#";
+		for (let i = 0; i < aColor.length; i++) {
+			let hex = Number(aColor[i]).toString(16);
+			hex = String(hex).length == 1 ? 0 + '' + hex : hex; // 保证每个rgb的值为2位
+			if (hex === "0") {
+				hex += hex;
+			}
+			strHex += hex;
+		}
+		if (strHex.length !== 7) {
+			strHex = _this;
+		}
+		return strHex;
+	} else if (reg.test(_this)) {
+		let aNum = _this.replace(/#/, "").split("");
+		if (aNum.length === 6) {
+			return _this;
+		} else if (aNum.length === 3) {
+			let numHex = "#";
+			for (let i = 0; i < aNum.length; i += 1) {
+				numHex += (aNum[i] + aNum[i]);
+			}
+			return numHex;
+		}
+	} else {
+		return _this;
+	}
+}
+
+
+/**
+* JS颜色十六进制转换为rgb或rgba,返回的格式为 rgba(255,255,255,0.5)字符串
+* sHex为传入的十六进制的色值
+* alpha为rgba的透明度
+*/
+function colorToRgba(color, alpha = 0.3) {
+	color = rgbToHex(color)
+	// 十六进制颜色值的正则表达式
+	var reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/
+	/* 16进制颜色转为RGB格式 */
+	let sColor = color.toLowerCase()
+	if (sColor && reg.test(sColor)) {
+		if (sColor.length === 4) {
+			var sColorNew = '#'
+			for (let i = 1; i < 4; i += 1) {
+				sColorNew += sColor.slice(i, i + 1).concat(sColor.slice(i, i + 1))
+			}
+			sColor = sColorNew
+		}
+		// 处理六位的颜色值
+		var sColorChange = []
+		for (let i = 1; i < 7; i += 2) {
+			sColorChange.push(parseInt('0x' + sColor.slice(i, i + 2)))
+		}
+		// return sColorChange.join(',')
+		return 'rgba(' + sColorChange.join(',') + ',' + alpha + ')'
+	} 
+	else {
+		return sColor
+	}
+}
+
+export default {
+	colorGradient,
+	hexToRgb,
+	rgbToHex,
+	colorToRgba
+}

+ 29 - 0
uview-ui/libs/function/debounce.js

@@ -0,0 +1,29 @@
+let timeout = null;
+
+/**
+ * 防抖原理:一定时间内,只有最后一次操作,再过wait毫秒后才执行函数
+ * 
+ * @param {Function} func 要执行的回调函数 
+ * @param {Number} wait 延时的时间
+ * @param {Boolean} immediate 是否立即执行 
+ * @return null
+ */
+function debounce(func, wait = 500, immediate = false) {
+	// 清除定时器
+	if (timeout !== null) clearTimeout(timeout);
+	// 立即执行,此类情况一般用不到
+	if (immediate) {
+		var callNow = !timeout;
+		timeout = setTimeout(function() {
+			timeout = null;
+		}, wait);
+		if (callNow) typeof func === 'function' && func();
+	} else {
+		// 设置定时器,当最后一次操作后,timeout不会再被清除,所以在延时wait毫秒后执行func回调方法
+		timeout = setTimeout(function() {
+			typeof func === 'function' && func();
+		}, wait);
+	}
+}
+
+export default debounce

+ 23 - 0
uview-ui/libs/function/deepClone.js

@@ -0,0 +1,23 @@
+// 判断arr是否为一个数组,返回一个bool值
+function isArray (arr) {
+    return Object.prototype.toString.call(arr) === '[object Array]';
+}
+
+// 深度克隆
+function deepClone (obj) {
+	// 对常见的“非”值,直接返回原来值
+	if([null, undefined, NaN, false].includes(obj)) return obj;
+    if(typeof obj !== "object" && typeof obj !== 'function') {
+		//原始类型直接返回
+        return obj;
+    }
+    var o = isArray(obj) ? [] : {};
+    for(let i in obj) {
+        if(obj.hasOwnProperty(i)){
+            o[i] = typeof obj[i] === "object" ? deepClone(obj[i]) : obj[i];
+        }
+    }
+    return o;
+}
+
+export default deepClone;

+ 30 - 0
uview-ui/libs/function/deepMerge.js

@@ -0,0 +1,30 @@
+import deepClone from "./deepClone";
+
+// JS对象深度合并
+function deepMerge(target = {}, source = {}) {
+	target = deepClone(target);
+	if (typeof target !== 'object' || typeof source !== 'object') return false;
+	for (var prop in source) {
+		if (!source.hasOwnProperty(prop)) continue;
+		if (prop in target) {
+			if (typeof target[prop] !== 'object') {
+				target[prop] = source[prop];
+			} else {
+				if (typeof source[prop] !== 'object') {
+					target[prop] = source[prop];
+				} else {
+					if (target[prop].concat && source[prop].concat) {
+						target[prop] = target[prop].concat(source[prop]);
+					} else {
+						target[prop] = deepMerge(target[prop], source[prop]);
+					}
+				}
+			}
+		} else {
+			target[prop] = source[prop];
+		}
+	}
+	return target;
+}
+
+export default deepMerge;

+ 47 - 0
uview-ui/libs/function/getParent.js

@@ -0,0 +1,47 @@
+// 获取父组件的参数,因为支付宝小程序不支持provide/inject的写法
+// this.$parent在非H5中,可以准确获取到父组件,但是在H5中,需要多次this.$parent.$parent.xxx
+export default function getParent(name, keys) {
+	let parent = this.$parent;
+	// 通过while历遍,这里主要是为了H5需要多层解析的问题
+	while (parent) {
+		// 父组件
+		if (parent.$options.name !== name) {
+			// 如果组件的name不相等,继续上一级寻找
+			parent = parent.$parent;
+		} else {
+			let data = {};
+			// 判断keys是否数组,如果传过来的是一个数组,那么直接使用数组元素值当做键值去父组件寻找
+			if(Array.isArray(keys)) {
+				keys.map(val => {
+					data[val] = parent[val] ? parent[val] : '';
+				})
+			} else {
+				// 历遍传过来的对象参数
+				for(let i in keys) {
+					// 如果子组件有此值则用,无此值则用父组件的值
+					// 判断是否空数组,如果是,则用父组件的值,否则用子组件的值
+					if(Array.isArray(keys[i])) {
+						if(keys[i].length) {
+							data[i] = keys[i];
+						} else {
+							data[i] = parent[i];
+						}
+					} else if(keys[i].constructor === Object) {
+						// 判断是否对象,如果是对象,且有属性,那么使用子组件的值,否则使用父组件的值
+						if(Object.keys(keys[i]).length) {
+							data[i] = keys[i];
+						} else {
+							data[i] = parent[i];
+						}
+					} else {
+						// 只要子组件有传值,即使是false值,也是“传值”了,也需要覆盖父组件的同名参数
+						data[i] = (keys[i] || keys[i] === false) ? keys[i] : parent[i];
+					}
+				}
+			}
+			return data;
+		}
+	}
+
+	return {};
+}

+ 41 - 0
uview-ui/libs/function/guid.js

@@ -0,0 +1,41 @@
+/**
+ * 本算法来源于简书开源代码,详见:https://www.jianshu.com/p/fdbf293d0a85
+ * 全局唯一标识符(uuid,Globally Unique Identifier),也称作 uuid(Universally Unique IDentifier) 
+ * 一般用于多个组件之间,给它一个唯一的标识符,或者v-for循环的时候,如果使用数组的index可能会导致更新列表出现问题
+ * 最可能的情况是左滑删除item或者对某条信息流"不喜欢"并去掉它的时候,会导致组件内的数据可能出现错乱
+ * v-for的时候,推荐使用后端返回的id而不是循环的index
+ * @param {Number} len uuid的长度
+ * @param {Boolean} firstU 将返回的首字母置为"u"
+ * @param {Nubmer} radix 生成uuid的基数(意味着返回的字符串都是这个基数),2-二进制,8-八进制,10-十进制,16-十六进制
+ */
+function guid(len = 32, firstU = true, radix = null) {
+	let chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('');
+	let uuid = [];
+	radix = radix || chars.length;
+
+	if (len) {
+		// 如果指定uuid长度,只是取随机的字符,0|x为位运算,能去掉x的小数位,返回整数位
+		for (let i = 0; i < len; i++) uuid[i] = chars[0 | Math.random() * radix];
+	} else {
+		let r;
+		// rfc4122标准要求返回的uuid中,某些位为固定的字符
+		uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-';
+		uuid[14] = '4';
+
+		for (let i = 0; i < 36; i++) {
+			if (!uuid[i]) {
+				r = 0 | Math.random() * 16;
+				uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r];
+			}
+		}
+	}
+	// 移除第一个字符,并用u替代,因为第一个字符为数值时,该guuid不能用作id或者class
+	if (firstU) {
+		uuid.shift();
+		return 'u' + uuid.join('');
+	} else {
+		return uuid.join('');
+	}
+}
+
+export default guid;

+ 385 - 0
uview-ui/libs/function/md5.js

@@ -0,0 +1,385 @@
+/*
+ * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
+ * Digest Algorithm, as defined in RFC 1321.
+ * Version 2.2 Copyright (C) Paul Johnston 1999 - 2009
+ * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
+ * Distributed under the BSD License
+ * See http://pajhome.org.uk/crypt/md5 for more info.
+ */
+
+/*
+ * Configurable variables. You may need to tweak these to be compatible with
+ * the server-side, but the defaults work in most cases.
+ */
+var hexcase = 0;   /* hex output format. 0 - lowercase; 1 - uppercase        */
+var b64pad  = "";  /* base-64 pad character. "=" for strict RFC compliance   */
+
+/*
+ * These are the functions you'll usually want to call
+ * They take string arguments and return either hex or base-64 encoded strings
+ */
+function hex_md5(s)    { return rstr2hex(rstr_md5(str2rstr_utf8(s))); }
+function b64_md5(s)    { return rstr2b64(rstr_md5(str2rstr_utf8(s))); }
+function any_md5(s, e) { return rstr2any(rstr_md5(str2rstr_utf8(s)), e); }
+function hex_hmac_md5(k, d)
+  { return rstr2hex(rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d))); }
+function b64_hmac_md5(k, d)
+  { return rstr2b64(rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d))); }
+function any_hmac_md5(k, d, e)
+  { return rstr2any(rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d)), e); }
+
+/*
+ * Perform a simple self-test to see if the VM is working
+ */
+function md5_vm_test()
+{
+  return hex_md5("abc").toLowerCase() == "900150983cd24fb0d6963f7d28e17f72";
+}
+
+/*
+ * Calculate the MD5 of a raw string
+ */
+function rstr_md5(s)
+{
+  return binl2rstr(binl_md5(rstr2binl(s), s.length * 8));
+}
+
+/*
+ * Calculate the HMAC-MD5, of a key and some data (raw strings)
+ */
+function rstr_hmac_md5(key, data)
+{
+  var bkey = rstr2binl(key);
+  if(bkey.length > 16) bkey = binl_md5(bkey, key.length * 8);
+
+  var ipad = Array(16), opad = Array(16);
+  for(var i = 0; i < 16; i++)
+  {
+    ipad[i] = bkey[i] ^ 0x36363636;
+    opad[i] = bkey[i] ^ 0x5C5C5C5C;
+  }
+
+  var hash = binl_md5(ipad.concat(rstr2binl(data)), 512 + data.length * 8);
+  return binl2rstr(binl_md5(opad.concat(hash), 512 + 128));
+}
+
+/*
+ * Convert a raw string to a hex string
+ */
+function rstr2hex(input)
+{
+  try { hexcase } catch(e) { hexcase=0; }
+  var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
+  var output = "";
+  var x;
+  for(var i = 0; i < input.length; i++)
+  {
+    x = input.charCodeAt(i);
+    output += hex_tab.charAt((x >>> 4) & 0x0F)
+           +  hex_tab.charAt( x        & 0x0F);
+  }
+  return output;
+}
+
+/*
+ * Convert a raw string to a base-64 string
+ */
+function rstr2b64(input)
+{
+  try { b64pad } catch(e) { b64pad=''; }
+  var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+  var output = "";
+  var len = input.length;
+  for(var i = 0; i < len; i += 3)
+  {
+    var triplet = (input.charCodeAt(i) << 16)
+                | (i + 1 < len ? input.charCodeAt(i+1) << 8 : 0)
+                | (i + 2 < len ? input.charCodeAt(i+2)      : 0);
+    for(var j = 0; j < 4; j++)
+    {
+      if(i * 8 + j * 6 > input.length * 8) output += b64pad;
+      else output += tab.charAt((triplet >>> 6*(3-j)) & 0x3F);
+    }
+  }
+  return output;
+}
+
+/*
+ * Convert a raw string to an arbitrary string encoding
+ */
+function rstr2any(input, encoding)
+{
+  var divisor = encoding.length;
+  var i, j, q, x, quotient;
+
+  /* Convert to an array of 16-bit big-endian values, forming the dividend */
+  var dividend = Array(Math.ceil(input.length / 2));
+  for(i = 0; i < dividend.length; i++)
+  {
+    dividend[i] = (input.charCodeAt(i * 2) << 8) | input.charCodeAt(i * 2 + 1);
+  }
+
+  /*
+   * Repeatedly perform a long division. The binary array forms the dividend,
+   * the length of the encoding is the divisor. Once computed, the quotient
+   * forms the dividend for the next step. All remainders are stored for later
+   * use.
+   */
+  var full_length = Math.ceil(input.length * 8 /
+                                    (Math.log(encoding.length) / Math.log(2)));
+  var remainders = Array(full_length);
+  for(j = 0; j < full_length; j++)
+  {
+    quotient = Array();
+    x = 0;
+    for(i = 0; i < dividend.length; i++)
+    {
+      x = (x << 16) + dividend[i];
+      q = Math.floor(x / divisor);
+      x -= q * divisor;
+      if(quotient.length > 0 || q > 0)
+        quotient[quotient.length] = q;
+    }
+    remainders[j] = x;
+    dividend = quotient;
+  }
+
+  /* Convert the remainders to the output string */
+  var output = "";
+  for(i = remainders.length - 1; i >= 0; i--)
+    output += encoding.charAt(remainders[i]);
+
+  return output;
+}
+
+/*
+ * Encode a string as utf-8.
+ * For efficiency, this assumes the input is valid utf-16.
+ */
+function str2rstr_utf8(input)
+{
+  var output = "";
+  var i = -1;
+  var x, y;
+
+  while(++i < input.length)
+  {
+    /* Decode utf-16 surrogate pairs */
+    x = input.charCodeAt(i);
+    y = i + 1 < input.length ? input.charCodeAt(i + 1) : 0;
+    if(0xD800 <= x && x <= 0xDBFF && 0xDC00 <= y && y <= 0xDFFF)
+    {
+      x = 0x10000 + ((x & 0x03FF) << 10) + (y & 0x03FF);
+      i++;
+    }
+
+    /* Encode output as utf-8 */
+    if(x <= 0x7F)
+      output += String.fromCharCode(x);
+    else if(x <= 0x7FF)
+      output += String.fromCharCode(0xC0 | ((x >>> 6 ) & 0x1F),
+                                    0x80 | ( x         & 0x3F));
+    else if(x <= 0xFFFF)
+      output += String.fromCharCode(0xE0 | ((x >>> 12) & 0x0F),
+                                    0x80 | ((x >>> 6 ) & 0x3F),
+                                    0x80 | ( x         & 0x3F));
+    else if(x <= 0x1FFFFF)
+      output += String.fromCharCode(0xF0 | ((x >>> 18) & 0x07),
+                                    0x80 | ((x >>> 12) & 0x3F),
+                                    0x80 | ((x >>> 6 ) & 0x3F),
+                                    0x80 | ( x         & 0x3F));
+  }
+  return output;
+}
+
+/*
+ * Encode a string as utf-16
+ */
+function str2rstr_utf16le(input)
+{
+  var output = "";
+  for(var i = 0; i < input.length; i++)
+    output += String.fromCharCode( input.charCodeAt(i)        & 0xFF,
+                                  (input.charCodeAt(i) >>> 8) & 0xFF);
+  return output;
+}
+
+function str2rstr_utf16be(input)
+{
+  var output = "";
+  for(var i = 0; i < input.length; i++)
+    output += String.fromCharCode((input.charCodeAt(i) >>> 8) & 0xFF,
+                                   input.charCodeAt(i)        & 0xFF);
+  return output;
+}
+
+/*
+ * Convert a raw string to an array of little-endian words
+ * Characters >255 have their high-byte silently ignored.
+ */
+function rstr2binl(input)
+{
+  var output = Array(input.length >> 2);
+  for(var i = 0; i < output.length; i++)
+    output[i] = 0;
+  for(var i = 0; i < input.length * 8; i += 8)
+    output[i>>5] |= (input.charCodeAt(i / 8) & 0xFF) << (i%32);
+  return output;
+}
+
+/*
+ * Convert an array of little-endian words to a string
+ */
+function binl2rstr(input)
+{
+  var output = "";
+  for(var i = 0; i < input.length * 32; i += 8)
+    output += String.fromCharCode((input[i>>5] >>> (i % 32)) & 0xFF);
+  return output;
+}
+
+/*
+ * Calculate the MD5 of an array of little-endian words, and a bit length.
+ */
+function binl_md5(x, len)
+{
+  /* append padding */
+  x[len >> 5] |= 0x80 << ((len) % 32);
+  x[(((len + 64) >>> 9) << 4) + 14] = len;
+
+  var a =  1732584193;
+  var b = -271733879;
+  var c = -1732584194;
+  var d =  271733878;
+
+  for(var i = 0; i < x.length; i += 16)
+  {
+    var olda = a;
+    var oldb = b;
+    var oldc = c;
+    var oldd = d;
+
+    a = md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936);
+    d = md5_ff(d, a, b, c, x[i+ 1], 12, -389564586);
+    c = md5_ff(c, d, a, b, x[i+ 2], 17,  606105819);
+    b = md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330);
+    a = md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897);
+    d = md5_ff(d, a, b, c, x[i+ 5], 12,  1200080426);
+    c = md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341);
+    b = md5_ff(b, c, d, a, x[i+ 7], 22, -45705983);
+    a = md5_ff(a, b, c, d, x[i+ 8], 7 ,  1770035416);
+    d = md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417);
+    c = md5_ff(c, d, a, b, x[i+10], 17, -42063);
+    b = md5_ff(b, c, d, a, x[i+11], 22, -1990404162);
+    a = md5_ff(a, b, c, d, x[i+12], 7 ,  1804603682);
+    d = md5_ff(d, a, b, c, x[i+13], 12, -40341101);
+    c = md5_ff(c, d, a, b, x[i+14], 17, -1502002290);
+    b = md5_ff(b, c, d, a, x[i+15], 22,  1236535329);
+
+    a = md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510);
+    d = md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632);
+    c = md5_gg(c, d, a, b, x[i+11], 14,  643717713);
+    b = md5_gg(b, c, d, a, x[i+ 0], 20, -373897302);
+    a = md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691);
+    d = md5_gg(d, a, b, c, x[i+10], 9 ,  38016083);
+    c = md5_gg(c, d, a, b, x[i+15], 14, -660478335);
+    b = md5_gg(b, c, d, a, x[i+ 4], 20, -405537848);
+    a = md5_gg(a, b, c, d, x[i+ 9], 5 ,  568446438);
+    d = md5_gg(d, a, b, c, x[i+14], 9 , -1019803690);
+    c = md5_gg(c, d, a, b, x[i+ 3], 14, -187363961);
+    b = md5_gg(b, c, d, a, x[i+ 8], 20,  1163531501);
+    a = md5_gg(a, b, c, d, x[i+13], 5 , -1444681467);
+    d = md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784);
+    c = md5_gg(c, d, a, b, x[i+ 7], 14,  1735328473);
+    b = md5_gg(b, c, d, a, x[i+12], 20, -1926607734);
+
+    a = md5_hh(a, b, c, d, x[i+ 5], 4 , -378558);
+    d = md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463);
+    c = md5_hh(c, d, a, b, x[i+11], 16,  1839030562);
+    b = md5_hh(b, c, d, a, x[i+14], 23, -35309556);
+    a = md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060);
+    d = md5_hh(d, a, b, c, x[i+ 4], 11,  1272893353);
+    c = md5_hh(c, d, a, b, x[i+ 7], 16, -155497632);
+    b = md5_hh(b, c, d, a, x[i+10], 23, -1094730640);
+    a = md5_hh(a, b, c, d, x[i+13], 4 ,  681279174);
+    d = md5_hh(d, a, b, c, x[i+ 0], 11, -358537222);
+    c = md5_hh(c, d, a, b, x[i+ 3], 16, -722521979);
+    b = md5_hh(b, c, d, a, x[i+ 6], 23,  76029189);
+    a = md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487);
+    d = md5_hh(d, a, b, c, x[i+12], 11, -421815835);
+    c = md5_hh(c, d, a, b, x[i+15], 16,  530742520);
+    b = md5_hh(b, c, d, a, x[i+ 2], 23, -995338651);
+
+    a = md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844);
+    d = md5_ii(d, a, b, c, x[i+ 7], 10,  1126891415);
+    c = md5_ii(c, d, a, b, x[i+14], 15, -1416354905);
+    b = md5_ii(b, c, d, a, x[i+ 5], 21, -57434055);
+    a = md5_ii(a, b, c, d, x[i+12], 6 ,  1700485571);
+    d = md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606);
+    c = md5_ii(c, d, a, b, x[i+10], 15, -1051523);
+    b = md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799);
+    a = md5_ii(a, b, c, d, x[i+ 8], 6 ,  1873313359);
+    d = md5_ii(d, a, b, c, x[i+15], 10, -30611744);
+    c = md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380);
+    b = md5_ii(b, c, d, a, x[i+13], 21,  1309151649);
+    a = md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070);
+    d = md5_ii(d, a, b, c, x[i+11], 10, -1120210379);
+    c = md5_ii(c, d, a, b, x[i+ 2], 15,  718787259);
+    b = md5_ii(b, c, d, a, x[i+ 9], 21, -343485551);
+
+    a = safe_add(a, olda);
+    b = safe_add(b, oldb);
+    c = safe_add(c, oldc);
+    d = safe_add(d, oldd);
+  }
+  return Array(a, b, c, d);
+}
+
+/*
+ * These functions implement the four basic operations the algorithm uses.
+ */
+function md5_cmn(q, a, b, x, s, t)
+{
+  return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s),b);
+}
+function md5_ff(a, b, c, d, x, s, t)
+{
+  return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t);
+}
+function md5_gg(a, b, c, d, x, s, t)
+{
+  return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t);
+}
+function md5_hh(a, b, c, d, x, s, t)
+{
+  return md5_cmn(b ^ c ^ d, a, b, x, s, t);
+}
+function md5_ii(a, b, c, d, x, s, t)
+{
+  return md5_cmn(c ^ (b | (~d)), a, b, x, s, t);
+}
+
+/*
+ * Add integers, wrapping at 2^32. This uses 16-bit operations internally
+ * to work around bugs in some JS interpreters.
+ */
+function safe_add(x, y)
+{
+  var lsw = (x & 0xFFFF) + (y & 0xFFFF);
+  var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
+  return (msw << 16) | (lsw & 0xFFFF);
+}
+
+/*
+ * Bitwise rotate a 32-bit number to the left.
+ */
+function bit_rol(num, cnt)
+{
+  return (num << cnt) | (num >>> (32 - cnt));
+}
+
+module.exports = {
+	md5 : function(str){
+		return hex_md5(str);
+	}
+}

+ 58 - 0
uview-ui/libs/function/queryParams.js

@@ -0,0 +1,58 @@
+/**
+ * 对象转url参数
+ * @param {*} data,对象
+ * @param {*} isPrefix,是否自动加上"?"
+ */
+function queryParams(data = {}, isPrefix = true, arrayFormat = 'brackets') {
+	let prefix = isPrefix ? '?' : ''
+	let _result = []
+	if (['indices', 'brackets', 'repeat', 'comma'].indexOf(arrayFormat) == -1) arrayFormat = 'brackets';
+	for (let key in data) {
+		let value = data[key]
+		// 去掉为空的参数
+		if (['', undefined, null].indexOf(value) >= 0) {
+			continue;
+		}
+		// 如果值为数组,另行处理
+		if (value.constructor === Array) {
+			// e.g. {ids: [1, 2, 3]}
+			switch (arrayFormat) {
+				case 'indices':
+					// 结果: ids[0]=1&ids[1]=2&ids[2]=3
+					for (let i = 0; i < value.length; i++) {
+						_result.push(key + '[' + i + ']=' + value[i])
+					}
+					break;
+				case 'brackets':
+					// 结果: ids[]=1&ids[]=2&ids[]=3
+					value.forEach(_value => {
+						_result.push(key + '[]=' + _value)
+					})
+					break;
+				case 'repeat':
+					// 结果: ids=1&ids=2&ids=3
+					value.forEach(_value => {
+						_result.push(key + '=' + _value)
+					})
+					break;
+				case 'comma':
+					// 结果: ids=1,2,3
+					let commaStr = "";
+					value.forEach(_value => {
+						commaStr += (commaStr ? "," : "") + _value;
+					})
+					_result.push(key + '=' + commaStr)
+					break;
+				default:
+					value.forEach(_value => {
+						_result.push(key + '[]=' + _value)
+					})
+			}
+		} else {
+			_result.push(key + '=' + value)
+		}
+	}
+	return _result.length ? prefix + _result.join('&') : ''
+}
+
+export default queryParams;

+ 10 - 0
uview-ui/libs/function/random.js

@@ -0,0 +1,10 @@
+function random(min, max) {
+	if (min >= 0 && max > 0 && max >= min) {
+		let gab = max - min + 1;
+		return Math.floor(Math.random() * gab + min);
+	} else {
+		return 0;
+	}
+}
+
+export default random;

+ 7 - 0
uview-ui/libs/function/randomArray.js

@@ -0,0 +1,7 @@
+// 打乱数组
+function randomArray(array = []) {
+	// 原理是sort排序,Math.random()产生0<= x < 1之间的数,会导致x-0.05大于或者小于0
+	return array.sort(() => Math.random() - 0.5);
+}
+
+export default randomArray

+ 122 - 0
uview-ui/libs/function/route.js

@@ -0,0 +1,122 @@
+/**
+ * 路由跳转方法,该方法相对于直接使用uni.xxx的好处是使用更加简单快捷
+ * 并且带有路由拦截功能
+ */
+
+class Router {
+	constructor() {
+		// 原始属性定义
+		this.config = {
+			type: 'navigateTo',
+			url: '',
+			delta: 1, // navigateBack页面后退时,回退的层数
+			params: {}, // 传递的参数
+			animationType: 'pop-in', // 窗口动画,只在APP有效
+			animationDuration: 300, // 窗口动画持续时间,单位毫秒,只在APP有效
+			intercept: false, // 是否需要拦截
+		}
+		// 因为route方法是需要对外赋值给另外的对象使用,同时route内部有使用this,会导致route失去上下文
+		// 这里在构造函数中进行this绑定
+		this.route = this.route.bind(this)
+	}
+
+	// 判断url前面是否有"/",如果没有则加上,否则无法跳转
+	addRootPath(url) {
+		return url[0] === '/' ? url : `/${url}`
+	}
+
+	// 整合路由参数
+	mixinParam(url, params) {
+		url = url && this.addRootPath(url)
+		
+		// 使用正则匹配,主要依据是判断是否有"/","?","="等,如“/page/index/index?name=mary"
+		// 如果有url中有get参数,转换后无需带上"?"
+		let query = ''
+		if (/.*\/.*\?.*=.*/.test(url)) {
+			// object对象转为get类型的参数
+			query = uni.$u.queryParams(params, false);
+			// 因为已有get参数,所以后面拼接的参数需要带上"&"隔开
+			return url += "&" + query
+		} else {
+			// 直接拼接参数,因为此处url中没有后面的query参数,也就没有"?/&"之类的符号
+			query = uni.$u.queryParams(params);
+			return url += query
+		}
+	}
+
+	// 对外的方法名称
+	async route(options = {}, params = {}) {
+		// 合并用户的配置和内部的默认配置
+		let mergeConfig = {}
+
+		if (typeof options === 'string') {
+			// 如果options为字符串,则为route(url, params)的形式
+			mergeConfig.url = this.mixinParam(options, params)
+			mergeConfig.type = 'navigateTo'
+		} else {
+			mergeConfig = uni.$u.deepClone(options, this.config)
+			// 否则正常使用mergeConfig中的url和params进行拼接
+			mergeConfig.url = this.mixinParam(options.url, options.params)
+		}
+		
+		if(params.intercept) {
+			this.config.intercept = params.intercept
+		}
+		// params参数也带给拦截器
+		mergeConfig.params = params
+		// 合并内外部参数
+		mergeConfig = uni.$u.deepMerge(this.config, mergeConfig)
+		// 判断用户是否定义了拦截器
+		if (typeof uni.$u.routeIntercept === 'function') {
+			// 定一个promise,根据用户执行resolve(true)或者resolve(false)来决定是否进行路由跳转
+			const isNext = await new Promise((resolve, reject) => {
+				uni.$u.routeIntercept(mergeConfig, resolve)
+			})
+			// 如果isNext为true,则执行路由跳转
+			isNext && this.openPage(mergeConfig)
+		} else {
+			this.openPage(mergeConfig)
+		}
+	}
+
+	// 执行路由跳转
+	openPage(config) {
+		// 解构参数
+		const {
+			url,
+			type,
+			delta,
+			animationType,
+			animationDuration
+		} = config
+		if (config.type == 'navigateTo' || config.type == 'to') {
+			uni.navigateTo({
+				url,
+				animationType,
+				animationDuration
+			});
+		}
+		if (config.type == 'redirectTo' || config.type == 'redirect') {
+			uni.redirectTo({
+				url
+			});
+		}
+		if (config.type == 'switchTab' || config.type == 'tab') {
+			uni.switchTab({
+				url
+			});
+		}
+		if (config.type == 'reLaunch' || config.type == 'launch') {
+			uni.reLaunch({
+				url
+			});
+		}
+		if (config.type == 'navigateBack' || config.type == 'back') {
+			uni.navigateBack({
+				delta
+			});
+		}
+	}
+}
+
+export default (new Router()).route

+ 9 - 0
uview-ui/libs/function/sys.js

@@ -0,0 +1,9 @@
+export function os() {
+	return uni.getSystemInfoSync().platform;
+};
+
+export function sys() {
+	return uni.getSystemInfoSync();
+}
+
+

+ 232 - 0
uview-ui/libs/function/test.js

@@ -0,0 +1,232 @@
+/**
+ * 验证电子邮箱格式
+ */
+function email(value) {
+	return /^\w+((-\w+)|(\.\w+))*\@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]+$/.test(value);
+}
+
+/**
+ * 验证手机格式
+ */
+function mobile(value) {
+	return /^1[3-9]\d{9}$/.test(value)
+}
+
+/**
+ * 验证URL格式
+ */
+function url(value) {
+	return /http(s)?:\/\/([\w-]+\.)+[\w-]+(\/[\w-.\/?%&=]*)?/.test(value)
+}
+
+/**
+ * 验证日期格式
+ */
+function date(value) {
+	return !/Invalid|NaN/.test(new Date(value).toString())
+}
+
+/**
+ * 验证ISO类型的日期格式
+ */
+function dateISO(value) {
+	return /^\d{4}[\/\-](0?[1-9]|1[012])[\/\-](0?[1-9]|[12][0-9]|3[01])$/.test(value)
+}
+
+/**
+ * 验证十进制数字
+ */
+function number(value) {
+	return /^(?:-?\d+|-?\d{1,3}(?:,\d{3})+)?(?:\.\d+)?$/.test(value)
+}
+
+/**
+ * 验证整数
+ */
+function digits(value) {
+	return /^\d+$/.test(value)
+}
+
+/**
+ * 验证身份证号码
+ */
+function idCard(value) {
+	return /^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}([0-9]|X)$/.test(
+		value)
+}
+
+/**
+ * 是否车牌号
+ */
+function carNo(value) {
+	// 新能源车牌
+	const xreg = /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}(([0-9]{5}[DF]$)|([DF][A-HJ-NP-Z0-9][0-9]{4}$))/;
+	// 旧车牌
+	const creg = /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}[A-HJ-NP-Z0-9]{4}[A-HJ-NP-Z0-9挂学警港澳]{1}$/;
+	if (value.length === 7) {
+		return creg.test(value);
+	} else if (value.length === 8) {
+		return xreg.test(value);
+	} else {
+		return false;
+	}
+}
+
+/**
+ * 金额,只允许2位小数
+ */
+function amount(value) {
+	//金额,只允许保留两位小数
+	return /^[1-9]\d*(,\d{3})*(\.\d{1,2})?$|^0\.\d{1,2}$/.test(value);
+}
+
+/**
+ * 中文
+ */
+function chinese(value) {
+	let reg = /^[\u4e00-\u9fa5]+$/gi;
+	return reg.test(value);
+}
+
+/**
+ * 只能输入字母
+ */
+function letter(value) {
+	return /^[a-zA-Z]*$/.test(value);
+}
+
+/**
+ * 只能是字母或者数字
+ */
+function enOrNum(value) {
+	//英文或者数字
+	let reg = /^[0-9a-zA-Z]*$/g;
+	return reg.test(value);
+}
+
+/**
+ * 验证是否包含某个值
+ */
+function contains(value, param) {
+	return value.indexOf(param) >= 0
+}
+
+/**
+ * 验证一个值范围[min, max]
+ */
+function range(value, param) {
+	return value >= param[0] && value <= param[1]
+}
+
+/**
+ * 验证一个长度范围[min, max]
+ */
+function rangeLength(value, param) {
+	return value.length >= param[0] && value.length <= param[1]
+}
+
+/**
+ * 是否固定电话
+ */
+function landline(value) {
+	let reg = /^\d{3,4}-\d{7,8}(-\d{3,4})?$/;
+	return reg.test(value);
+}
+
+/**
+ * 判断是否为空
+ */
+function empty(value) {
+	switch (typeof value) {
+		case 'undefined':
+			return true;
+		case 'string':
+			if (value.replace(/(^[ \t\n\r]*)|([ \t\n\r]*$)/g, '').length == 0) return true;
+			break;
+		case 'boolean':
+			if (!value) return true;
+			break;
+		case 'number':
+			if (0 === value || isNaN(value)) return true;
+			break;
+		case 'object':
+			if (null === value || value.length === 0) return true;
+			for (var i in value) {
+				return false;
+			}
+			return true;
+	}
+	return false;
+}
+
+/**
+ * 是否json字符串
+ */
+function jsonString(value) {
+	if (typeof value == 'string') {
+		try {
+			var obj = JSON.parse(value);
+			if (typeof obj == 'object' && obj) {
+				return true;
+			} else {
+				return false;
+			}
+		} catch (e) {
+			return false;
+		}
+	}
+	return false;
+}
+
+
+/**
+ * 是否数组
+ */
+function array(value) {
+	if (typeof Array.isArray === "function") {
+		return Array.isArray(value);
+	} else {
+		return Object.prototype.toString.call(value) === "[object Array]";
+	}
+}
+
+/**
+ * 是否对象
+ */
+function object(value) {
+	return Object.prototype.toString.call(value) === '[object Object]';
+}
+
+/**
+ * 是否短信验证码
+ */
+function code(value, len = 6) {
+	return new RegExp(`^\\d{${len}}$`).test(value);
+}
+
+
+export default {
+	email,
+	mobile,
+	url,
+	date,
+	dateISO,
+	number,
+	digits,
+	idCard,
+	carNo,
+	amount,
+	chinese,
+	letter,
+	enOrNum,
+	contains,
+	range,
+	rangeLength,
+	empty,
+	isEmpty: empty,
+	jsonString,
+	landline,
+	object,
+	array,
+	code
+}

+ 32 - 0
uview-ui/libs/function/throttle.js

@@ -0,0 +1,32 @@
+let timer, flag;
+/**
+ * 节流原理:在一定时间内,只能触发一次
+ * 
+ * @param {Function} func 要执行的回调函数 
+ * @param {Number} wait 延时的时间
+ * @param {Boolean} immediate 是否立即执行
+ * @return null
+ */
+function throttle(func, wait = 500, immediate = true) {
+	if (immediate) {
+		if (!flag) {
+			flag = true;
+			// 如果是立即执行,则在wait毫秒内开始时执行
+			typeof func === 'function' && func();
+			timer = setTimeout(() => {
+				flag = false;
+			}, wait);
+		}
+	} else {
+		if (!flag) {
+			flag = true
+			// 如果是非立即执行,则在wait毫秒内的结束处执行
+			timer = setTimeout(() => {
+				flag = false
+				typeof func === 'function' && func();
+			}, wait);
+		}
+		
+	}
+};
+export default throttle

+ 51 - 0
uview-ui/libs/function/timeFormat.js

@@ -0,0 +1,51 @@
+// padStart 的 polyfill,因为某些机型或情况,还无法支持es7的padStart,比如电脑版的微信小程序
+// 所以这里做一个兼容polyfill的兼容处理
+if (!String.prototype.padStart) {
+	// 为了方便表示这里 fillString 用了ES6 的默认参数,不影响理解
+	String.prototype.padStart = function(maxLength, fillString = ' ') {
+		if (Object.prototype.toString.call(fillString) !== "[object String]") throw new TypeError(
+			'fillString must be String')
+		let str = this
+		// 返回 String(str) 这里是为了使返回的值是字符串字面量,在控制台中更符合直觉
+		if (str.length >= maxLength) return String(str)
+
+		let fillLength = maxLength - str.length,
+			times = Math.ceil(fillLength / fillString.length)
+		while (times >>= 1) {
+			fillString += fillString
+			if (times === 1) {
+				fillString += fillString
+			}
+		}
+		return fillString.slice(0, fillLength) + str;
+	}
+}
+
+// 其他更多是格式化有如下:
+// yyyy:mm:dd|yyyy:mm|yyyy年mm月dd日|yyyy年mm月dd日 hh时MM分等,可自定义组合
+function timeFormat(dateTime = null, fmt = 'yyyy-mm-dd') {
+	// 如果为null,则格式化当前时间
+	if (!dateTime) dateTime = Number(new Date());
+	// 如果dateTime长度为10或者13,则为秒和毫秒的时间戳,如果超过13位,则为其他的时间格式
+	if (dateTime.toString().length == 10) dateTime *= 1000;
+	let date = new Date(dateTime);
+	let ret;
+	let opt = {
+		"y+": date.getFullYear().toString(), // 年
+		"m+": (date.getMonth() + 1).toString(), // 月
+		"d+": date.getDate().toString(), // 日
+		"h+": date.getHours().toString(), // 时
+		"M+": date.getMinutes().toString(), // 分
+		"s+": date.getSeconds().toString() // 秒
+		// 有其他格式化字符需求可以继续添加,必须转化成字符串
+	};
+	for (let k in opt) {
+		ret = new RegExp("(" + k + ")").exec(fmt);
+		if (ret) {
+			fmt = fmt.replace(ret[1], (ret[1].length == 1) ? (opt[k]) : (opt[k].padStart(ret[1].length, "0")))
+		};
+	};
+	return fmt;
+}
+
+export default timeFormat

+ 47 - 0
uview-ui/libs/function/timeFrom.js

@@ -0,0 +1,47 @@
+import timeFormat from '../../libs/function/timeFormat.js';
+
+/**
+ * 时间戳转为多久之前
+ * @param String timestamp 时间戳
+ * @param String | Boolean format 如果为时间格式字符串,超出一定时间范围,返回固定的时间格式;
+ * 如果为布尔值false,无论什么时间,都返回多久以前的格式
+ */
+function timeFrom(dateTime = null, format = 'yyyy-mm-dd') {
+	// 如果为null,则格式化当前时间
+	if (!dateTime) dateTime = Number(new Date());
+	// 如果dateTime长度为10或者13,则为秒和毫秒的时间戳,如果超过13位,则为其他的时间格式
+	if (dateTime.toString().length == 10) dateTime *= 1000;
+	let timestamp = + new Date(Number(dateTime));
+
+	let timer = (Number(new Date()) - timestamp) / 1000;
+	// 如果小于5分钟,则返回"刚刚",其他以此类推
+	let tips = '';
+	switch (true) {
+		case timer < 300:
+			tips = '刚刚';
+			break;
+		case timer >= 300 && timer < 3600:
+			tips = parseInt(timer / 60) + '分钟前';
+			break;
+		case timer >= 3600 && timer < 86400:
+			tips = parseInt(timer / 3600) + '小时前';
+			break;
+		case timer >= 86400 && timer < 2592000:
+			tips = parseInt(timer / 86400) + '天前';
+			break;
+		default:
+			// 如果format为false,则无论什么时间戳,都显示xx之前
+			if(format === false) {
+				if(timer >= 2592000 && timer < 365 * 86400) {
+					tips = parseInt(timer / (86400 * 30)) + '个月前';
+				} else {
+					tips = parseInt(timer / (86400 * 365)) + '年前';
+				}
+			} else {
+				tips = timeFormat(timestamp, format);
+			}
+	}
+	return tips;
+}
+
+export default timeFrom;

+ 9 - 0
uview-ui/libs/function/toast.js

@@ -0,0 +1,9 @@
+function toast(title, duration = 1500) {
+	uni.showToast({
+		title: title,
+		icon: 'none',
+		duration: duration
+	})
+}
+
+export default toast

+ 15 - 0
uview-ui/libs/function/trim.js

@@ -0,0 +1,15 @@
+function trim(str, pos = 'both') {
+	if (pos == 'both') {
+		return str.replace(/^\s+|\s+$/g, "");
+	} else if (pos == "left") {
+		return str.replace(/^\s*/, '');
+	} else if (pos == 'right') {
+		return str.replace(/(\s*$)/g, "");
+	} else if (pos == 'all') {
+		return str.replace(/\s+/g, "");
+	} else {
+		return str;
+	}
+}
+
+export default trim

+ 35 - 0
uview-ui/libs/function/type2icon.js

@@ -0,0 +1,35 @@
+/**
+ * 根据主题type值,获取对应的图标
+ * @param String type 主题名称,primary|info|error|warning|success
+ * @param String fill 是否使用fill填充实体的图标  
+ */
+function type2icon(type = 'success', fill = false) {
+	// 如果非预置值,默认为success
+	if (['primary', 'info', 'error', 'warning', 'success'].indexOf(type) == -1) type = 'success';
+	let iconName = '';
+	// 目前(2019-12-12),info和primary使用同一个图标
+	switch (type) {
+		case 'primary':
+			iconName = 'info-circle';
+			break;
+		case 'info':
+			iconName = 'info-circle';
+			break;
+		case 'error':
+			iconName = 'close-circle';
+			break;
+		case 'warning':
+			iconName = 'error-circle';
+			break;
+		case 'success':
+			iconName = 'checkmark-circle';
+			break;
+		default:
+			iconName = 'checkmark-circle';
+	}
+	// 是否是实体类型,加上-fill,在icon组件库中,实体的类名是后面加-fill的
+	if (fill) iconName += '-fill';
+	return iconName;
+}
+
+export default type2icon

+ 64 - 0
uview-ui/libs/mixin/mixin.js

@@ -0,0 +1,64 @@
+module.exports = {
+	data() {
+		return {}
+	},
+	onLoad() {
+		// getRect挂载到$u上,因为这方法需要使用in(this),所以无法把它独立成一个单独的文件导出
+		this.$u.getRect = this.$uGetRect
+	},
+	methods: {
+		// 查询节点信息
+		// 目前此方法在支付宝小程序中无法获取组件跟接点的尺寸,为支付宝的bug(2020-07-21)
+		// 解决办法为在组件根部再套一个没有任何作用的view元素
+		$uGetRect(selector, all) {
+			return new Promise(resolve => {
+				uni.createSelectorQuery().
+				in(this)[all ? 'selectAll' : 'select'](selector)
+					.boundingClientRect(rect => {
+						if (all && Array.isArray(rect) && rect.length) {
+							resolve(rect)
+						}
+						if (!all && rect) {
+							resolve(rect)
+						}
+					})
+					.exec()
+			})
+		},
+		getParentData(parentName = '') {
+			// 避免在created中去定义parent变量
+			if(!this.parent) this.parent = false;
+			// 这里的本质原理是,通过获取父组件实例(也即u-radio-group的this)
+			// 将父组件this中对应的参数,赋值给本组件(u-radio的this)的parentData对象中对应的属性
+			// 之所以需要这么做,是因为所有端中,头条小程序不支持通过this.parent.xxx去监听父组件参数的变化
+			this.parent = this.$u.$parent.call(this, parentName);
+			if(this.parent) {
+				// 历遍parentData中的属性,将parent中的同名属性赋值给parentData
+				Object.keys(this.parentData).map(key => {
+					this.parentData[key] = this.parent[key];
+				});
+			}
+		},
+		// 阻止事件冒泡
+		preventEvent(e) {
+			e && e.stopPropagation && e.stopPropagation()
+		}
+	},
+	onReachBottom() {
+		uni.$emit('uOnReachBottom')
+	},
+	beforeDestroy() {
+		// 判断当前页面是否存在parent和chldren,一般在checkbox和checkbox-group父子联动的场景会有此情况
+		// 组件销毁时,移除子组件在父组件children数组中的实例,释放资源,避免数据混乱
+		if(this.parent && uni.$u.test.array(this.parent.children)) {
+			// 组件销毁时,移除父组件中的children数组中对应的实例
+			const childrenList = this.parent.children
+			childrenList.map((child, index) => {
+				// 如果相等,则移除
+				if(child === this) {
+					childrenList.splice(index, 1)
+				}
+			})
+		}
+	}
+}

+ 18 - 0
uview-ui/libs/mixin/mpShare.js

@@ -0,0 +1,18 @@
+module.exports = {
+	onLoad() {
+		// 设置默认的转发参数
+		this.$u.mpShare = {
+			title: '', // 默认为小程序名称
+			path: '', // 默认为当前页面路径
+			imageUrl: '' // 默认为当前页面的截图
+		}
+	},
+	onShareAppMessage() {
+		return this.$u.mpShare
+	},
+	// #ifdef MP-WEIXIN
+	onShareTimeline() {
+		return this.$u.mpShare
+	}
+	// #endif
+}

+ 169 - 0
uview-ui/libs/request/index.js

@@ -0,0 +1,169 @@
+import deepMerge from "../function/deepMerge";
+import validate from "../function/test";
+class Request {
+	// 设置全局默认配置
+	setConfig(customConfig) {
+		// 深度合并对象,否则会造成对象深层属性丢失
+		this.config = deepMerge(this.config, customConfig);
+	}
+
+	// 主要请求部分
+	request(options = {}) {
+		// 检查请求拦截
+		if (this.interceptor.request && typeof this.interceptor.request === 'function') {
+			let tmpConfig = {};
+			let interceptorRequest = this.interceptor.request(options);
+			if (interceptorRequest === false) {
+				// 返回一个处于pending状态中的Promise,来取消原promise,避免进入then()回调
+				return new Promise(()=>{});
+			}
+			this.options = interceptorRequest;
+		}
+		options.dataType = options.dataType || this.config.dataType;
+		options.responseType = options.responseType || this.config.responseType;
+		options.url = options.url || '';
+		options.params = options.params || {};
+		options.header = Object.assign({}, this.config.header, options.header);
+		options.method = options.method || this.config.method;
+
+		return new Promise((resolve, reject) => {
+			options.complete = (response) => {
+				// 请求返回后,隐藏loading(如果请求返回快的话,可能会没有loading)
+				uni.hideLoading();
+				// 清除定时器,如果请求回来了,就无需loading
+				clearTimeout(this.config.timer);
+				this.config.timer = null;
+				// 判断用户对拦截返回数据的要求,如果originalData为true,返回所有的数据(response)到拦截器,否则只返回response.data
+				if(this.config.originalData) {
+					// 判断是否存在拦截器
+					if (this.interceptor.response && typeof this.interceptor.response === 'function') {
+						let resInterceptors = this.interceptor.response(response);
+						// 如果拦截器不返回false,就将拦截器返回的内容给this.$u.post的then回调
+						if (resInterceptors !== false) {
+							resolve(resInterceptors);
+						} else {
+							// 如果拦截器返回false,意味着拦截器定义者认为返回有问题,直接接入catch回调
+							reject(response);
+						}
+					} else {
+						// 如果要求返回原始数据,就算没有拦截器,也返回最原始的数据
+						resolve(response);
+					}
+				} else {
+					if (response.statusCode == 200) {
+						if (this.interceptor.response && typeof this.interceptor.response === 'function') {
+							let resInterceptors = this.interceptor.response(response.data);
+							if (resInterceptors !== false) {
+								resolve(resInterceptors);
+							} else {
+								reject(response.data);
+							}
+						} else {
+							// 如果不是返回原始数据(originalData=false),且没有拦截器的情况下,返回纯数据给then回调
+							resolve(response.data);
+						}
+					} else {
+						// 不返回原始数据的情况下,服务器状态码不为200,modal弹框提示
+						// if(response.errMsg) {
+						// 	uni.showModal({
+						// 		title: response.errMsg
+						// 	});
+						// }
+						reject(response)
+					}
+				}
+			}
+
+			// 判断用户传递的URL是否/开头,如果不是,加上/,这里使用了uView的test.js验证库的url()方法
+			options.url = validate.url(options.url) ? options.url : (this.config.baseUrl + (options.url.indexOf('/') == 0 ?
+				options.url : '/' + options.url));
+			
+			// 是否显示loading
+			// 加一个是否已有timer定时器的判断,否则有两个同时请求的时候,后者会清除前者的定时器id
+			// 而没有清除前者的定时器,导致前者超时,一直显示loading
+			if(this.config.showLoading && !this.config.timer) {
+				this.config.timer = setTimeout(() => {
+					uni.showLoading({
+						title: this.config.loadingText,
+						mask: this.config.loadingMask
+					})
+					this.config.timer = null;
+				}, this.config.loadingTime);
+			}
+			uni.request(options);
+		})
+		// .catch(res => {
+		// 	// 如果返回reject(),不让其进入this.$u.post().then().catch()后面的catct()
+		// 	// 因为很多人都会忘了写后面的catch(),导致报错捕获不到catch
+		// 	return new Promise(()=>{});
+		// })
+	}
+
+	constructor() {
+		this.config = {
+			baseUrl: '', // 请求的根域名
+			// 默认的请求头
+			header: {},
+			method: 'POST',
+			// 设置为json,返回后uni.request会对数据进行一次JSON.parse
+			dataType: 'json',
+			// 此参数无需处理,因为5+和支付宝小程序不支持,默认为text即可
+			responseType: 'text',
+			showLoading: true, // 是否显示请求中的loading
+			loadingText: '请求中...',
+			loadingTime: 800, // 在此时间内,请求还没回来的话,就显示加载中动画,单位ms
+			timer: null, // 定时器
+			originalData: false, // 是否在拦截器中返回服务端的原始数据,见文档说明
+			loadingMask: true, // 展示loading的时候,是否给一个透明的蒙层,防止触摸穿透
+		}
+	
+		// 拦截器
+		this.interceptor = {
+			// 请求前的拦截
+			request: null,
+			// 请求后的拦截
+			response: null
+		}
+
+		// get请求
+		this.get = (url, data = {}, header = {}) => {
+			return this.request({
+				method: 'GET',
+				url,
+				header,
+				data
+			})
+		}
+
+		// post请求
+		this.post = (url, data = {}, header = {}) => {
+			return this.request({
+				url,
+				method: 'POST',
+				header,
+				data
+			})
+		}
+		
+		// put请求,不支持支付宝小程序(HX2.6.15)
+		this.put = (url, data = {}, header = {}) => {
+			return this.request({
+				url,
+				method: 'PUT',
+				header,
+				data
+			})
+		}
+		
+		// delete请求,不支持支付宝和头条小程序(HX2.6.15)
+		this.delete = (url, data = {}, header = {}) => {
+			return this.request({
+				url,
+				method: 'DELETE',
+				header,
+				data
+			})
+		}
+	}
+}
+export default new Request

+ 19 - 0
uview-ui/libs/store/index.js

@@ -0,0 +1,19 @@
+// 暂时不用vuex模块方式实现,将该方法直接放入到/store/index.js中
+const module = {
+	actions: {
+		$uStore({rootState}, params) {
+			let nameArr = params.name.split('.');
+			if(nameArr.length >= 2) {
+				let obj = rootState[nameArr[0]];
+				for(let i = 1; i < nameArr.length - 1; i ++) {
+					obj = obj[nameArr[i]];
+				}
+				obj[nameArr[nameArr.length - 1]] = params.value;
+			} else {
+				rootState[params.name] = params.value;
+			}
+		}
+	}
+}
+
+export default module

Dosya farkı çok büyük olduğundan ihmal edildi
+ 1 - 0
uview-ui/libs/util/area.js


Dosya farkı çok büyük olduğundan ihmal edildi
+ 1356 - 0
uview-ui/libs/util/async-validator.js


Dosya farkı çok büyük olduğundan ihmal edildi
+ 1 - 0
uview-ui/libs/util/city.js


+ 51 - 0
uview-ui/libs/util/emitter.js

@@ -0,0 +1,51 @@
+/**
+ * 递归使用 call 方式this指向
+ * @param componentName // 需要找的组件的名称
+ * @param eventName // 事件名称
+ * @param params // 需要传递的参数
+ */
+function broadcast(componentName, eventName, params) {
+    // 循环子节点找到名称一样的子节点 否则 递归 当前子节点
+    this.$children.map(child=>{
+        if (componentName===child.$options.name) {
+            child.$emit.apply(child,[eventName].concat(params))
+        }else {
+            broadcast.apply(child,[componentName,eventName].concat(params))
+        }
+    })
+}
+export default {
+    methods: {
+        /**
+         * 派发 (向上查找) (一个)
+         * @param componentName // 需要找的组件的名称
+         * @param eventName // 事件名称
+         * @param params // 需要传递的参数
+         */
+        dispatch(componentName, eventName, params) {
+            let parent = this.$parent || this.$root;//$parent 找到最近的父节点 $root 根节点
+            let name = parent.$options.name; // 获取当前组件实例的name
+            // 如果当前有节点 && 当前没名称 且 当前名称等于需要传进来的名称的时候就去查找当前的节点
+            // 循环出当前名称的一样的组件实例
+            while (parent && (!name||name!==componentName)) {
+                parent = parent.$parent;
+                if (parent) {
+                    name = parent.$options.name;
+                }
+            }
+            // 有节点表示当前找到了name一样的实例
+            if (parent) {
+                parent.$emit.apply(parent,[eventName].concat(params))
+            }
+        },
+        /**
+         * 广播 (向下查找) (广播多个)
+         * @param componentName // 需要找的组件的名称
+         * @param eventName // 事件名称
+         * @param params // 需要传递的参数
+         */
+        broadcast(componentName, eventName, params) {
+            broadcast.call(this,componentName, eventName, params)
+        }
+    }
+}

Dosya farkı çok büyük olduğundan ihmal edildi
+ 1 - 0
uview-ui/libs/util/province.js


+ 39 - 0
uview-ui/package.json

@@ -0,0 +1,39 @@
+{
+	"name": "uview-ui",
+	"version": "1.8.4",
+	"description": "uView UI,是uni-app生态优秀的UI框架,全面的组件和便捷的工具会让您信手拈来,如鱼得水",
+	"main": "index.js",
+	"keywords": [
+		"uview",
+		"uView",
+		"uni-app",
+		"uni-app ui",
+		"uniapp",
+		"uviewui",
+		"uview ui",
+		"uviewUI",
+		"uViewui",
+		"uViewUI",
+		"uView UI",
+		"uni ui",
+		"uni UI",
+		"uniapp ui",
+		"ui",
+		"UI框架",
+		"uniapp ui框架",
+		"uniapp UI"
+	],
+	"scripts": {
+		"test": "echo \"Error: no test specified\" && exit 1"
+	},
+	"repository": {
+		"type": "git",
+		"url": ""
+	},
+	"devDependencies": {
+		"node-sass": "^4.14.0",
+		"sass-loader": "^8.0.2"
+	},
+	"author": "uView",
+	"license": "MIT"
+}

+ 38 - 0
uview-ui/theme.scss

@@ -0,0 +1,38 @@
+// 此文件为uView的主题变量,这些变量目前只能通过uni.scss引入才有效,另外由于
+// uni.scss中引入的样式会同时混入到全局样式文件和单独每一个页面的样式中,造成微信程序包太大,
+// 故uni.scss只建议放scss变量名相关样式,其他的样式可以通过main.js或者App.vue引入
+
+$u-main-color: #303133;
+$u-content-color: #606266;
+$u-tips-color: #909399;
+$u-light-color: #c0c4cc;
+$u-border-color: #e4e7ed;
+$u-bg-color: #f3f4f6;
+
+$u-type-primary: #2979ff;
+$u-type-primary-light: #ecf5ff;
+$u-type-primary-disabled: #a0cfff;
+$u-type-primary-dark: #2b85e4;
+
+$u-type-warning: #ff9900;
+$u-type-warning-disabled: #fcbd71;
+$u-type-warning-dark: #f29100;
+$u-type-warning-light: #fdf6ec;
+
+$u-type-success: #19be6b;
+$u-type-success-disabled: #71d5a1;
+$u-type-success-dark: #18b566;
+$u-type-success-light: #dbf1e1;
+
+$u-type-error: #fa3534;
+$u-type-error-disabled: #fab6b6;
+$u-type-error-dark: #dd6161;
+$u-type-error-light: #fef0f0;
+
+$u-type-info: #909399;
+$u-type-info-disabled: #c8c9cc;
+$u-type-info-dark: #82848a;
+$u-type-info-light: #f4f4f5;
+
+$u-form-item-height: 70rpx;
+$u-form-item-border-color: #dcdfe6;

+ 9 - 0
zdsz_WeChatApplet.iml

@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="WEB_MODULE" version="4">
+  <component name="NewModuleRootManager" inherit-compiler-output="true">
+    <exclude-output />
+    <content url="file://$MODULE_DIR$" />
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+  </component>
+</module>

+ 9 - 0
zdsz_applet.iml

@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="WEB_MODULE" version="4">
+  <component name="NewModuleRootManager" inherit-compiler-output="true">
+    <exclude-output />
+    <content url="file://$MODULE_DIR$" />
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+  </component>
+</module>