cmy 2 rokov pred
rodič
commit
c7ad5bb83f
100 zmenil súbory, kde vykonal 9690 pridanie a 8242 odobranie
  1. 1 0
      api/index.js
  2. 50 0
      api/product.js
  3. 10 1
      api/user.js
  4. 15 1
      pages.json
  5. 252 44
      pages/index/cangp.vue
  6. 139 97
      pages/index/index.vue
  7. 0 405
      pages/order/evaluate.vue
  8. 0 74
      pages/order/expressInfo.vue
  9. 0 1
      pages/order/order.vue
  10. 21 17
      pages/order/orderDetail.vue
  11. 620 0
      pages/product/productMh.vue
  12. 37 7
      pages/user/user.vue
  13. BIN
      static/user/back.png
  14. 0 6
      store/index.js
  15. 36 76
      uview-ui/README.md
  16. 344 0
      uview-ui/changelog.md
  17. 78 0
      uview-ui/components/u--form/u--form.vue
  18. 47 0
      uview-ui/components/u--image/u--image.vue
  19. 72 0
      uview-ui/components/u--input/u--input.vue
  20. 44 0
      uview-ui/components/u--text/u--text.vue
  21. 47 0
      uview-ui/components/u--textarea/u--textarea.vue
  22. 54 0
      uview-ui/components/u-action-sheet/props.js
  23. 236 148
      uview-ui/components/u-action-sheet/u-action-sheet.vue
  24. 59 0
      uview-ui/components/u-album/props.js
  25. 259 0
      uview-ui/components/u-album/u-album.vue
  26. 0 256
      uview-ui/components/u-alert-tips/u-alert-tips.vue
  27. 44 0
      uview-ui/components/u-alert/props.js
  28. 243 0
      uview-ui/components/u-alert/u-alert.vue
  29. 0 290
      uview-ui/components/u-avatar-cropper/u-avatar-cropper.vue
  30. 0 1265
      uview-ui/components/u-avatar-cropper/weCropper.js
  31. 52 0
      uview-ui/components/u-avatar-group/props.js
  32. 103 0
      uview-ui/components/u-avatar-group/u-avatar-group.vue
  33. 78 0
      uview-ui/components/u-avatar/props.js
  34. 51 19
      uview-ui/components/u-avatar/u-avatar.vue
  35. 54 0
      uview-ui/components/u-back-top/props.js
  36. 97 121
      uview-ui/components/u-back-top/u-back-top.vue
  37. 72 0
      uview-ui/components/u-badge/props.js
  38. 137 182
      uview-ui/components/u-badge/u-badge.vue
  39. 46 0
      uview-ui/components/u-button/nvue.scss
  40. 161 0
      uview-ui/components/u-button/props.js
  41. 452 558
      uview-ui/components/u-button/u-button.vue
  42. 80 0
      uview-ui/components/u-button/vue.scss
  43. 99 0
      uview-ui/components/u-calendar/header.vue
  44. 579 0
      uview-ui/components/u-calendar/month.vue
  45. 144 0
      uview-ui/components/u-calendar/props.js
  46. 355 611
      uview-ui/components/u-calendar/u-calendar.vue
  47. 85 0
      uview-ui/components/u-calendar/util.js
  48. 14 0
      uview-ui/components/u-car-keyboard/props.js
  49. 156 102
      uview-ui/components/u-car-keyboard/u-car-keyboard.vue
  50. 0 299
      uview-ui/components/u-card/u-card.vue
  51. 14 0
      uview-ui/components/u-cell-group/props.js
  52. 45 54
      uview-ui/components/u-cell-group/u-cell-group.vue
  53. 0 316
      uview-ui/components/u-cell-item/u-cell-item.vue
  54. 110 0
      uview-ui/components/u-cell/props.js
  55. 229 0
      uview-ui/components/u-cell/u-cell.vue
  56. 82 0
      uview-ui/components/u-checkbox-group/props.js
  57. 69 89
      uview-ui/components/u-checkbox-group/u-checkbox-group.vue
  58. 69 0
      uview-ui/components/u-checkbox/props.js
  59. 262 202
      uview-ui/components/u-checkbox/u-checkbox.vue
  60. 8 0
      uview-ui/components/u-circle-progress/props.js
  61. 181 203
      uview-ui/components/u-circle-progress/u-circle-progress.vue
  62. 0 147
      uview-ui/components/u-circle-progress/u-line-progress/u-line-progress.vue
  63. 79 0
      uview-ui/components/u-code-input/props.js
  64. 252 0
      uview-ui/components/u-code-input/u-code-input.vue
  65. 34 0
      uview-ui/components/u-code/props.js
  66. 51 86
      uview-ui/components/u-code/u-code.vue
  67. 29 0
      uview-ui/components/u-col/props.js
  68. 80 74
      uview-ui/components/u-col/u-col.vue
  69. 59 0
      uview-ui/components/u-collapse-item/props.js
  70. 181 160
      uview-ui/components/u-collapse-item/u-collapse-item.vue
  71. 19 0
      uview-ui/components/u-collapse/props.js
  72. 58 67
      uview-ui/components/u-collapse/u-collapse.vue
  73. 55 0
      uview-ui/components/u-column-notice/props.js
  74. 134 211
      uview-ui/components/u-column-notice/u-column-notice.vue
  75. 24 0
      uview-ui/components/u-count-down/props.js
  76. 145 300
      uview-ui/components/u-count-down/u-count-down.vue
  77. 62 0
      uview-ui/components/u-count-down/utils.js
  78. 59 0
      uview-ui/components/u-count-to/props.js
  79. 21 78
      uview-ui/components/u-count-to/u-count-to.vue
  80. 116 0
      uview-ui/components/u-datetime-picker/props.js
  81. 360 0
      uview-ui/components/u-datetime-picker/u-datetime-picker.vue
  82. 44 0
      uview-ui/components/u-divider/props.js
  83. 102 139
      uview-ui/components/u-divider/u-divider.vue
  84. 36 0
      uview-ui/components/u-dropdown-item/props.js
  85. 118 104
      uview-ui/components/u-dropdown-item/u-dropdown-item.vue
  86. 65 0
      uview-ui/components/u-dropdown/props.js
  87. 80 251
      uview-ui/components/u-dropdown/u-dropdown.vue
  88. 59 0
      uview-ui/components/u-empty/props.js
  89. 81 146
      uview-ui/components/u-empty/u-empty.vue
  90. 0 384
      uview-ui/components/u-field/u-field.vue
  91. 43 0
      uview-ui/components/u-form-item/props.js
  92. 172 368
      uview-ui/components/u-form-item/u-form-item.vue
  93. 45 0
      uview-ui/components/u-form/props.js
  94. 195 115
      uview-ui/components/u-form/u-form.vue
  95. 0 52
      uview-ui/components/u-full-screen/u-full-screen.vue
  96. 24 0
      uview-ui/components/u-gap/props.js
  97. 28 44
      uview-ui/components/u-gap/u-gap.vue
  98. 14 0
      uview-ui/components/u-grid-item/props.js
  99. 155 72
      uview-ui/components/u-grid-item/u-grid-item.vue
  100. 19 0
      uview-ui/components/u-grid/props.js

+ 1 - 0
api/index.js

@@ -50,3 +50,4 @@ export function store_list(data) {
 		data
 	});
 }
+

+ 50 - 0
api/product.js

@@ -191,3 +191,53 @@ export function jfShop(data) {
 		data
 	});
 }
+//盲盒列表
+export function mysteryList(data) {
+	return request({
+		url: '/api/mystery/list',
+		method: 'get',
+		data
+	});
+}
+//盲盒详情
+export function mysteryDetail(data) {
+	return request({
+		url: '/api/mystery/'+data.id,
+		method: 'get',
+		data
+	});
+}
+//盲盒详情
+export function mysteryPay(data) {
+	return request({
+		url: '/api/mystery/'+data.id,
+		method: 'post',
+		data
+	});
+}
+//我的盲盒
+export function mysteryMy(data) {
+	return request({
+		url: '/api/mystery/my',
+		method: 'get',
+		data
+	});
+}
+//打开盲盒
+export function openMysteryMy(data) {
+	return request({
+		url: '/api/mystery/my',
+		method: 'post',
+		data
+	});
+}
+// 合成方案
+export function craftGuide(data) {
+	return request({
+		url: '/api/nft/craft/guide',
+		method: 'get',
+		data
+	});
+}
+
+

+ 10 - 1
api/user.js

@@ -286,4 +286,13 @@ export function proposal(data) {
 		method: 'post',
 		data
 	});
-}
+}
+// nft列表 合并
+export function myClass(data) {
+	return request({
+		url: '/api/nft/my/class',
+		method: 'get',
+		data
+	});
+}
+

+ 15 - 1
pages.json

@@ -9,6 +9,7 @@
 				"navigationBarBackgroundColor": "#111111",
 				"navigationBarTextStyle": "white", 
 				"navigationBarTitleText": "艺绘",
+				"enablePullDownRefresh":true,
 				"app-plus": {
 					// "titleNView": false
 				}
@@ -123,7 +124,20 @@
 				"navigationBarTextStyle": "white", 
 				"app-plus": {
 					"titleNView": {
-						// "type": "transparent"
+						"type": "transparent"
+					}
+				}
+			}
+		},
+		{
+			"path": "pages/product/productMh",
+			"style": {
+				"navigationBarTitleText": "盲盒详情",
+				"navigationBarBackgroundColor": "#111111",
+				"navigationBarTextStyle": "white", 
+				"app-plus": {
+					"titleNView": {
+						"type": "transparent"
 					}
 				}
 			}

+ 252 - 44
pages/index/cangp.vue

@@ -2,35 +2,84 @@
 	<view class="container">
 		<view class="topNav">
 			<view class="hgHeight">
-				
+
 			</view>
 			<view class="flex">
-			<view class="titleItem" @click="tabList(0)" :class="{action:onIndex==0}">
-				我的盲盒
-			</view>
-			<view class="titleItem" @click="tabList(1)" :class="{action:onIndex==1}">
-				NFT合成
-			</view>
+				<view class="titleItem" @click="tabList(0)" :class="{action:onIndex==0}">
+					我的盲盒
+				</view>
+				<view class="titleItem" @click="tabList(1)" :class="{action:onIndex==1}">
+					NFT合成
+				</view>
 			</view>
 		</view>
 		<!-- 我的盲盒 -->
 		<view class="ls" v-show="onIndex==0">
 			<view class="ll" v-for="(item,index) in mh.list">
-				<!-- <image class="imgBox" src="" mode="scaleToFill"></image> -->
-				<view class="title clamp margin-t-20">
-					盲盒首发
-				</view>
-				<view class="tip flex margin-t-10">
-					<view class="clamp">官方发行</view>
-					<view class="buttom" @click="navTo(item)">打开</view>
+				<image class="imgBox" :src="item.mystery_box_info.pic" mode="scaleToFill"></image>
+				<view class="bottomTitle padding-20">
+
+					<view class="title clamp ">
+						{{item.mystery_box_info.name}}
+					</view>
+					<view class="tip flex margin-t-10">
+						<view>
+							<view class="clamp">
+								官方发行
+							</view>
+							<view class=" margin-t-10">
+								数量:{{item.mystery_box_count}}
+							</view>
+						</view>
+						<view class="buttom" @click="openMh(item)">打开</view>
+					</view>
 				</view>
 			</view>
-			<u-loadmore class='clearFloat' lineColor='#FFFFFF' iconColor='#FFFFFF' color="#FFFFFF" :status="mh.loadingType" />
+			<u-loadmore class='clearFloat' lineColor='#FFFFFF' iconColor='#FFFFFF' color="#FFFFFF"
+				:status="mh.loadingType" />
 		</view>
 		<!-- NFT合成 -->
 		<view class="ls" v-show="onIndex==1">
-			<u-loadmore class='clearFloat' lineColor='#FFFFFF' iconColor='#FFFFFF' color="#FFFFFF" :status="nft.loadingType" />
+			<view class="lt flex" v-for="(item,index) in nft.list">
+				<image class="leftImg" :src="item.res.uri" mode="scaleToFill"></image>
+				<view class="rightContent padding-20 flex">
+					<view class="left">
+						<view class="rightName clamp ">
+							{{item.name}}
+						</view>
+						<view class="flex margin-t-10">
+							<view class="item">
+
+							</view>
+						</view>
+					</view>
+					<image class="rightImg" src="../../static/user/back.png" mode="widthFix"></image>
+
+				</view>
+			</view>
+
+
+			<u-loadmore class='clearFloat' lineColor='#FFFFFF' iconColor='#FFFFFF' color="#FFFFFF"
+				:status="nft.loadingType" />
 		</view>
+		<u-modal @confirm='confirmPay' :showCancelButton='true' @cancel='showAlert=false' :show="showAlert"
+			title="请输入打开盲盒数量">
+			<view class="slot-content padding-t-20">
+				<u--input class='margin-l-30' border='surround' type='number' v-model="payNum" inputAlign='right'
+					@input="changeNum"></u--input>
+			</view>
+		</u-modal>
+		<u-modal @confirm='showAlertSuccess = false' :show="showAlertSuccess" title="盲盒开启结果">
+			<view class="slot-content padding-t-20">
+				
+				<view class="boxAlert flex" v-for="(item , inde) in successJg ">
+					<image class="img" :src="item.info.uri" mode="scaleToFill"></image>
+					<view>
+						{{item.info.name}}
+					</view>
+				</view>
+			</view>
+		</u-modal>
 	</view>
 </template>
 
@@ -43,22 +92,36 @@
 		weixindata
 	} from '@/utils/wxAuthorized';
 	// #endif
+	import {
+		mysteryMy,
+		openMysteryMy,
+		craftGuide
+	} from '@/api/product.js';
 	export default {
 		data() {
 			return {
+				// 当前选中的数据对象
+				actionItem: '',
+				payNum: 1, //打开的数量
+				// 弹出输入窗口
+				showAlert: false,
+				// 成功弹出窗
+				showAlertSuccess: false,
+				// 弹出开启盲盒结果
+				successJg: [],
 				onIndex: 0, //当前选中的分类
 				// 盲盒里诶啊哦
 				mh: {
 					page: 1,
-					limit: 10,
-					list: [{}, {}, {}, {}],
+					limit: 100,
+					list: [],
 					loaded: false,
 					loadingType: 'loadmore'
 				},
 				// nft合成列表
 				nft: {
 					page: 1,
-					limit: 10,
+					limit: 100,
 					list: [],
 					loaded: false,
 					loadingType: 'loadmore'
@@ -69,18 +132,106 @@
 			...mapState(['loginInterceptor', 'baseURL']),
 			...mapState('user', ['hasLogin', 'userInfo'])
 		},
-		onLoad: function(option) {},
+		onLoad: function(option) {
+			// 合成表
+			this.craftGuide();
+		},
+		onShow() {
+			// 我的盲盒
+			this.lodingMh();
+		},
 		methods: {
-			info() {
+			// nft合成列表
+			craftGuide(source) {
+				let navItem = this.nft;
+				if (source === 'tabChange' && navItem.loaded === true) {
+					//tab切换只有第一次需要加载数据
+					return;
+				}
+				if (navItem.loadingType === 'loading') {
+					//防止重复加载
+					return;
+				}
+				// 修改当前对象状态为加载中
+				navItem.loadingType = 'loading';
+				craftGuide({
+						page: navItem.page,
+						limit: navItem.limit,
+						status: 0
+					})
+					.then(({
+						data
+					}) => {
+						if (data.list.length > 0) {
+							navItem.list = navItem.list.concat(data.list);
+							navItem.page++;
+						}
+						if (navItem.limit == data.list.length) {
+							//判断是否还有数据, 有改为 more, 没有改为noMore
+							navItem.loadingType = 'loadmore';
+							return;
+						} else {
+							//判断是否还有数据, 有改为 more, 没有改为noMore
+							navItem.loadingType = 'nomore';
+						}
+						uni.hideLoading();
+						navItem.loaded = true;
+					})
+					.catch(e => {
+						console.log(e);
+					});
 
 			},
+			// 打开盲盒
+			confirmPay() {
+				const that = this;
+				that.showAlert = false;
+				uni.showLoading({
+					title: '开启中...',
+					mask: true
+				});
+				openMysteryMy({
+						num: that.payNum,
+						mystery_id: that.actionItem.mystery_box_id
+					})
+					.then(({
+						data
+					}) => {
+						// 显示成功弹窗{
+						that.showAlertSuccess = true;
+						that.successJg = data.boxes.map((i) => {
+							return i
+						})
+						uni.hideLoading()
+						console.log(data, 'dk');
+					})
+					.catch(e => {
+						uni.hideLoading()
+						console.log(e);
+					});
+			},
+			// 判断是否输入正确数量
+			changeNum(s) {
+				console.log(s);
+				let num = Math.floor(s);
+				if (s.indexOf('.') > -1) {
+					uni.showModal({
+						title: '错误',
+						content: '只可填入整数',
+						showCancel: false,
+					});
+				}
+				this.$nextTick(() => {
+					this.payNum = +num
+				})
+			},
 			tabList(index) {
 				// 保存当前选中的对象
 				this.onIndex = index;
 			},
 			// 加载盲盒
 			lodingMh(source) {
-				let navItem = this.mh.list;
+				let navItem = this.mh;
 				if (source === 'tabChange' && navItem.loaded === true) {
 					//tab切换只有第一次需要加载数据
 					return;
@@ -91,16 +242,16 @@
 				}
 				// 修改当前对象状态为加载中
 				navItem.loadingType = 'loading';
-				cashList({
+				mysteryMy({
 						page: navItem.page,
 						limit: navItem.limit,
-						pm: state
+						status: 0
 					})
 					.then(({
 						data
 					}) => {
 						if (data.list.length > 0) {
-							navItem.orderList = navItem.orderList.concat(data.list);
+							navItem.list = navItem.list.concat(data.list);
 							navItem.page++;
 						}
 						if (navItem.limit == data.list.length) {
@@ -118,6 +269,12 @@
 						console.log(e);
 					});
 			},
+			// 打开盲盒
+			openMh(item) {
+				this.actionItem = item;
+				this.showAlert = true;
+				console.log('dk');
+			},
 			navTo(url) {
 				uni.switchTab({
 					url
@@ -138,6 +295,28 @@
 		/* #ifdef APP */
 		padding-top: var(--status-bar-height);
 		/* #endif */
+		min-height: 100%;
+	}
+
+	.slot-content {
+		display: flex;
+		flex-wrap: wrap;
+
+		.boxAlert {
+			line-height: 1;
+			border: 1px solid #9F570E;
+			padding: 10rpx 20rpx;
+			color: #9F570E;
+			border-radius: 10rpx;
+			margin: 10rpx;
+
+			.img {
+				width: 30rpx;
+				height: 30rpx;
+				margin-right: 10rpx;
+				border-radius: 10rpx;
+			}
+		}
 	}
 
 	.topNav {
@@ -148,9 +327,11 @@
 		z-index: 999;
 		padding: 30rpx;
 		background-color: #181820;
-		.hgHeight{
+
+		.hgHeight {
 			height: var(--status-bar-height);
 		}
+
 		.titleItem {
 			width: 336rpx;
 			height: 80rpx;
@@ -172,20 +353,43 @@
 	.ls {
 		color: #FFFFFF;
 		padding: 30rpx;
-		padding-top: 140rpx;
+		padding-top: 170rpx;
 
 		.clearFloat {
 			clear: both;
 		}
-		.ll:nth-child(even) {
-			margin-left: 30rpx;
+		.lt{
+			width: 100%;
+			background-color: #1d1c21;
+			line-height: 0;
+			padding: 30rpx;
+			border-radius: 20rpx;
+			.leftImg{
+				flex-shrink: 0;
+				width: 100rpx;
+				height: 100rpx;
+				border-radius: 30rpx;
+			}
+			.rightContent{
+				line-height: 1;
+				flex-grow: 1;
+			.rightImg{
+				width: 20rpx;
+			}
+				
+			}
 		}
-
 		.ll {
 			width: 330rpx;
-			line-height: 1;
+			line-height: 0;
 			margin-bottom: 30rpx;
 			float: left;
+			border-radius: 20rpx;
+			overflow: hidden;
+
+			&:nth-child(even) {
+				margin-left: 30rpx;
+			}
 
 			.imgBox {
 				background-color: #1D1D22;
@@ -193,23 +397,27 @@
 				height: 330rpx;
 			}
 
-			.title {
-				font-size: $font-sm;
-			}
+			.bottomTitle {
+				background-color: #1d1c21;
+				line-height: 1;
 
-			.tip {
-				font-size: $font-sm - 4rpx;
+				.title {
+					font-size: $font-sm;
+				}
 
-				.buttom {
-					width: 98rpx;
-					padding: 10rpx 0;
-					text-align: center;
-					background: linear-gradient(270deg, #FFE7C4, #FFCE8A);
-					border-radius: 20rpx;
-					color: #9F570E;
+				.tip {
+					font-size: $font-sm - 4rpx;
+
+					.buttom {
+						width: 98rpx;
+						padding: 10rpx 0;
+						text-align: center;
+						background: linear-gradient(270deg, #FFE7C4, #FFCE8A);
+						border-radius: 20rpx;
+						color: #9F570E;
+					}
 				}
 			}
-
 		}
 	}
 </style>

+ 139 - 97
pages/index/index.vue

@@ -17,14 +17,16 @@
 		</view>
 		<view class="box">
 			<view class="box-title">
-				<view class="left">
-					艺绘
-				</view>
-				<view class="right" @click="navTo('/pages/index/cangp')">
-					查看更多 >
+				<view class="left flex">
+					<view class="item" @click="changeList(0)" :class="{action:checked==0}">
+						艺绘发售
+					</view>
+					<view class="item margin-l-30" @click="changeList(1)" :class="{action:checked==1}">
+						盲盒发售
+					</view>
 				</view>
 			</view>
-			<view class="box-content" v-for="(item,index) in list" @click="buy(item)">
+			<view v-show="checked==0" class="box-content" v-for="(item,index) in dataList[0].list" @click="buy(item)">
 				<view class="img">
 					<image :src="item.image" mode=""></image>
 				</view>
@@ -33,7 +35,7 @@
 						{{item.store_name}}
 					</view>
 					<view class="text">
-						黑道研究会 发行
+						艺绘官方 发行
 					</view>
 					<view class="text">
 						发行数量: {{item.stock}}份
@@ -43,51 +45,33 @@
 					</view>
 				</view>
 			</view>
-			<!-- <view class="box-content">
+			<u-loadmore  v-show="checked==0" :status="dataList[0].list.loadType" />
+			<view v-show="checked==1" class="box-content" v-for="(item,index) in dataList[1].list" @click="buyMh(item)">
 				<view class="img">
-					<image src="../../static/index/nvwang.png" mode=""></image>
+					<image :src="item.pic" mode=""></image>
 				</view>
 				<view class="content-title">
-					<view class="">
-						黑道女王
+					<view class="title">
+						{{item.name}}
 					</view>
 					<view class="text">
-						黑道研究会 发行
+						艺绘官方 发行
 					</view>
 					<view class="text">
-						发行数量: 14521份
-					</view>
-				</view>
-			</view> -->
-		</view>
-		<view class="btm" style="height: 44px;"></view>
-		<!-- 客服 -->
-		<!-- <uni-popup ref="popupkf" type="center">
-			<view class="popup-box">
-				<view class="img"><image src="../../static/img/img009.png" mode=""></image></view>
-				<view class="mian">
-					<view class="delivery">
-						<view class="title">已经为您定制专属客服</view>
-						<image src="../../static/img/img010.png" mode=""></image>
-					</view>
-					<view class="nocancel">客服VX:{{ text }}</view>
-					<view class="comfirm-box">
-						<view class="cancel" @click="kfClose()">取消</view>
-						<view class="comfirm" @click="comfirm(text)">复制微信</view>
+						¥{{item.price}}
 					</view>
 				</view>
 			</view>
-		</uni-popup> -->
-
+			<u-loadmore  v-show="checked==1" :status="dataList[1].list.loadType" />
+		</view>
 	</view>
 </template>
 
 <script>
 	import {
-		getBargainList,
-		getProducts
+		getProducts,
+		mysteryList
 	} from '@/api/product.js';
-	// import {mapState} from 'vuex'
 	import {
 		loadIndexs
 	} from '@/api/index.js'
@@ -95,32 +79,24 @@
 		mapState,
 		mapMutations
 	} from 'vuex';
-	// #ifdef H5
-	import {
-		weixindata,
-		shareLoad
-	} from '@/utils/wxAuthorized';
-	// #endif
 	export default {
 		data() {
 			return {
+				checked: 0, //当前选中的出售对象
 				isSc: 2,
-				text: '',
-				checkid: 0,
-				titleNViewBackground: '',
-				longitude: 0, //经度
-				latitude: 0, //纬度
-				swiperCurrent: 0,
-				swiperLength: 0,
 				carouselList: [], //轮播列表
-				bastList: [], //会员礼包
-				integralList: [], //兑换专区
-				list: [], //卡片信息
-				page: 1,
-				limit: 10,
-				loadType: 'more',
-				text: [],
-				article: []
+				dataList: [{
+					list: [], //艺发售
+					page: 1,
+					limit: 1,
+					loadType: 'loadmore',
+				}, {
+					list: [], //盲盒发售
+					page: 1,
+					limit: 1,
+					loadType: 'loadmore',
+				}]
+
 			};
 		},
 		computed: {
@@ -128,51 +104,115 @@
 			...mapState('user', ['hasLogin', 'userInfo'])
 		},
 		onLoad: function(option) {
-				this.loadIndex()
-				this.getProduct()
+			this.loadIndex()
 		},
 		onShow: function() {
-			if (this.hasLogin) {
-				this.loadIndex()
-				this.getProduct()
-			}
+			this.getProduct()
+		},
+		onReachBottom(){
+			this.getProduct()
 		},
-		// onReachBottom() {
-		// 	this.getproducts();
-		// },
 		methods: {
 			...mapMutations(['setLat', 'setLon']),
 			loadIndex() {
 				let obj = this
 				loadIndexs().then(res => {
-					// console.log(res, 'res');
 					obj.carouselList = res.data.banner
-					// console.log(res,'res');
-					// obj.list = res.data.info.fastList
-
 				})
 			},
+			// 切换选中的需要显示的列表数据
+			changeList(ind) {
+				// 判断是否重复点击
+				if (this.checked == ind) {
+					return
+				}
+				this.checked = ind;
+				this.getProduct()
+			},
+			// 更新数据
 			getProduct() {
-				let obj = this
-				getProducts().then(res => {
-					// obj.carouselList = res.data.banner
-					// console.log(res, 'res');
-					obj.list = res.data.map((e) => {
+				let obj = this;
+				let item = obj.dataList[obj.checked];
+				if (item.loadType === 'loading') {
+					//防止重复加载
+					return;
+				}
+				if (item.loadType === 'nomore') {
+					//防止重复加载
+					return;
+				}
+				// 修改当前对象状态为加载中
+				item.loadType = 'loading';
+				// 判断是否为艺绘商品
+				if (obj.checked == 0) {
+					this.getBaseYh(item)
+				}
+				// 判断是否盲盒
+				if (obj.checked == 1) {
+					this.mysteryList(item)
+				}
+				
+			},
+			// 盲盒列表
+			mysteryList(item){
+				mysteryList({
+					page: item.page,
+					limit: item.limit
+				}).then(res => {
+					let arr = res.data.list.map((e) => {
+						return e
+					})
+					item.list = item.list.concat(arr);
+					item.page++;
+					if (item.limit == arr.length) {
+						//判断是否还有数据, 有改为 more, 没有改为noMore
+						item.loadType = 'loadmore';
+						return;
+					} else {
+						//判断是否还有数据, 有改为 more, 没有改为noMore
+						item.loadType = 'nomore';
+					}
+				})
+			},
+			// 获取艺商品
+			getBaseYh(item) {
+				getProducts({
+					page: item.page,
+					limit: item.limit
+				}).then(res => {
+					let arr = res.data.map((e) => {
 						let time = new Date(e.sell_time * 1000);
 						e.cmy_pay_time = time.getFullYear() + '年' + (time.getMonth() + 1) + '月' + time
 							.getDate() + '日' + time.getHours() + '时' + time.getMinutes() + '分' + time
 							.getSeconds() + '秒'
 						return e
 					})
-
+					item.list = item.list.concat(arr);
+					item.page++;
+
+					if (item.limit == arr.length) {
+						//判断是否还有数据, 有改为 more, 没有改为noMore
+						item.loadType = 'loadmore';
+						return;
+					} else {
+						//判断是否还有数据, 有改为 more, 没有改为noMore
+						item.loadType = 'nomore';
+					}
 				})
 			},
+			// 普通商品
 			buy(item) {
 				console.log(item, 'res');
 				uni.navigateTo({
 					url: '/pages/product/product?id=' + item.id + '&isSc=' + this.isSc
 				})
 			},
+			// 盲盒
+			buyMh(item){
+				uni.navigateTo({
+					url: '/pages/product/productMh?id=' + item.id
+				})
+			},
 			navTo(url) {
 				uni.switchTab({
 					url
@@ -195,18 +235,20 @@
 <style lang="scss">
 	page {
 		width: 750rpx;
-		height: 100%;
+		min-height: 100%;
 		background: #111111;
 	}
 	.carousel {
 		width: 750rpx;
 		height: 375rpx;
+
 		.carousel-item {
-			height:100%;
-			width:100%;
-			.img{
-				height:100%;
-				width:100%;
+			height: 100%;
+			width: 100%;
+
+			.img {
+				height: 100%;
+				width: 100%;
 			}
 		}
 	}
@@ -230,13 +272,12 @@
 			padding: 30rpx;
 
 			.search-box {
-				justify-content: center;
+				justify-content: flex-start;
 				width: 698rpx;
 				height: 60rpx;
-				background: #191919;
-
-				// box-shadow: 0px 10rpx 20rpx 0px rgba(4, 114, 69, 0.22);
-				border-radius: 30rpx;
+				background: #26262E;
+				border-radius: 10rpx;
+				padding-left: 20rpx;
 
 				.search {
 					width: 34rpx;
@@ -261,13 +302,15 @@
 			margin-top: 20rpx;
 			display: flex;
 			justify-content: space-between;
-
+			margin-bottom: 50rpx;
 			.left {
-				font-size: 38rpx;
+				font-size: 34rpx;
 				font-family: PingFang SC;
-				font-weight: bold;
+				font-weight: 500;
 				color: #FFFFFF;
-				line-height: 120rpx;
+				.action {
+					color: #FDD58A;
+				}
 			}
 
 			.right {
@@ -282,7 +325,10 @@
 		}
 
 		.box-content {
-			margin: 50rpx 0;
+			margin-bottom: 50rpx;
+			background-color: rgb(15, 15, 15);
+			border-radius: 20rpx;
+			overflow: hidden;
 
 			.img {
 				width: 690rpx;
@@ -295,14 +341,10 @@
 			}
 
 			.content-title {
-				margin-top: -10rpx;
 				padding: 30rpx 20rpx;
-
 				border-bottom-left-radius: 10rpx;
 				border-bottom-right-radius: 10rpx;
-				border: 2rpx solid #533A6A;
-				// box-shadow: 0px 0px 20rpx 0px rgba(79,59,103,0.0600);
-
+				background-color: rgb(29, 28, 33);
 				.title {
 					font-size: 39rpx;
 					font-weight: bold;

+ 0 - 405
pages/order/evaluate.vue

@@ -1,405 +0,0 @@
-<template>
-	<view class="content">
-		<view class="order-item">
-			<view class="goods-box-single">
-				<image class="goods-img" :src="productInfo.image" mode="aspectFill"></image>
-				<view class="right position-relative">
-					<view class="flex">
-						<text class="title">{{ productInfo.store_name }}</text>
-						<view class="title-right">
-							<view class="price">{{ productInfo.price }}</view>
-							<view class="attr-box">x{{ list.cart_num }}</view>
-						</view>
-					</view>
-				</view>
-			</view>
-			<view>
-				<view class="flex_item zhil">
-					<view>商品质量</view>
-					<view><uniRate text="1" size="20" margin="10" :value="rateValue1" @change="rateChange1"></uniRate></view>
-				</view>
-				<view class="flex_item zhil">
-					<view>服务态度</view>
-					<view><uniRate text="1" size="20" margin="10" :value="rateValue2" @change="rateChange2"></uniRate></view>
-				</view>
-				<view class="equity_box">
-					<view class="text-box uni-textarea">
-						<textarea placeholder-style="color:#999" :placeholder="placeholder" @blur="bindTextAreaBlur"></textarea>
-					</view>
-					<view class="">
-						<view class="add-img-box flex_item">
-							<view class="add-img-item" v-for="(item, index) in imgList" :key="index">
-								<image class="add-img" @click.stop="imgInfo(index)" :src="item.url" mode="aspectFill"></image>
-								<image class="add-img-del" @click.stop="delImg(index)" src="/static/img/delete.png"></image>
-							</view>
-							<view v-if="imgList.length < 9" class="add-img-item" @click.stop="scImg()">
-								<image class="add-img" src="/static/img/add.png"></image>
-							</view>
-						</view>
-					</view>
-				</view>
-			</view>
-		</view>
-		<view @click.stop="submit" class="address-box submit-box"><text class="submit-btn">提交评论</text></view>
-	</view>
-</template>
-
-<script>
-import { product, upload, order_comment } from '@/api/order.js';
-import uniRate from '@/components/uni-rate/uni-rate.vue';
-export default {
-	components: {
-		uniRate
-	},
-	data() {
-		return {
-			list: '', //订单详情
-			productInfo: '',
-			text: '', //评论内容
-			placeholder: '商品满足你的期待么?说说你的想法,分享给想买的他们吧~',
-			imgList: [],
-			unique: '', //商品唯一标识码
-			cloudimgList: [],
-			rateValue1: '', //商品质量
-			rateValue2: '', //服务态度
-			imgCount: 6 //最多支持9张上传,可以修改
-		};
-	},
-	onLoad(option) {
-		this.unique = option.unique;
-		this.loadOrder();
-	},
-	onShow() {},
-	methods: {
-		//text
-		bindTextAreaBlur: function(e) {
-			this.text = e.detail.value;
-		},
-		//获取收入支出信息
-		async loadOrder() {
-			product({
-				unique: this.unique
-			}).then(e => {
-				this.list = e.data;
-				this.productInfo = e.data.productInfo;
-			});
-		},
-		//商品质量评分
-		rateChange1(val) {
-			this.rateValue1 = val.value;
-		},
-		//服务态度评分
-		rateChange2(val) {
-			this.rateValue2 = val.value;
-		},
-		//单张上传图片
-		scImg() {
-			let obj = this;
-			console.log(obj.imgCount, 11);
-			if (obj.imgCount == 0) {
-				uni.showToast({
-					title: '最多添加6张图片',
-					icon: 'none'
-				});
-				return;
-			}
-			upload({
-				file: ''
-			})
-				.then(e => {
-					console.log(e,'e')
-					obj.imgList = [...obj.imgList, ...e];
-					console.log(obj.imgList,'imgList')
-					obj.imgCount = 10 - obj.imgList.length;
-					console.log(obj.imgCount ,'imgCount ')
-				})
-				.catch(e => {});
-		},
-		//提交评论
-		submit(e) {
-			let obj = this;
-			if (obj.imgList.length < 1) {
-				uni.showToast({
-					title: '请添加图片',
-					icon: 'none'
-				});
-				return;
-			}
-			for (let i = 0; i < obj.imgList.length; i++) {
-				obj.cloudimgList.push(obj.imgList[i].url);
-			}
-			let arr = obj.cloudimgList.join(',');
-			order_comment({
-				pics: arr,
-				comment: obj.text,
-				product_score: obj.rateValue1,
-				service_score: obj.rateValue2,
-				unique: obj.unique,
-				
-			})
-				.then(e => {
-					uni.navigateTo({
-						url: '/pages/order/order?state=4'
-					});
-				})
-				.catch(e => {
-					uni.navigateTo({
-						url: '/pages/order/order?state=4'
-					});
-				});
-		},
-		//点击图片显示大图
-		imgInfo(i) {
-			let tempList = [];
-			console.log(111);
-			this.imgList.forEach(e => {
-				tempList.push(e.url);
-			});
-			console.log(tempList);
-			//显示图片
-			uni.previewImage({
-				current: i,
-				loop: false,
-				urls: tempList,
-				indicator: 'default'
-			});
-		},
-		//删除图片
-		delImg(i) {
-			uni.showModal({
-				content: '确定删除这张吗',
-				success: res => {
-					if (res.confirm) {
-						this.imgList.splice(i, 1);
-						this.imgCount++;
-					} else if (res.cancel) {
-					}
-				}
-			});
-		},
-		// 页面跳转
-		navto(e) {
-			uni.navigateTo({
-				url: e
-			});
-		}
-	}
-};
-</script>
-
-<style lang="scss">
-page {
-	background: #ffffff;
-	height: 100%;
-	.content {
-		background: #ffffff;
-		height: 100%;
-	}
-}
-/* 多条商品 */
-.order-item {
-	display: flex;
-	flex-direction: column;
-	padding: 0rpx 30rpx;
-	background: #fff;
-	margin-top: 20rpx;
-	/* 单条商品 */
-	.goods-box-single {
-		display: flex;
-		padding: 20rpx 0;
-		.goods-img {
-			display: block;
-			width: 120rpx;
-			height: 120rpx;
-		}
-		.right {
-			flex: 1;
-			display: flex;
-			flex-direction: column;
-			padding: 0 30rpx 0 24rpx;
-			overflow: hidden;
-			height: 100%;
-			.title {
-				align-self: flex-start;
-				font-size: $font-base + 2rpx;
-				color: $font-color-dark;
-				height: 80rpx;
-				overflow:hidden; 
-				text-overflow:ellipsis;
-				display:-webkit-box; 
-				-webkit-box-orient:vertical;
-				-webkit-line-clamp:2; 
-			}
-			.title-right {
-				flex-shrink: 0;
-				text-align: right;
-				align-self: flex-start;
-			}
-			.attr-box {
-				font-size: $font-sm + 2rpx;
-				color: $font-color-light;
-			}
-			.price {
-				font-size: $font-base + 2rpx;
-				color: $font-color-dark;
-				&:before {
-					content: '¥';
-					font-size: $font-sm;
-					margin: 0 2rpx 0 8rpx;
-				}
-			}
-		}
-	}
-}
-.equity_box {
-	background-color: #fafafa;
-	border-radius: 10rpx;
-	padding: 25rpx 25rpx;
-	margin: 25rpx 0rpx;
-	.text-box {
-		height: 200rpx;
-		textarea {
-			font-size: 25rpx;
-			width: 100%;
-			height: 100%;
-			overflow: hidden;
-			text-overflow: ellipsis;
-			display: -webkit-box;
-			-webkit-box-orient: vertical;
-			-webkit-line-clamp: 5;
-		}
-	}
-}
-.zhil {
-	font-size: 28rpx !important;
-	padding: 15rpx 15rpx;
-}
-.submit-box {
-	bottom: 0;
-	left: 0;
-	width: 750rpx;
-}
-.submit-btn {
-	margin-top: 10px;
-	display: inline-block;
-	width: 670rpx;
-	height: 96rpx;
-	line-height: 96rpx;
-	text-align: center;
-	background-color: #1BCC26 !important;
-	opacity: 1;
-	border-radius: 32rpx;
-	border-width: 8rpx;
-	border-color: rgba(255, 255, 255, 1);
-	box-shadow: 0rpx 8rpx 12rpx rgba(0, 0, 0, 0.16);
-	border-radius: 56rpx;
-	font-size: 39rpx;
-	font-weight: bold;
-	color: rgba(255, 255, 255, 1);
-}
-.submit-btn-txt {
-	font-size: 39rpx;
-	font-weight: bold;
-	line-height: 47rpx;
-	color: rgba(255, 255, 255, 1);
-	opacity: 1;
-}
-.map-box {
-	width: 484rpx;
-	height: 256rpx;
-	border-width: 4rpx;
-	border-color: rgba(255, 255, 255, 1);
-	box-shadow: 0rpx 0rpx 24rpx rgba(0, 0, 0, 0.16);
-	/* border-radius: 12rpx; */
-	position: relative;
-}
-.map {
-	position: absolute;
-	top: 0;
-	left: 0;
-	right: 0;
-	bottom: 0;
-	width: 476rpx;
-	height: 250rpx;
-}
-.map-img {
-	position: absolute;
-	top: 90rpx;
-	left: 156rpx;
-	width: 230rpx;
-	height: 68rpx;
-	background-color: rgba(51, 51, 51, 0.64);
-	border-width: 1rpx;
-	border-color: rgba(0, 0, 0, 0);
-	border-radius: 34px;
-
-	font-size: 28rpx;
-	font-weight: bold;
-	line-height: 66rpx;
-	color: rgba(255, 255, 255, 1);
-	text-align: center;
-}
-.address-box {
-	padding: 15rpx 40rpx;
-	margin-bottom: 10px;
-}
-.label {
-	font-size: 36rpx;
-	font-weight: bold;
-	line-height: 50rpx;
-	color: #222222;
-}
-.label-img {
-	padding-left: 40rpx;
-}
-
-.add-img-box {
-	width: 100%;
-	flex-direction: row;
-	flex-wrap: wrap;
-	margin-top: 50rpx;
-}
-.add-img-item {
-	width: 180rpx;
-	height: 180rpx;
-	border-radius: 24rpx;
-	position: relative;
-	margin: 0rpx 20rpx;
-	margin-bottom: 25rpx;
-	.add-img {
-		width: 100%;
-		height: 100%;
-		border-radius: 24rpx;
-	}
-}
-.add-img-camera {
-	flex: 1;
-}
-.add-img-del {
-	position: absolute;
-	width: 40rpx;
-	height: 40rpx;
-	left: 155rpx;
-	bottom: 155rpx;
-	//background-color: rgba(238, 0, 0, 1);
-	border-radius: 20rpx;
-}
-.address-time {
-	width: 484rpx;
-	height: 88rpx;
-	background-color: rgba(245, 245, 245, 1);
-	opacity: 1;
-	border-radius: 24rpx;
-	text-align: center;
-
-	font-size: 35rpx;
-	font-weight: 500;
-	color: rgba(51, 51, 51, 1);
-}
-.line {
-	width: 750rpx;
-	height: 1px;
-	transform: scaleY(0.3);
-	background-color: rgba(0, 0, 0, 0.5);
-}
-</style>

+ 0 - 74
pages/order/expressInfo.vue

@@ -1,74 +0,0 @@
-<template>
-	<view class="container">
-		<view class="express-box">
-			<view class="top-text">
-				<text class="top-com">{{expressList.com}}</text>
-				<text>{{ expressList.nu }}</text>
-			</view>
-			<view class="express-body">
-				<uni-steps :options="expressList.data" active-color="#007AFF" :active="active" direction="column"></uni-steps>
-			</view>
-		</view>
-	</view>
-</template>
-
-<script>
-import { express_query } from '@/api/order.js';
-import uniSteps from '@/components/uni-steps/uni-steps.vue';
-export default {
-	components: {
-		uniSteps
-	},
-	data() {
-		return {
-			id: '',
-			expressList: [],
-			active: 0
-		}
-	},
-	onLoad(option) {
-		this.id = option.id
-	},
-	onShow() {
-		this.loadData()
-	},
-	methods: {
-		loadData() {
-			express_query({
-				id: this.id,
-			}).then(({data}) => {
-				this.expressList = data;
-				console.log(this.expressList)
-			})
-		}
-	}
-}
-</script>
-
-<style lang="scss">
-.container {
-	padding: 40rpx;
-	.express-box {
-		border-radius: $border-radius-sm;
-		.top-text {
-			width: 100%;
-			height: 60rpx;
-			background: #FFFFFF;
-			padding-left: 20rpx;
-			font-size: $font-base;
-			border-radius: $border-radius-sm;
-			display: flex;
-			align-items: center;
-			.top-com {
-				margin-right: 10rpx;
-			}
-		}
-		.express-body {
-			margin-top: 20rpx;
-			background: #FFFFFF;
-			border-radius: $border-radius-sm;
-			padding: 20rpx 10rpx 20rpx 0;
-		}
-	}
-}
-</style>

+ 0 - 1
pages/order/order.vue

@@ -63,7 +63,6 @@
 							<!-- <button v-if="item._status._title == '未发货' && item.is_gift != 1" class="action-btn" @click.stop="orderRefund(item)">申请退款</button> -->
 						</view>
 					</view>
-
 					<uni-load-more :status="tabItem.loadingType"></uni-load-more>
 				</scroll-view>
 			</swiper-item>

+ 21 - 17
pages/order/orderDetail.vue

@@ -40,12 +40,12 @@
 							<view class="attr-box">
 								{{ goodsItem.attrInfo ? goodsItem.attrInfo.suk : '' }} x {{ goodsItem.cart_num + '' + (goodsItem.productInfo.unit_name || '') }}
 							</view>
-							<view>
+							<!-- <view>
 								<view v-if="status._type == 3">
 									<view @click="evaluate(goodsItem)" class="yesevaluate" v-if="goodsItem.is_reply > 0"><text>已评价</text></view>
 									<view @click="evaluate(goodsItem)" class="evaluate" v-if="goodsItem.is_reply < 1"><text>去评价</text></view>
 								</view>
-							</view>
+							</view> -->
 						</view>
 					</view>
 				</view>
@@ -127,7 +127,9 @@
 					<text class="title" v-if="item.delivery_type == 'send'">送货人员:</text>
 					<view class="text">{{ item.delivery_name }}</view>
 				</view>
+				<!-- #ifdef H5 -->
 				<view v-if="status._type != 1 && status._type != 0" class="buttom-right" @click="towuliu(item)">查看物流</view>
+				<!-- #endif -->
 			</view>
 			<view class="item flex">
 				<view class="title-left flex">
@@ -214,27 +216,29 @@ export default {
 				console.log(e);
 			});
 		},
+			// #ifdef H5
 		// 查看快递单号
 		towuliu(item) {
 			let delivery_id = item.delivery_id;
 			window.location.href = 'https://m.kuaidi100.com/result.jsp?nu=' + delivery_id;
 		},
+			// #endif
 		//跳转到评价页面
-		evaluate(e) {
-			let unique = e.unique;
-			let is_reply = e.is_reply;
-			if (is_reply == 0) {
-				uni.navigateTo({
-					url: '/pages/order/evaluate?unique=' + unique
-				});
-			} else {
-				uni.showToast({
-					title: '已评价!',
-					duration: 1500,
-					icon: 'none'
-				});
-			}
-		},
+		// evaluate(e) {
+		// 	let unique = e.unique;
+		// 	let is_reply = e.is_reply;
+		// 	if (is_reply == 0) {
+		// 		uni.navigateTo({
+		// 			url: '/pages/order/evaluate?unique=' + unique
+		// 		});
+		// 	} else {
+		// 		uni.showToast({
+		// 			title: '已评价!',
+		// 			duration: 1500,
+		// 			icon: 'none'
+		// 		});
+		// 	}
+		// },
 		// 复制订单编号
 		copyOrderId(text) {
 			// #ifndef H5

+ 620 - 0
pages/product/productMh.vue

@@ -0,0 +1,620 @@
+<template>
+	<view class="container padding-b-30">
+		<view class="swiper-box">
+			<view class="img">
+				<image :src="shopDetail.pic" mode="widthFix"></image>
+			</view>
+		</view>
+		<view class="content-box">
+			<view class="content-top">
+				<view class="hd">{{ shopDetail.name }}</view>
+				<!-- <view class="content-img">
+					<image src="../../static/img/fx.png" mode=""></image>分享
+				</view> -->
+			</view>
+			<view class="content-center">
+				<view class="title">发行方:艺绘官方 </view>
+			</view>
+			<view class="content-bottom">
+				<view class="price">
+					{{ shopDetail.price }}
+					<span>RMB</span>
+				</view>
+				<view class="gobuy" @click="buy()">立即购买</view>
+			</view>
+		</view>
+		<view class="rz">
+			<view class="rz-title">认证信息</view>
+			<view class="rz-ms">
+			<view class="tc-item flex">
+				<view class="tcitem-name">创作者</view>
+				<view class="ali-name clamp">艺绘官方</view>
+			</view>
+			<view class="tc-item flex margin-t-30">
+				<view class="tcitem-name">发行方</view>
+				<view class="ali-name clamp">艺绘官方</view>
+			</view>
+			</view>
+		</view>
+		<view class="rz">
+			<view class="rz-title">购买需知</view>
+			<view class="rz-ms">
+				1.著作权人同意,在交易完成后,您将获得该数字艺术品除人身权利外的其他著作权(包括复制权、发行权、出租权、展览权、表演权、放映权、广播权、信息网络传播权、摄制权、改编权、翻译权、汇编权等)。
+				<br/>
+				2.数字藏品的版权由发行方或创作者所有,除另行去取得版权 所有者书面同意外, 用户不得将数字藏品用于任何商业用途。
+			</view>
+		</view>
+		<view class="rz">
+			<view class="rz-title">权益需知</view>
+			<view class="rz-ms">
+				数字藏品为虚拟数字商品,而非实物,仅限实名认证为年满18周岁的中国大陆用户购买。本商品一经售出,不支持任何形式的退换货。本商品源文件不支持本地下载。请勿对数字藏品进行炒作、场外交易、欺诈,或以其他任何非法形式进行交易和使用。
+			</view>
+		</view>
+		<uni-popup ref="popupkf" type="bottom">
+
+			<view class="popup-box">
+				<view class="pop-title flex borde-b">
+					<view class="title ">
+						购买数量
+					</view>
+					<u--input :border='"surround"' class='margin-l-30' placeholder="请输入数量" type='number' clearable v-model="num" inputAlign='right'
+						@input="changeNum"></u--input>
+				</view>
+				<view class="pop-radio">
+					<radio-group name="">
+						<!-- #ifdef APP-PLUS -->
+						<label class="pop-radio-box" @click="type = 'ali'">
+							<view class="pop-radio-cont">
+								<image src="../../static/img/ali.png" style="width: 40rpx;height: 40rpx;"
+									mode="scaleToFill"></image>
+								<text>支付宝支付</text>
+							</view>
+							<radio :checked="type == 'ali'" style="transform: scale(0.8);" />
+						</label>
+						<!-- <label class="pop-radio-box" @click="type='weixin'">
+							<view class="pop-radio-cont">
+								<image src="../../static/img/weixin.png" style="width: 40rpx;height: 40rpx;"
+									mode="scaleToFill">
+								</image>
+								<text>微信支付</text>
+							</view>
+							<radio :checked="type=='weixin'" style="transform: scale(0.8);" />
+						</label> -->
+						<!-- #endif -->
+						<label class="pop-radio-box" @click="type = 'yue'">
+							<view class="pop-radio-cont">
+								<image src="../../static/img/yue.png" style="width: 40rpx;height: 40rpx;"
+									mode="scaleToFill"></image>
+								<text>余额支付:{{ now_money }}</text>
+							</view>
+							<radio :checked="type == 'yue'" style="transform: scale(0.8);" />
+						</label>
+					</radio-group>
+					<view class="pop-bottom">
+						<view class="pirce">
+							<view class="price-left">合计:</view>
+							<view class="price-right">
+								¥
+								<span>{{ shopDetail.price*num }}</span>
+							</view>
+						</view>
+						<!-- "!payLoding ? pay() : ''" -->
+						<view class="buy" @click="pay()" :class="{ clickbg: payLoding }">立即支付</view>
+					</view>
+				</view>
+			</view>
+		</uni-popup>
+	</view>
+</template>
+
+<script>
+	import {
+		balance
+	} from '@/api/wallet.js';
+	import {
+		mapState
+	} from 'vuex';
+	import uniCopy from '@/components/js_sdk/xb-copy/uni-copy.js';
+	import {
+		mysteryDetail,
+		mysteryPay
+	} from '@/api/product.js';
+	export default {
+		data() {
+			return {
+				num: 1, //默认购买数量
+				now_money: 0, //余额
+				payLoding: true, //判断是否支付中
+				type: 'yue', //支付状态
+				shopDetail: {
+
+				}, //商品详情
+				weixin: '',
+				yue: '',
+				shopId: '', //商品id
+				list: [],
+			};
+		},
+		onLoad(options) {
+			this.shopId = options.id;
+			// 加载详情
+			this.goodsDetail();
+			// 载入余额
+			balance({}).then(({
+				data
+			}) => {
+				// 获取余额
+				this.now_money = data.now_money;
+			});
+		},
+		computed: {
+			...mapState(['weichatObj', 'baseURL', 'urlFile']),
+			...mapState('user', ['userInfo', 'hasLogin'])
+		},
+		methods: {
+			// 判断是否输入正确数量
+			changeNum(s) {
+				console.log(s);
+				let num = Math.floor(s);
+				if(s.indexOf('.')>-1){
+					uni.showModal({
+						title: '错误',
+						content: '只可填入整数',
+						showCancel: false,
+					});
+				}
+				this.$nextTick(() => {
+					this.num = +num
+				})
+			},
+			//选择支付方式
+			changePayType(type) {
+				this.type = type;
+			},
+			//复制
+			copy(value) {
+				let obj = this;
+				let content = value; //需要复制的内容
+				console.log('复制的内容:', content);
+				// content = typeof content === 'string' ? content : content.toString(); // 复制内容,必须字符串,数字需要转换为字符串
+				const result = uniCopy(content);
+				if (result === false) {
+					uni.showToast({
+						title: '不支持'
+					});
+				} else {
+					uni.showToast({
+						title: '复制成功',
+						icon: 'none'
+					});
+				}
+			},
+			// 支付
+			buy() {
+				this.$refs.popupkf.open();
+			},
+			//支付
+			async pay() {
+				let obj = this;
+				if (obj.type == 'yue' && obj.now_money * 1 < obj.shopDetail.price * 1) {
+					uni.showModal({
+						title: '提示',
+						content: '账户余额不足!',
+						showCancel: false
+					});
+					return;
+				}
+				// uni.hideLoading()
+				uni.showLoading({
+					title: '支付中',
+					mask: true
+				});
+				// 判断是否支付中
+				if (!obj.payLoding) {
+					return;
+				}
+				obj.payLoding = false;
+				if (obj.type == 'weixin') {
+					obj.$api.msg('微信暂未开通!');
+					uni.hideLoading();
+					obj.payLoding = true;
+					return;
+				}
+				obj.marketPay();
+			},
+			// 市场支付
+			marketPay() {
+				const obj = this;
+				mysteryPay({
+						pay_type: obj.type,
+						id: obj.shopId,
+						num: obj.num
+					})
+					.then(res => {
+						console.log(res, '购入');
+						uni.hideLoading();
+						if (obj.type == 'yue') {
+							obj.paySuccessTo();
+						}
+						if (obj.type == 'ali') {
+							const url = res.data.payConfig;
+							console.log(url, 'url');
+							uni.requestPayment({
+								provider: 'alipay',
+								orderInfo: url,
+								success: res => {
+									obj.paySuccessTo();
+								},
+								fail: e => {
+									uni.showModal({
+										title: '错误',
+										content: '未成功支付',
+										showCancel: false
+									});
+									console.log(e);
+								},
+								complete: () => {}
+							});
+						}
+					})
+					.catch(() => {
+						obj.payLoding = true;
+					});
+
+				return;
+			},
+			// 获取商品详情
+			goodsDetail() {
+				let obj = this;
+				mysteryDetail({
+					id: obj.shopId
+				}, ).then(res => {
+					obj.shopDetail = res.data.info;
+					console.log(res, 'xq');
+				});
+			},
+			// 支付成功跳转
+			paySuccessTo() {
+				uni.hideLoading();
+				uni.redirectTo({
+					url: '/pages/money/paySuccess?orderKey=' + this.orderKey
+				});
+			}
+		}
+	};
+</script>
+
+<style lang="scss">
+	.container,
+	page {
+		background: #111111;
+		min-height: 100%;
+	}
+
+	/deep/ .rich-img {
+		width: 100% !important;
+		height: auto;
+	}
+
+	/* #ifdef MP */
+	.rich-img {
+		width: 100% !important;
+		height: auto;
+	}
+
+	// 处理图片间白色间距问题
+	.pHeight {
+		line-height: 0;
+	}
+
+	/* #endif */
+
+	.swiper-box {
+		background: #0c0a36;
+	}
+
+	.img {
+		margin: 0 auto;
+		width: 750rpx;
+
+		image {
+			width: 100%;
+		}
+	}
+
+	.content-box {
+		border-bottom: 2rpx solid #919295;
+		padding: 30rpx;
+		display: flex;
+		flex-direction: column;
+
+		.content-top {
+			display: flex;
+			justify-content: space-between;
+			align-items: center;
+
+			.hd {
+				font-size: 38rpx;
+				font-family: PingFang SC;
+				font-weight: bold;
+				color: #ffffff;
+				line-height: 42rpx;
+			}
+
+			.content-img {
+				display: flex;
+				align-items: center;
+				justify-content: space-around;
+				font-size: 24rpx;
+				font-family: PingFang SC;
+				font-weight: 500;
+				color: #585be7;
+				line-height: 39rpx;
+				padding: 10rpx;
+				height: 41rpx;
+				border: 2rpx solid #585be7;
+				border-radius: 5rpx;
+
+				image {
+					width: 26rpx;
+					height: 26rpx;
+				}
+			}
+		}
+
+		.content-center {
+			margin: 20rpx 0;
+			display: flex;
+			flex-direction: column;
+
+			.title {
+				font-size: 24rpx;
+				font-weight: 500;
+				color: #ffffff;
+				line-height: 42rpx;
+			}
+		}
+
+		.content-bottom {
+			display: flex;
+			align-items: center;
+			justify-content: space-between;
+
+			.price {
+				display: flex;
+				justify-content: flex-end;
+				font-size: 48rpx;
+				font-weight: bold;
+				color: #585be7;
+
+				span {
+					font-size: 24rpx;
+					font-weight: bold;
+					color: #666666;
+					font-family: SourceHanSerifSC;
+					line-height: 66rpx;
+				}
+			}
+
+			.gobuy {
+				width: 334rpx;
+				height: 90rpx;
+				background: linear-gradient(270deg, #6e8df7, #9977f6);
+				border-radius: 10rpx;
+				font-size: 36rpx;
+				font-weight: 500;
+				color: #ffffff;
+				line-height: 90rpx;
+				text-align: center;
+			}
+		}
+	}
+
+	.rz {
+		display: flex;
+		flex-direction: column;
+		align-items: center;
+
+		.rz-title {
+			margin: 35rpx 0;
+			font-size: 36rpx;
+			font-family: PingFang SC;
+			font-weight: bold;
+			color: #ffffff;
+			line-height: 42rpx;
+		}
+
+		.rich {
+			display: block;
+			justify-content: center;
+			margin: 30rpx;
+			width: 750rpx;
+			overflow: hidden;
+		}
+
+		.rz-ms {
+			padding: 30rpx;
+			width: 690rpx;
+			font-size: 24rpx;
+			line-height: 36rpx;
+			color: #ffffff;
+			word-wrap: break-word;
+			word-break: normal;
+			background-color: rgb(29, 28, 33);
+			border-radius: 20rpx;
+		}
+
+		.rz-content {
+			padding: 20rpx;
+			display: flex;
+			flex-direction: column;
+			width: 690rpx;
+			background: #222222;
+			border-radius: 20rpx;
+
+			.con-box {
+				line-height: 60rpx;
+				display: flex;
+				justify-content: space-between;
+				align-items: center;
+
+				.left {
+					font-size: 24rpx;
+					color: #a5a5a5;
+				}
+
+				.right {
+					display: flex;
+					align-items: center;
+
+					.address {
+						color: #6363fe;
+						font-size: 24rpx;
+						width: 300rpx;
+						text-align: right;
+						overflow: hidden;
+						text-overflow: ellipsis;
+						white-space: nowrap;
+					}
+
+					.copy {
+						margin-left: 15rpx;
+						margin-bottom: 22rpx;
+						width: 30rpx;
+						height: 30rpx;
+
+						image {
+							width: 30rpx;
+							height: 30rpx;
+						}
+					}
+				}
+			}
+		}
+
+		.tc-item {
+			width: 100%;
+			line-height: 1;
+			.tcitem-name {
+				flex-shrink: 0;
+				font-size: 30rpx;
+				font-family: PingFang SC;
+				font-weight: 500;
+				color: #fff;
+			}
+
+			.ali-name {
+				font-size: 30rpx;
+				font-family: PingFang SC;
+				font-weight: 500;
+				color: #FDD58A;
+			}
+
+			.tcitem-image {
+				flex-shrink: 0;
+				width: 26rpx;
+				height: 28rpx;
+			}
+		}
+	}
+
+	.popup-box {
+		width: 750rpx;
+		background: #ffffff;
+		border-radius: 10rpx 10rpx 0px 0px;
+		padding-bottom: 130rpx;
+
+		.pop-title {
+			line-height: 1;
+			padding: 30rpx;
+			padding-bottom: 10rpx;
+			font-size: 34rpx;
+			font-weight: bold;
+			color: #333333;
+		}
+
+		.pop-radio {
+			display: flex;
+
+			.pop-radio-box {
+				height: 100rpx;
+				display: flex;
+				align-items: center;
+				padding: 25rpx;
+				justify-content: space-between;
+				width: 750rpx;
+
+				.pop-radio-cont {
+					display: flex;
+
+					img {
+						width: 40rpx;
+						height: 40rpx;
+					}
+
+					text {
+						margin-left: 10rpx;
+						font-size: 28rpx;
+						font-weight: 400;
+						color: #3f454b;
+					}
+				}
+			}
+
+			.pop-bottom {
+				position: fixed;
+				bottom: 0;
+				right: 0;
+				height: 93rpx;
+				width: 750rpx;
+				display: flex;
+				justify-content: space-between;
+
+				.pirce {
+					display: flex;
+					align-items: center;
+					margin-left: 25rpx;
+
+					.price-left {
+						font-size: 28rpx;
+						font-family: PingFang SC;
+						font-weight: 400;
+						color: #3f454b;
+						line-height: 100px;
+					}
+
+					display: flex;
+
+					.price-right {
+						font-size: 24rpx;
+						font-weight: 500;
+						color: #fd3b39;
+						line-height: 55px;
+
+						span {
+							font-size: 36rpx;
+							font-weight: 500;
+							color: #fd3b39;
+							line-height: 55px;
+						}
+					}
+				}
+
+				.buy {
+					width: 360rpx;
+					height: 93rpx;
+					background: linear-gradient(270deg, #6e8df7, #9977f6);
+					font-size: 32rpx;
+					font-weight: bold;
+					line-height: 93rpx;
+					text-align: center;
+					color: #ffffff;
+				}
+			}
+		}
+	}
+
+	.clickbg {
+		background-color: $color-gray !important;
+	}
+</style>

+ 37 - 7
pages/user/user.vue

@@ -11,6 +11,14 @@
 				</view>
 			</view>
 		</view>
+		<view class="hashAddress flex">
+			<view class="clamp">
+				区块链地址:{{userInfo.hash_address}}
+			</view>
+			<view class="fzButtom" @click="uniCopy(userInfo.hash_address)">
+				复制
+			</view>
+		</view>
 		<view class="main-box">
 			<view class="title flex" @click="navTo('/pages/order/order?state=0')">
 				<view class="title-left">
@@ -100,15 +108,19 @@
 								<view class="zm" v-if="item.hang != null">转卖中</view>
 							</view>
 							<view class="content-price">
-								<view class="price">
+								<view class="price" v-if="tabCurrentIndex==1">
 									¥{{ item.price }}
-
-									<!-- ¥{{item.class_info.name}} -->
+								</view>
+								<view class="price" v-if="tabCurrentIndex==0">
+									数量{{ item.nft_count }}
 								</view>
 								<view class="" v-show="tabCurrentIndex != 1 && isshow == 1">
 									<view class="button" @click="navPop(item)" v-if="item.hang == null">卖出</view>
 									<view class="button" @click="qxMai(item.hang.id)" v-else>取消卖出</view>
 								</view>
+								<view class="" v-show="tabCurrentIndex==0">
+									<view class="button" @click="listInfo(item)">展开</view>
+								</view>
 							</view>
 						</view>
 					</view>
@@ -162,7 +174,7 @@ import uEmpty from '@/uview-ui/components/u-empty/u-empty.vue';
 import { mapState, mapMutations } from 'vuex';
 import uniList from '@/components/uni-list/uni-list.vue';
 import uniListItem from '@/components/uni-list-item/uni-list-item.vue';
-import { orderData, getUserInfo, getMyNft, myGdList } from '@/api/user.js';
+import { orderData, getUserInfo, getMyNft,myClass, myGdList } from '@/api/user.js';
 import { loadIndexs } from '@/api/index.js';
 import { saveUrl, interceptor } from '@/utils/loginUtils.js';
 export default {
@@ -397,7 +409,7 @@ export default {
 			}
 			navitem.loadingType = 'loading';
 			if (index == 0) {
-				getMyNft({
+				myClass({
 					page: navitem.page,
 					limit: navitem.limit
 				}).then(res => {
@@ -413,7 +425,6 @@ export default {
 				});
 			}
 			if (index == 1) {
-				console.log('get22');
 				myGdList({
 					page: navitem.page,
 					limit: navitem.limit,
@@ -441,7 +452,26 @@ page,
 	height: 100%;
 	background: #111111;
 }
-
+.hashAddress{
+	line-height: 1;
+	margin: 30rpx;
+	border: 1px solid #7D5FA4;
+	border-radius: 10rpx;
+	color: #7D5FA4;
+	font-size: 20rpx;
+	height: 60rpx;
+	padding: 0 30rpx;
+	.fzButtom{
+		padding: 6rpx 0;
+		padding-left: 20rpx;
+		padding-right: 20rpx;
+		color: #FFFFFF;
+		border-radius: 100rpx;
+		border-top-right-radius: 0rpx;
+		border: 1px solid #7D5FA4;
+		flex-shrink: 0;
+	}
+}
 .mai-box {
 	display: flex;
 	flex-direction: column;

BIN
static/user/back.png


+ 0 - 6
store/index.js

@@ -1,12 +1,6 @@
 import Vue from 'vue'
 import Vuex from 'vuex'
 import user from './model/user'
-//引入axios模块(先下载`axios`--)
-// import axios from 'axios'
-import axios from 'axios'
-//将axios挂载在vue原型链上
-// Vue.prototype.$axios = axios;
-Vue.prototype.$axios = axios
 Vue.use(Vuex)
 
 const store = new Vuex.Store({

+ 36 - 76
uview-ui/README.md

@@ -1,106 +1,66 @@
 <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" style="margin: 30px 0 30px;font-weight: bold;font-size:40px;">uView 2.0</h3>
 <h3 align="center">多平台快速开发的UI框架</h3>
 
+[![stars](https://img.shields.io/github/stars/umicro/uView2.0?style=flat-square&logo=GitHub)](https://github.com/umicro/uView2.0)
+[![forks](https://img.shields.io/github/forks/umicro/uView2.0?style=flat-square&logo=GitHub)](https://github.com/umicro/uView2.0)
+[![issues](https://img.shields.io/github/issues/umicro/uView2.0?style=flat-square&logo=GitHub)](https://github.com/umicro/uView2.0/issues)
+[![Website](https://img.shields.io/badge/uView-up-blue?style=flat-square)](https://uviewui.com)
+[![release](https://img.shields.io/github/v/release/umicro/uView2.0?style=flat-square)](https://gitee.com/umicro/uView2.0/releases)
+[![license](https://img.shields.io/github/license/umicro/uView2.0?style=flat-square)](https://en.wikipedia.org/wiki/MIT_License)
 
 ## 说明
 
-uView UI,是[uni-app](https://uniapp.dcloud.io/)生态优秀的UI框架,全面的组件和便捷的工具会让您信手拈来,如鱼得水
+uView UI,是[uni-app](https://uniapp.dcloud.io/)全面兼容nvue的uni-app生态框架,全面的组件和便捷的工具会让您信手拈来,如鱼得水
 
-## 特性
+## [官方文档:https://uviewui.com](https://uviewui.com)
 
-- 兼容安卓,iOS,微信小程序,H5,QQ小程序,百度小程序,支付宝小程序,头条小程序
-- 60+精选组件,功能丰富,多端兼容,让您快速集成,开箱即用
-- 众多贴心的JS利器,让您飞镖在手,召之即来,百步穿杨
-- 众多的常用页面和布局,让您专注逻辑,事半功倍
-- 详尽的文档支持,现代化的演示效果
-- 按需引入,精简打包体积
 
+## 预览
 
-## 安装
+您可以通过**微信**扫码,查看最佳的演示效果。
+<br>
+<br>
+<img src="https://uviewui.com/common/weixin_mini_qrcode.png" width="220" height="220" >
 
-```bash
-# npm方式安装
-npm i uview-ui
-```
 
-## 快速上手
+## 链接
 
-1. `main.js`引入uView库
-```js
-// main.js
-import uView from 'uview-ui';
-Vue.use(uView);
-```
+- [官方文档](https://www.uviewui.com/)
+- [更新日志](https://www.uviewui.com/components/changelog.html)
+- [升级指南](https://www.uviewui.com/components/changeGuide.html)
+- [关于我们](https://www.uviewui.com/cooperation/about.html)
 
-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";
-```
+欢迎加入我们的QQ群交流反馈:[点此跳转](https://www.uviewui.com/components/addQQGroup.html)
 
-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": [
-		// ......
-	]
-}
-```
+## 关于PR
+
+> 我们非常乐意接受各位的优质PR,但在此之前我希望您了解uView2.0是一个需要兼容多个平台的(小程序、h5、ios app、android app)包括nvue页面、vue页面。
+> 所以希望在您修复bug并提交之前尽可能的去这些平台测试一下兼容性。最好能携带测试截图以方便审核。非常感谢!
+
+## 安装
+
+#### **uni-app插件市场链接** —— [https://ext.dcloud.net.cn/plugin?id=1593](https://ext.dcloud.net.cn/plugin?id=1593)
 
-请通过[快速上手](https://uviewui.com/components/quickstart.html)了解更详细的内容 
+请通过[官网安装文档](https://www.uviewui.com/components/install.html)了解更详细的内容
+
+## 快速上手
+
+请通过[快速上手](https://uviewui.com/components/quickstart.html)了解更详细的内容
 
 ## 使用方法
 配置easycom规则后,自动按需引入,无需`import`组件,直接引用即可。
 
 ```html
 <template>
-	<u-button>按钮</u-button>
+	<u-button text="按钮"></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应用到您的产品中。
+

+ 344 - 0
uview-ui/changelog.md

@@ -0,0 +1,344 @@
+## 2.0.33(2022-06-17)
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 修复`loadmore`组件`lineColor`类型错误问题
+2. 修复`u-parse`组件`imgtap`、`linktap`不生效问题
+## 2.0.32(2022-06-16)
+# uView2.0重磅发布,利剑出鞘,一统江湖
+1. `u-loadmore`新增自定义颜色、虚/实线
+2. 修复`u-swiper-action`组件部分平台不能上下滑动的问题
+3. 修复`u-list`回弹问题
+4. 修复`notice-bar`组件动画在低端安卓机可能会抖动的问题
+5. `u-loading-page`添加控制图标大小的属性`iconSize`
+6. 修复`u-tooltip`组件`color`参数不生效的问题
+7. 修复`u--input`组件使用`blur`事件输出为`undefined`的bug
+8. `u-code-input`组件新增键盘弹起时,是否自动上推页面参数`adjustPosition`
+9. 修复`image`组件`load`事件无回调对象问题
+10. 修复`button`组件`loadingSize`设置无效问题
+10. 其他修复
+## 2.0.31(2022-04-19)
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 修复`upload`在`vue`页面上传成功后没有成功标志的问题
+2. 解决演示项目中微信小程序模拟上传图片一直出于上传中问题
+3. 修复`u-code-input`组件在`nvue`页面编译到`app`平台上光标异常问题(`app`去除此功能)
+4. 修复`actionSheet`组件标题关闭按钮点击事件名称错误的问题
+5. 其他修复
+## 2.0.30(2022-04-04)
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. `u-rate`增加`readonly`属性
+2. `tabs`滑块支持设置背景图片
+3. 修复`u-subsection` `mode`为`subsection`时,滑块样式不正确的问题
+4. `u-code-input`添加光标效果动画
+5. 修复`popup`的`open`事件不触发
+6. 修复`u-flex-column`无效的问题
+7. 修复`u-datetime-picker`索引在特定场合异常问题
+8. 修复`u-datetime-picker`最小时间字符串模板错误问题
+9. `u-swiper`添加`m3u8`验证
+10. `u-swiper`修改判断image和video逻辑
+11. 修复`swiper`无法使用本地图片问题,增加`type`参数
+12. 修复`u-row-notice`格式错误问题
+13. 修复`u-switch`组件当`unit`为`rpx`时,`nodeStyle`消失的问题
+14. 修复`datetime-picker`组件`showToolbar`与`visibleItemCount`属性无效的问题
+15. 修复`upload`组件条件编译位置判断错误,导致`previewImage`属性设置为`false`时,整个组件都会被隐藏的问题
+16. 修复`u-checkbox-group`设置`shape`属性无效的问题
+17. 修复`u-upload`的`capture`传入字符串的时候不生效的问题
+18. 修复`u-action-sheet`组件,关闭事件逻辑错误的问题
+19. 修复`u-list`触顶事件的触发错误的问题
+20. 修复`u-text`只有手机号可拨打的问题
+21. 修复`u-textarea`不能换行的问题
+22. 其他修复
+## 2.0.29(2022-03-13)
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 修复`u--text`组件设置`decoration`属性未生效的问题
+2. 修复`u-datetime-picker`使用`formatter`后返回值不正确
+3. 修复`u-datetime-picker` `intercept` 可能为undefined
+4. 修复已设置单位 uni..config.unit = 'rpx'时,线型指示器 `transform` 的位置翻倍,导致指示器超出宽度
+5. 修复mixin中bem方法生成的类名在支付宝和字节小程序中失效
+6. 修复默认值传值为空的时候,打开`u-datetime-picker`报错,不能选中第一列时间的bug
+7. 修复`u-datetime-picker`使用`formatter`后返回值不正确
+8. 修复`u-image`组件`loading`无效果的问题
+9. 修复`config.unit`属性设为`rpx`时,导航栏占用高度不足导致塌陷的问题
+10. 修复`u-datetime-picker`组件`itemHeight`无效问题
+11. 其他修复
+## 2.0.28(2022-02-22)
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. search组件新增searchIconSize属性
+2. 兼容Safari/Webkit中传入时间格式如2022-02-17 12:00:56
+3. 修复text value.js 判断日期出format错误问题
+4. priceFormat格式化金额出现精度错误
+5. priceFormat在部分情况下出现精度损失问题
+6. 优化表单rules提示
+7. 修复avatar组件src为空时,展示状态不对
+8. 其他修复
+## 2.0.27(2022-01-28)
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1.样式修复
+## 2.0.26(2022-01-28)
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1.样式修复
+## 2.0.25(2022-01-27)
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 修复text组件mode=price时,可能会导致精度错误的问题
+2. 添加$u.setConfig()方法,可设置uView内置的config, props, zIndex, color属性,详见:[修改uView内置配置方案](https://uviewui.com/components/setting.html#%E9%BB%98%E8%AE%A4%E5%8D%95%E4%BD%8D%E9%85%8D%E7%BD%AE)
+3. 优化form组件在errorType=toast时,如果输入错误页面会有抖动的问题
+4. 修复$u.addUnit()对配置默认单位可能无效的问题
+## 2.0.24(2022-01-25)
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 修复swiper在current指定非0时缩放有误
+2. 修复u-icon添加stop属性的时候报错
+3. 优化遗留的通过正则判断rpx单位的问题
+4. 优化Layout布局 vue使用gutter时,会超出固定区域
+5. 优化search组件高度单位问题(rpx -> px)
+6. 修复u-image slot 加载和错误的图片失去了高度
+7. 修复u-index-list中footer插槽与header插槽存在性判断错误
+8. 修复部分机型下u-popup关闭时会闪烁
+9. 修复u-image在nvue-app下失去宽高
+10. 修复u-popup运行报错
+11. 修复u-tooltip报错
+12. 修复box-sizing在app下的警告
+13. 修复u-navbar在小程序中报运行时错误
+14. 其他修复
+## 2.0.23(2022-01-24)
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 修复image组件在hx3.3.9的nvue下可能会显示异常的问题
+2. 修复col组件gutter参数带rpx单位处理不正确的问题
+3. 修复text组件单行时无法显示省略号的问题
+4. navbar添加titleStyle参数
+5. 升级到hx3.3.9可消除nvue下控制台样式警告的问题
+## 2.0.22(2022-01-19)
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. $u.page()方法优化,避免在特殊场景可能报错的问题
+2. picker组件添加immediateChange参数
+3. 新增$u.pages()方法
+## 2.0.21(2022-01-19)
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 优化:form组件在用户设置rules的时候提示用户model必传
+2. 优化遗留的通过正则判断rpx单位的问题
+3. 修复微信小程序环境中tabbar组件开启safeAreaInsetBottom属性后,placeholder高度填充不正确
+4. 修复swiper在current指定非0时缩放有误
+5. 修复u-icon添加stop属性的时候报错
+6. 修复upload组件在accept=all的时候没有作用
+7. 修复在text组件mode为phone时call属性无效的问题
+8. 处理u-form clearValidate方法
+9. 其他修复
+## 2.0.20(2022-01-14)
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 修复calendar默认会选择一个日期,如果直接点确定的话,无法取到值的问题
+2. 修复Slider缺少disabled props 还有注释
+3. 修复u-notice-bar点击事件无法拿到index索引值的问题
+4. 修复u-collapse-item在vue文件下,app端自定义插槽不生效的问题
+5. 优化头像为空时显示默认头像 
+6. 修复图片地址赋值后判断加载状态为完成问题
+7. 修复日历滚动到默认日期月份区域
+8. search组件暴露点击左边icon事件
+9. 修复u-form clearValidate方法不生效
+10. upload h5端增加返回文件参数(文件的name参数)
+11. 处理upload选择文件后url为blob类型无法预览的问题
+12. u-code-input 修复输入框没有往左移出一半屏幕
+13. 修复Upload上传 disabled为true时,控制台报hoverClass类型错误
+14. 临时处理ios app下grid点击坍塌问题
+15. 其他修复
+## 2.0.19(2021-12-29)
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 优化微信小程序包体积可在微信中预览,请升级HbuilderX3.3.4,同时在“运行->运行到小程序模拟器”中勾选“运行时是否压缩代码”
+2. 优化微信小程序setData性能,处理某些方法如$u.route()无法在模板中使用的问题
+3. navbar添加autoBack参数
+4. 允许avatar组件的事件冒泡
+5. 修复cell组件报错问题
+6. 其他修复
+## 2.0.18(2021-12-28)
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 修复app端编译报错问题
+2. 重新处理微信小程序端setData过大的性能问题
+3. 修复边框问题
+4. 修复最大最小月份不大于0则没有数据出现的问题
+5. 修复SwipeAction微信小程序端无法上下滑动问题
+6. 修复input的placeholder在小程序端默认显示为true问题
+7. 修复divider组件click事件无效问题
+8. 修复u-code-input maxlength 属性值为 String 类型时显示异常
+9. 修复当 grid只有 1到2时 在小程序端algin设置无效的问题
+10. 处理form-item的label为top时,取消错误提示的左边距
+11. 其他修复
+## 2.0.17(2021-12-26)
+## uView正在参与开源中国的“年度最佳项目”评选,之前投过票的现在也可以投票,恳请同学们投一票,[点此帮助uView](https://www.oschina.net/project/top_cn_2021/?id=583)
+
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 解决HBuilderX3.3.3.20211225版本导致的样式问题
+2. calendar日历添加monthNum参数
+3. navbar添加center slot
+## 2.0.16(2021-12-25)
+## uView正在参与开源中国的“年度最佳项目”评选,之前投过票的现在也可以投票,恳请同学们投一票,[点此帮助uView](https://www.oschina.net/project/top_cn_2021/?id=583)
+
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 解决微信小程序setData性能问题
+2. 修复count-down组件change事件不触发问题
+## 2.0.15(2021-12-21)
+## uView正在参与开源中国的“年度最佳项目”评选,之前投过票的现在也可以投票,恳请同学们投一票,[点此帮助uView](https://www.oschina.net/project/top_cn_2021/?id=583)
+
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 修复Cell单元格titleWidth无效
+2. 修复cheakbox组件ischecked不更新
+3. 修复keyboard是否显示"."按键默认值问题
+4. 修复number-keyboard是否显示键盘的"."符号问题
+5. 修复Input输入框 readonly无效
+6. 修复u-avatar 导致打包app、H5时候报错问题
+7. 修复Upload上传deletable无效
+8. 修复upload当设置maxSize时无效的问题
+9. 修复tabs lineWidth传入带单位的字符串的时候偏移量计算错误问题
+10. 修复rate组件在有padding的view内,显示的星星位置和可触摸区域不匹配,无法正常选中星星
+## 2.0.13(2021-12-14)
+## [点击加群交流反馈:364463526](https://jq.qq.com/?_chanwv=1027&k=mCxS3TGY)
+
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 修复配置默认单位为rpx可能会导致自定义导航栏高度异常的问题
+## 2.0.12(2021-12-14)
+## [点击加群交流反馈:364463526](https://jq.qq.com/?_chanwv=1027&k=mCxS3TGY)
+
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 修复tabs组件在vue环境下划线消失的问题
+2. 修复upload组件在安卓小程序无法选择视频的问题
+3. 添加uni.$u.config.unit配置,用于配置参数默认单位,详见:[默认单位配置](https://www.uviewui.com/components/setting.html#%E9%BB%98%E8%AE%A4%E5%8D%95%E4%BD%8D%E9%85%8D%E7%BD%AE)
+4. 修复textarea组件在没绑定v-model时,字符统计不生效问题
+5. 修复nvue下控制是否出现滚动条失效问题
+## 2.0.11(2021-12-13)
+## [点击加群交流反馈:364463526](https://jq.qq.com/?_chanwv=1027&k=mCxS3TGY)
+
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. text组件align参数无效的问题
+2. subsection组件添加keyName参数
+3. upload组件无法判断[Object file]类型的问题
+4. 处理notify层级过低问题
+5. codeInput组件添加disabledDot参数
+6. 处理actionSheet组件round参数无效的问题
+7. calendar组件添加round参数用于控制圆角值
+8. 处理swipeAction组件在vue环境下默认被打开的问题
+9. button组件的throttleTime节流参数无效的问题
+10. 解决u-notify手动关闭方法close()无效的问题
+11. input组件readonly不生效问题
+12. tag组件type参数为info不生效问题
+## 2.0.10(2021-12-08)
+## [点击加群交流反馈:364463526](https://jq.qq.com/?_chanwv=1027&k=mCxS3TGY)
+
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 修复button sendMessagePath属性不生效
+2. 修复DatetimePicker选择器title无效
+3. 修复u-toast设置loading=true不生效
+4. 修复u-text金额模式传0报错
+5. 修复u-toast组件的icon属性配置不生效
+6. button的icon在特殊场景下的颜色优化
+7. IndexList优化,增加#
+## 2.0.9(2021-12-01)
+## [点击加群交流反馈:232041042](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
+
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 优化swiper的height支持100%值(仅vue有效),修复嵌入视频时click事件无法触发的问题
+2. 优化tabs组件对list值为空的判断,或者动态变化list时重新计算相关尺寸的问题
+3. 优化datetime-picker组件逻辑,让其后续打开的默认值为上一次的选中值,需要通过v-model绑定值才有效
+4. 修复upload内嵌在其他组件中,选择图片可能不会换行的问题
+## 2.0.8(2021-12-01)
+## [点击加群交流反馈:232041042](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
+
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 修复toast的position参数无效问题
+2. 处理input在ios nvue上无法获得焦点的问题
+3. avatar-group组件添加extraValue参数,让剩余展示数量可手动控制
+4. tabs组件添加keyName参数用于配置从对象中读取的键名
+5. 处理text组件名字脱敏默认配置无效的问题
+6. 处理picker组件item文本太长换行问题
+## 2.0.7(2021-11-30)
+## [点击加群交流反馈:232041042](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
+
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 修复radio和checkbox动态改变v-model无效的问题。
+2. 优化form规则validator在微信小程序用法
+3. 修复backtop组件mode参数在微信小程序无效的问题
+4. 处理Album的previewFullImage属性无效的问题
+5. 处理u-datetime-picker组件mode='time'在选择改变时间时,控制台报错的问题
+## 2.0.6(2021-11-27)
+## [点击加群交流反馈:232041042](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
+
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 处理tag组件在vue下边框无效的问题。
+2. 处理popup组件圆角参数可能无效的问题。
+3. 处理tabs组件lineColor参数可能无效的问题。
+4. propgress组件在值很小时,显示异常的问题。
+## 2.0.5(2021-11-25)
+## [点击加群交流反馈:232041042](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
+
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. calendar在vue下显示异常问题。 
+2. form组件labelPosition和errorType参数无效的问题
+3. input组件inputAlign无效的问题
+4. 其他一些修复
+## 2.0.4(2021-11-23)
+## [点击加群交流反馈:232041042](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
+
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+0. input组件缺失@confirm事件,以及subfix和prefix无效问题
+1. component.scss文件样式在vue下干扰全局布局问题
+2. 修复subsection在vue环境下表现异常的问题
+3. tag组件的bgColor等参数无效的问题
+4. upload组件不换行的问题
+5. 其他的一些修复处理
+## 2.0.3(2021-11-16)
+## [点击加群交流反馈:1129077272](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
+
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. uView2.0已实现全面兼容nvue
+2. uView2.0对1.x进行了架构重构,细节和性能都有极大提升
+3. 目前uView2.0为公测阶段,相关细节可能会有变动
+4. 我们写了一份与1.x的对比指南,详见[对比1.x](https://www.uviewui.com/components/diff1.x.html)
+5. 处理modal的confirm回调事件拼写错误问题
+6. 处理input组件@input事件参数错误问题
+7. 其他一些修复
+## 2.0.2(2021-11-16)
+## [点击加群交流反馈:1129077272](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
+
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. uView2.0已实现全面兼容nvue
+2. uView2.0对1.x进行了架构重构,细节和性能都有极大提升
+3. 目前uView2.0为公测阶段,相关细节可能会有变动
+4. 我们写了一份与1.x的对比指南,详见[对比1.x](https://www.uviewui.com/components/diff1.x.html)
+5. 修复input组件formatter参数缺失问题
+6. 优化loading-icon组件的scss写法问题,防止不兼容新版本scss
+## 2.0.0(2020-11-15)
+## [点击加群交流反馈:1129077272](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
+
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. uView2.0已实现全面兼容nvue
+2. uView2.0对1.x进行了架构重构,细节和性能都有极大提升
+3. 目前uView2.0为公测阶段,相关细节可能会有变动
+4. 我们写了一份与1.x的对比指南,详见[对比1.x](https://www.uviewui.com/components/diff1.x.html)
+5. 修复input组件formatter参数缺失问题
+
+

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

@@ -0,0 +1,78 @@
+<template>
+	<uvForm
+		ref="uForm"
+		:model="model"
+		:rules="rules"
+		:errorType="errorType"
+		:borderBottom="borderBottom"
+		:labelPosition="labelPosition"
+		:labelWidth="labelWidth"
+		:labelAlign="labelAlign"
+		:labelStyle="labelStyle"
+		:customStyle="customStyle"
+	>
+		<slot />
+	</uvForm>
+</template>
+
+<script>
+	/**
+	 * 此组件存在的理由是,在nvue下,u-form被uni-app官方占用了,u-form在nvue中相当于form组件
+	 * 所以在nvue下,取名为u--form,内部其实还是u-form.vue,只不过做一层中转
+	 */
+	import uvForm from '../u-form/u-form.vue';
+	import props from '../u-form/props.js'
+	export default {
+		// #ifdef MP-WEIXIN
+		name: 'u-form',
+		// #endif
+		// #ifndef MP-WEIXIN
+		name: 'u--form',
+		// #endif
+		mixins: [uni.$u.mpMixin, props, uni.$u.mixin],
+		components: {
+			uvForm
+		},
+		created() {
+			this.children = []
+		},
+		methods: {
+			// 手动设置校验的规则,如果规则中有函数的话,微信小程序中会过滤掉,所以只能手动调用设置规则
+			setRules(rules) {
+				this.$refs.uForm.setRules(rules)
+			},
+			validate() {
+				/**
+				 * 在微信小程序中,通过this.$parent拿到的父组件是u--form,而不是其内嵌的u-form
+				 * 导致在u-form组件中,拿不到对应的children数组,从而校验无效,所以这里每次调用u-form组件中的
+				 * 对应方法的时候,在小程序中都先将u--form的children赋值给u-form中的children
+				 */
+				// #ifdef MP-WEIXIN
+				this.setMpData()
+				// #endif
+				return this.$refs.uForm.validate()
+			},
+			validateField(value, callback) {
+				// #ifdef MP-WEIXIN
+				this.setMpData()
+				// #endif
+				return this.$refs.uForm.validateField(value, callback)
+			},
+			resetFields() {
+				// #ifdef MP-WEIXIN
+				this.setMpData()
+				// #endif
+				return this.$refs.uForm.resetFields()
+			},
+			clearValidate(props) {
+				// #ifdef MP-WEIXIN
+				this.setMpData()
+				// #endif
+				return this.$refs.uForm.clearValidate(props)
+			},
+			setMpData() {
+				this.$refs.uForm.children = this.children
+			}
+		},
+	}
+</script>

+ 47 - 0
uview-ui/components/u--image/u--image.vue

@@ -0,0 +1,47 @@
+<template>
+	<uvImage 
+		:src="src"
+		:mode="mode"
+		:width="width"
+		:height="height"
+		:shape="shape"
+		:radius="radius"
+		:lazyLoad="lazyLoad"
+		:showMenuByLongpress="showMenuByLongpress"
+		:loadingIcon="loadingIcon"
+		:errorIcon="errorIcon"
+		:showLoading="showLoading"
+		:showError="showError"
+		:fade="fade"
+		:webp="webp"
+		:duration="duration"
+		:bgColor="bgColor"
+		:customStyle="customStyle"
+		@click="$emit('click')"
+		@error="$emit('error')"
+		@load="$emit('load')"
+	>
+		<template v-slot:loading>
+			<slot name="loading"></slot>
+		</template>
+		<template v-slot:error>
+			<slot name="error"></slot>
+		</template>
+	</uvImage>
+</template>
+
+<script>
+	/**
+	 * 此组件存在的理由是,在nvue下,u-image被uni-app官方占用了,u-image在nvue中相当于image组件
+	 * 所以在nvue下,取名为u--image,内部其实还是u-iamge.vue,只不过做一层中转
+	 */
+	import uvImage from '../u-image/u-image.vue';
+	import props from '../u-image/props.js';
+	export default {
+		name: 'u--image',
+		mixins: [uni.$u.mpMixin, props, uni.$u.mixin],
+		components: {
+			uvImage
+		},
+	}
+</script>

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

@@ -0,0 +1,72 @@
+<template>
+	<uvInput 
+		:value="value"
+		:type="type"
+		:fixed="fixed"
+		:disabled="disabled"
+		:disabledColor="disabledColor"
+		:clearable="clearable"
+		:password="password"
+		:maxlength="maxlength"
+		:placeholder="placeholder"
+		:placeholderClass="placeholderClass"
+		:placeholderStyle="placeholderStyle"
+		:showWordLimit="showWordLimit"
+		:confirmType="confirmType"
+		:confirmHold="confirmHold"
+		:holdKeyboard="holdKeyboard"
+		:focus="focus"
+		:autoBlur="autoBlur"
+		:disableDefaultPadding="disableDefaultPadding"
+		:cursor="cursor"
+		:cursorSpacing="cursorSpacing"
+		:selectionStart="selectionStart"
+		:selectionEnd="selectionEnd"
+		:adjustPosition="adjustPosition"
+		:inputAlign="inputAlign"
+		:fontSize="fontSize"
+		:color="color"
+		:prefixIcon="prefixIcon"
+		:suffixIcon="suffixIcon"
+		:suffixIconStyle="suffixIconStyle"
+		:prefixIconStyle="prefixIconStyle"
+		:border="border"
+		:readonly="readonly"
+		:shape="shape"
+		:customStyle="customStyle"
+		:formatter="formatter"
+		@focus="$emit('focus')"
+		@blur="e => $emit('blur', e)"
+		@keyboardheightchange="$emit('keyboardheightchange')"
+		@change="e => $emit('change', e)"
+		@input="e => $emit('input', e)"
+		@confirm="e => $emit('confirm', e)"
+		@clear="$emit('clear')"
+		@click="$emit('click')"
+	>
+		<!-- #ifdef MP -->
+		<slot name="prefix"></slot>
+		<slot name="suffix"></slot>
+		<!-- #endif -->
+		<!-- #ifndef MP -->
+		<slot name="prefix" slot="prefix"></slot>
+		<slot name="suffix" slot="suffix"></slot>
+		<!-- #endif -->
+	</uvInput>
+</template>
+
+<script>
+	/**
+	 * 此组件存在的理由是,在nvue下,u-input被uni-app官方占用了,u-input在nvue中相当于input组件
+	 * 所以在nvue下,取名为u--input,内部其实还是u-input.vue,只不过做一层中转
+	 */
+	import uvInput from '../u-input/u-input.vue';
+	import props from '../u-input/props.js'
+	export default {
+		name: 'u--input',
+		mixins: [uni.$u.mpMixin, props, uni.$u.mixin],
+		components: {
+			uvInput
+		},
+	}
+</script>

+ 44 - 0
uview-ui/components/u--text/u--text.vue

@@ -0,0 +1,44 @@
+<template>
+    <uvText
+        :type="type"
+        :show="show"
+        :text="text"
+        :prefixIcon="prefixIcon"
+        :suffixIcon="suffixIcon"
+        :mode="mode"
+        :href="href"
+        :format="format"
+        :call="call"
+        :openType="openType"
+        :bold="bold"
+        :block="block"
+        :lines="lines"
+        :color="color"
+		:decoration="decoration"
+        :size="size"
+        :iconStyle="iconStyle"
+        :margin="margin"
+        :lineHeight="lineHeight"
+        :align="align"
+        :wordWrap="wordWrap"
+        :customStyle="customStyle"
+        @click="$emit('click')"
+    ></uvText>
+</template>
+
+<script>
+/**
+ * 此组件存在的理由是,在nvue下,u-text被uni-app官方占用了,u-text在nvue中相当于input组件
+ * 所以在nvue下,取名为u--input,内部其实还是u-text.vue,只不过做一层中转
+ * 不使用v-bind="$attrs",而是分开独立写传参,是因为微信小程序不支持此写法
+ */
+import uvText from "../u-text/u-text.vue";
+import props from "../u-text/props.js";
+export default {
+    name: "u--text",
+    mixins: [uni.$u.mpMixin, props, uni.$u.mixin],
+    components: {
+        uvText,
+    },
+};
+</script>

+ 47 - 0
uview-ui/components/u--textarea/u--textarea.vue

@@ -0,0 +1,47 @@
+<template>
+	<uvTextarea
+		:value="value"
+		:placeholder="placeholder"
+		:height="height"
+		:confirmType="confirmType"
+		:disabled="disabled"
+		:count="count"
+		:focus="focus"
+		:autoHeight="autoHeight"
+		:fixed="fixed"
+		:cursorSpacing="cursorSpacing"
+		:cursor="cursor"
+		:showConfirmBar="showConfirmBar"
+		:selectionStart="selectionStart"
+		:selectionEnd="selectionEnd"
+		:adjustPosition="adjustPosition"
+		:disableDefaultPadding="disableDefaultPadding"
+		:holdKeyboard="holdKeyboard"
+		:maxlength="maxlength"
+		:border="border"
+		:customStyle="customStyle"
+		:formatter="formatter"
+		@focus="e => $emit('focus')"
+		@blur="e => $emit('blur')"
+		@linechange="e => $emit('linechange', e)"
+		@confirm="e => $emit('confirm')"
+		@input="e => $emit('input', e)"
+		@keyboardheightchange="e => $emit('keyboardheightchange')"
+	></uvTextarea>
+</template>
+
+<script>
+	/**
+	 * 此组件存在的理由是,在nvue下,u--textarea被uni-app官方占用了,u-textarea在nvue中相当于textarea组件
+	 * 所以在nvue下,取名为u--textarea,内部其实还是u-textarea.vue,只不过做一层中转
+	 */
+	import uvTextarea from '../u-textarea/u-textarea.vue';
+	import props from '../u-textarea/props.js'
+	export default {
+		name: 'u--textarea',
+		mixins: [uni.$u.mpMixin, props, uni.$u.mixin],
+		components: {
+			uvTextarea
+		},
+	}
+</script>

+ 54 - 0
uview-ui/components/u-action-sheet/props.js

@@ -0,0 +1,54 @@
+export default {
+    props: {
+        // 操作菜单是否展示 (默认false)
+        show: {
+            type: Boolean,
+            default: uni.$u.props.actionSheet.show
+        },
+        // 标题
+        title: {
+            type: String,
+            default: uni.$u.props.actionSheet.title
+        },
+        // 选项上方的描述信息
+        description: {
+            type: String,
+            default: uni.$u.props.actionSheet.description
+        },
+        // 数据
+        actions: {
+            type: Array,
+            default: uni.$u.props.actionSheet.actions
+        },
+        // 取消按钮的文字,不为空时显示按钮
+        cancelText: {
+            type: String,
+            default: uni.$u.props.actionSheet.cancelText
+        },
+        // 点击某个菜单项时是否关闭弹窗
+        closeOnClickAction: {
+            type: Boolean,
+            default: uni.$u.props.actionSheet.closeOnClickAction
+        },
+        // 处理底部安全区(默认true)
+        safeAreaInsetBottom: {
+            type: Boolean,
+            default: uni.$u.props.actionSheet.safeAreaInsetBottom
+        },
+        // 小程序的打开方式
+        openType: {
+            type: String,
+            default: uni.$u.props.actionSheet.openType
+        },
+        // 点击遮罩是否允许关闭 (默认true)
+        closeOnClickOverlay: {
+            type: Boolean,
+            default: uni.$u.props.actionSheet.closeOnClickOverlay
+        },
+        // 圆角值
+        round: {
+            type: [Boolean, String, Number],
+            default: uni.$u.props.actionSheet.round
+        }
+    }
+}

+ 236 - 148
uview-ui/components/u-action-sheet/u-action-sheet.vue

@@ -1,190 +1,278 @@
+
 <template>
-	<u-popup mode="bottom" :border-radius="borderRadius" :popup="false" v-model="value" :maskCloseAble="maskCloseAble"
-	    length="auto" :safeAreaInsetBottom="safeAreaInsetBottom" @close="popupClose" :z-index="uZIndex">
-		<view class="u-tips u-border-bottom" v-if="tips.text" :style="[tipsStyle]">
-			{{tips.text}}
-		</view>
-		<block v-for="(item, index) in list" :key="index">
-			<view 
-				@touchmove.stop.prevent 
-				@tap="itemClick(index)" 
-				:style="[itemStyle(index)]" 
-				class="u-action-sheet-item u-line-1" 
-				:class="[index < list.length - 1 ? 'u-border-bottom' : '']"
-				:hover-stay-time="150"
+	<u-popup
+	    :show="show"
+	    mode="bottom"
+	    @close="closeHandler"
+	    :safeAreaInsetBottom="safeAreaInsetBottom"
+	    :round="round"
+	>
+		<view class="u-action-sheet">
+			<view
+			    class="u-action-sheet__header"
+			    v-if="title"
 			>
-				<text>{{item.text}}</text>
-				<text class="u-action-sheet-item__subtext u-line-1" v-if="item.subText">{{item.subText}}</text>
+				<text class="u-action-sheet__header__title u-line-1">{{title}}</text>
+				<view
+				    class="u-action-sheet__header__icon-wrap"
+				    @tap.stop="cancel"
+				>
+					<u-icon
+					    name="close"
+					    size="17"
+					    color="#c8c9cc"
+					    bold
+					></u-icon>
+				</view>
+			</view>
+			<text
+			    class="u-action-sheet__description"
+				:style="[{
+					marginTop: `${title && description ? 0 : '18px'}`
+				}]"
+			    v-if="description"
+			>{{description}}</text>
+			<slot>
+				<u-line v-if="description"></u-line>
+				<view class="u-action-sheet__item-wrap">
+					<template v-for="(item, index) in actions">
+						<!-- #ifdef MP -->
+						<button
+						    :key="index"
+						    class="u-reset-button"
+						    :openType="item.openType"
+						    @getuserinfo="onGetUserInfo"
+						    @contact="onContact"
+						    @getphonenumber="onGetPhoneNumber"
+						    @error="onError"
+						    @launchapp="onLaunchApp"
+						    @opensetting="onOpenSetting"
+						    :lang="lang"
+						    :session-from="sessionFrom"
+						    :send-message-title="sendMessageTitle"
+						    :send-message-path="sendMessagePath"
+						    :send-message-img="sendMessageImg"
+						    :show-message-card="showMessageCard"
+						    :app-parameter="appParameter"
+						    @tap="selectHandler(index)"
+						    :hover-class="!item.disabled && !item.loading ? 'u-action-sheet--hover' : ''"
+						>
+							<!-- #endif -->
+							<view
+							    class="u-action-sheet__item-wrap__item"
+							    @tap.stop="selectHandler(index)"
+							    :hover-class="!item.disabled && !item.loading ? 'u-action-sheet--hover' : ''"
+							    :hover-stay-time="150"
+							>
+								<template v-if="!item.loading">
+									<text
+									    class="u-action-sheet__item-wrap__item__name"
+									    :style="[itemStyle(index)]"
+									>{{ item.name }}</text>
+									<text
+									    v-if="item.subname"
+									    class="u-action-sheet__item-wrap__item__subname"
+									>{{ item.subname }}</text>
+								</template>
+								<u-loading-icon
+								    v-else
+								    custom-class="van-action-sheet__loading"
+								    size="18"
+								    mode="circle"
+								/>
+							</view>
+							<!-- #ifdef MP -->
+						</button>
+						<!-- #endif -->
+						<u-line v-if="index !== actions.length - 1"></u-line>
+					</template>
+				</view>
+			</slot>
+			<u-gap
+			    bgColor="#eaeaec"
+			    height="6"
+			    v-if="cancelText"
+			></u-gap>
+			<view hover-class="u-action-sheet--hover">
+				<text
+				    @touchmove.stop.prevent
+				    :hover-stay-time="150"
+				    v-if="cancelText"
+				    class="u-action-sheet__cancel-text"
+				    @tap="cancel"
+				>{{cancelText}}</text>
 			</view>
-		</block>
-		<view class="u-gab" v-if="cancelBtn">
 		</view>
-		<view @touchmove.stop.prevent class="u-actionsheet-cancel u-action-sheet-item" hover-class="u-hover-class"
-		    :hover-stay-time="150" v-if="cancelBtn" @tap="close">{{cancelText}}</view>
 	</u-popup>
 </template>
 
 <script>
+	import openType from '../../libs/mixin/openType'
+	import button from '../../libs/mixin/button'
+	import props from './props.js';
 	/**
-	 * actionSheet 操作菜单
+	 * ActionSheet 操作菜单
 	 * @description 本组件用于从底部弹出一个操作菜单,供用户选择并返回结果。本组件功能类似于uni的uni.showActionSheetAPI,配置更加灵活,所有平台都表现一致。
 	 * @tutorial https://www.uviewui.com/components/actionSheet.html
-	 * @property {Array<Object>} list 按钮的文字数组,见官方文档示例
-	 * @property {Object} tips 顶部的提示文字,见官方文档示例
-	 * @property {String} cancel-text 取消按钮的提示文字
-	 * @property {Boolean} cancel-btn 是否显示底部的取消按钮(默认true)
-	 * @property {Number String} border-radius 弹出部分顶部左右的圆角值,单位rpx(默认0)
-	 * @property {Boolean} mask-close-able 点击遮罩是否可以关闭(默认true)
-	 * @property {Boolean} safe-area-inset-bottom 是否开启底部安全区适配(默认false)
-	 * @property {Number String} z-index z-index值(默认1075)
-	 * @property {String} cancel-text 取消按钮的提示文字
-	 * @event {Function} click 点击ActionSheet列表项时触发
-	 * @event {Function} close 点击取消按钮时触发
-	 * @example <u-action-sheet :list="list" @click="click" v-model="show"></u-action-sheet>
+	 * 
+	 * @property {Boolean}			show				操作菜单是否展示 (默认 false )
+	 * @property {String}			title				操作菜单标题
+	 * @property {String}			description			选项上方的描述信息
+	 * @property {Array<Object>}	actions				按钮的文字数组,见官方文档示例
+	 * @property {String}			cancelText			取消按钮的提示文字,不为空时显示按钮
+	 * @property {Boolean}			closeOnClickAction	点击某个菜单项时是否关闭弹窗 (默认 true )
+	 * @property {Boolean}			safeAreaInsetBottom	处理底部安全区 (默认 true )
+	 * @property {String}			openType			小程序的打开方式 (contact | launchApp | getUserInfo | openSetting |getPhoneNumber |error )
+	 * @property {Boolean}			closeOnClickOverlay	点击遮罩是否允许关闭  (默认 true )
+	 * @property {Number|String}	round				圆角值,默认无圆角  (默认 0 )
+	 * @property {String}			lang				指定返回用户信息的语言,zh_CN 简体中文,zh_TW 繁体中文,en 英文
+	 * @property {String}			sessionFrom			会话来源,openType="contact"时有效
+	 * @property {String}			sendMessageTitle	会话内消息卡片标题,openType="contact"时有效
+	 * @property {String}			sendMessagePath		会话内消息卡片点击跳转小程序路径,openType="contact"时有效
+	 * @property {String}			sendMessageImg		会话内消息卡片图片,openType="contact"时有效
+	 * @property {Boolean}			showMessageCard		是否显示会话内消息卡片,设置此参数为 true,用户进入客服会话会在右下角显示"可能要发送的小程序"提示,用户点击后可以快速发送小程序消息,openType="contact"时有效 (默认 false )
+	 * @property {String}			appParameter		打开 APP 时,向 APP 传递的参数,openType=launchApp 时有效
+	 * 
+	 * @event {Function} select			点击ActionSheet列表项时触发 
+	 * @event {Function} close			点击取消按钮时触发
+	 * @event {Function} getuserinfo	用户点击该按钮时,会返回获取到的用户信息,回调的 detail 数据与 wx.getUserInfo 返回的一致,openType="getUserInfo"时有效
+	 * @event {Function} contact		客服消息回调,openType="contact"时有效
+	 * @event {Function} getphonenumber	获取用户手机号回调,openType="getPhoneNumber"时有效
+	 * @event {Function} error			当使用开放能力时,发生错误的回调,openType="error"时有效
+	 * @event {Function} launchapp		打开 APP 成功的回调,openType="launchApp"时有效
+	 * @event {Function} opensetting	在打开授权设置页后回调,openType="openSetting"时有效
+	 * @example <u-action-sheet :actions="list" :title="title" :show="show"></u-action-sheet>
 	 */
 	export default {
 		name: "u-action-sheet",
-		props: {
-			// 点击遮罩是否可以关闭actionsheet
-			maskCloseAble: {
-				type: Boolean,
-				default: true
-			},
-			// 按钮的文字数组,可以自定义颜色和字体大小,字体单位为rpx
-			list: {
-				type: Array,
-				default () {
-					// 如下
-					// return [{
-					// 	text: '确定',
-					// 	color: '',
-					// 	fontSize: ''
-					// }]
-					return [];
-				}
-			},
-			// 顶部的提示文字
-			tips: {
-				type: Object,
-				default () {
-					return {
-						text: '',
-						color: '',
-						fontSize: '26'
-					}
-				}
-			},
-			// 底部的取消按钮
-			cancelBtn: {
-				type: Boolean,
-				default: true
-			},
-			// 是否开启底部安全区适配,开启的话,会在iPhoneX机型底部添加一定的内边距
-			safeAreaInsetBottom: {
-				type: Boolean,
-				default: false
-			},
-			// 通过双向绑定控制组件的弹出与收起
-			value: {
-				type: Boolean,
-				default: false
-			},
-			// 弹出的顶部圆角值
-			borderRadius: {
-				type: [String, Number],
-				default: 0
-			},
-			// 弹出的z-index值
-			zIndex: {
-				type: [String, Number],
-				default: 0
-			},
-			// 取消按钮的文字提示
-			cancelText: {
-				type: String,
-				default: '取消'
+		// 一些props参数和methods方法,通过mixin混入,因为其他文件也会用到
+		mixins: [openType, button, uni.$u.mixin, props],
+		data() {
+			return {
+
 			}
 		},
 		computed: {
-			// 顶部提示的样式
-			tipsStyle() {
-				let style = {};
-				if (this.tips.color) style.color = this.tips.color;
-				if (this.tips.fontSize) style.fontSize = this.tips.fontSize + 'rpx';
-				return style;
-			},
 			// 操作项目的样式
 			itemStyle() {
 				return (index) => {
 					let style = {};
-					if (this.list[index].color) style.color = this.list[index].color;
-					if (this.list[index].fontSize) style.fontSize = this.list[index].fontSize + 'rpx';
+					if (this.actions[index].color) style.color = this.actions[index].color
+					if (this.actions[index].fontSize) style.fontSize = uni.$u.addUnit(this.actions[index].fontSize)
 					// 选项被禁用的样式
-					if (this.list[index].disabled) style.color = '#c0c4cc';
+					if (this.actions[index].disabled) style.color = '#c0c4cc'
 					return style;
 				}
 			},
-			uZIndex() {
-				// 如果用户有传递z-index值,优先使用
-				return this.zIndex ? this.zIndex : this.$u.zIndex.popup;
-			}
 		},
 		methods: {
+			closeHandler() {
+				// 允许点击遮罩关闭时,才发出close事件
+				if(this.closeOnClickOverlay) {
+					this.$emit('close')
+				}
+			},
 			// 点击取消按钮
-			close() {
-				// 发送input事件,并不会作用于父组件,而是要设置组件内部通过props传递的value参数
-				// 这是一个vue发送事件的特殊用法
-				this.popupClose();
-				this.$emit('close');
+			cancel() {
+				this.$emit('close')
 			},
-			// 弹窗关闭
-			popupClose() {
-				this.$emit('input', false);
+			selectHandler(index) {
+				const item = this.actions[index]
+				if (item && !item.disabled && !item.loading) {
+					this.$emit('select', item)
+					if (this.closeOnClickAction) {
+						this.$emit('close')
+					}
+				}
 			},
-			// 点击某一个item
-			itemClick(index) {
-				// disabled的项禁止点击
-				if(this.list[index].disabled) return;
-				this.$emit('click', index);
-				this.$emit('input', false);
-			}
 		}
 	}
 </script>
 
 <style lang="scss" scoped>
-	@import "../../libs/css/style.components.scss";
+	@import "../../libs/css/components.scss";
+	$u-action-sheet-reset-button-width:100% !default;
+	$u-action-sheet-title-font-size: 16px !default;
+	$u-action-sheet-title-padding: 12px 30px !default;
+	$u-action-sheet-title-color: $u-main-color !default;
+	$u-action-sheet-header-icon-wrap-right:15px !default;
+	$u-action-sheet-header-icon-wrap-top:15px !default;
+	$u-action-sheet-description-font-size:13px !default;
+	$u-action-sheet-description-color:14px !default;
+	$u-action-sheet-description-margin: 18px 15px !default;
+	$u-action-sheet-item-wrap-item-padding:15px !default;
+	$u-action-sheet-item-wrap-name-font-size:16px !default;
+	$u-action-sheet-item-wrap-subname-font-size:13px !default;
+	$u-action-sheet-item-wrap-subname-color: #c0c4cc !default;
+	$u-action-sheet-item-wrap-subname-margin-top:10px !default;
+	$u-action-sheet-cancel-text-font-size:16px !default;
+	$u-action-sheet-cancel-text-color:$u-content-color !default;
+	$u-action-sheet-cancel-text-font-size:15px !default;
+	$u-action-sheet-cancel-text-hover-background-color:rgb(242, 243, 245) !default;
 
-	.u-tips {
-		font-size: 26rpx;
-		text-align: center;
-		padding: 34rpx 0;
-		line-height: 1;
-		color: $u-tips-color;
+	.u-reset-button {
+		width: $u-action-sheet-reset-button-width;
 	}
 
-	.u-action-sheet-item {
-		@include vue-flex;;
-		line-height: 1;
-		justify-content: center;
-		align-items: center;
-		font-size: 32rpx;
-		padding: 34rpx 0;
-		flex-direction: column;
-	}
-	
-	.u-action-sheet-item__subtext {
-		font-size: 24rpx;
-		color: $u-tips-color;
-		margin-top: 20rpx;
-	}
+	.u-action-sheet {
+		text-align: center;
+		&__header {
+			position: relative;
+			padding: $u-action-sheet-title-padding;
+			&__title {
+				font-size: $u-action-sheet-title-font-size;
+				color: $u-action-sheet-title-color;
+				font-weight: bold;
+				text-align: center;
+			}
 
-	.u-gab {
-		height: 12rpx;
-		background-color: rgb(234, 234, 236);
-	}
+			&__icon-wrap {
+				position: absolute;
+				right: $u-action-sheet-header-icon-wrap-right;
+				top: $u-action-sheet-header-icon-wrap-top;
+			}
+		}
+
+		&__description {
+			font-size: $u-action-sheet-description-font-size;
+			color: $u-tips-color;
+			margin: $u-action-sheet-description-margin;
+			text-align: center;
+		}
+
+		&__item-wrap {
 
-	.u-actionsheet-cancel {
-		color: $u-main-color;
+			&__item {
+				padding: $u-action-sheet-item-wrap-item-padding;
+				@include flex;
+				align-items: center;
+				justify-content: center;
+				flex-direction: column;
+
+				&__name {
+					font-size: $u-action-sheet-item-wrap-name-font-size;
+					color: $u-main-color;
+					text-align: center;
+				}
+
+				&__subname {
+					font-size: $u-action-sheet-item-wrap-subname-font-size;
+					color: $u-action-sheet-item-wrap-subname-color;
+					margin-top: $u-action-sheet-item-wrap-subname-margin-top;
+					text-align: center;
+				}
+			}
+		}
+
+		&__cancel-text {
+			font-size: $u-action-sheet-cancel-text-font-size;
+			color: $u-action-sheet-cancel-text-color;
+			text-align: center;
+			padding: $u-action-sheet-cancel-text-font-size;
+		}
+
+		&--hover {
+			background-color: $u-action-sheet-cancel-text-hover-background-color;
+		}
 	}
 </style>

+ 59 - 0
uview-ui/components/u-album/props.js

@@ -0,0 +1,59 @@
+export default {
+    props: {
+        // 图片地址,Array<String>|Array<Object>形式
+        urls: {
+            type: Array,
+            default: uni.$u.props.album.urls
+        },
+        // 指定从数组的对象元素中读取哪个属性作为图片地址
+        keyName: {
+            type: String,
+            default: uni.$u.props.album.keyName
+        },
+        // 单图时,图片长边的长度
+        singleSize: {
+            type: [String, Number],
+            default: uni.$u.props.album.singleSize
+        },
+        // 多图时,图片边长
+        multipleSize: {
+            type: [String, Number],
+            default: uni.$u.props.album.multipleSize
+        },
+        // 多图时,图片水平和垂直之间的间隔
+        space: {
+            type: [String, Number],
+            default: uni.$u.props.album.space
+        },
+        // 单图时,图片缩放裁剪的模式
+        singleMode: {
+            type: String,
+            default: uni.$u.props.album.singleMode
+        },
+        // 多图时,图片缩放裁剪的模式
+        multipleMode: {
+            type: String,
+            default: uni.$u.props.album.multipleMode
+        },
+        // 最多展示的图片数量,超出时最后一个位置将会显示剩余图片数量
+        maxCount: {
+            type: [String, Number],
+            default: uni.$u.props.album.maxCount
+        },
+        // 是否可以预览图片
+        previewFullImage: {
+            type: Boolean,
+            default: uni.$u.props.album.previewFullImage
+        },
+        // 每行展示图片数量,如设置,singleSize和multipleSize将会无效
+        rowCount: {
+            type: [String, Number],
+            default: uni.$u.props.album.rowCount
+        },
+        // 超出maxCount时是否显示查看更多的提示
+        showMore: {
+            type: Boolean,
+            default: uni.$u.props.album.showMore
+        }
+    }
+}

+ 259 - 0
uview-ui/components/u-album/u-album.vue

@@ -0,0 +1,259 @@
+<template>
+    <view class="u-album">
+        <view
+            class="u-album__row"
+            ref="u-album__row"
+            v-for="(arr, index) in showUrls"
+            :forComputedUse="albumWidth"
+            :key="index"
+        >
+            <view
+                class="u-album__row__wrapper"
+                v-for="(item, index1) in arr"
+                :key="index1"
+                :style="[imageStyle(index + 1, index1 + 1)]"
+                @tap="previewFullImage ? onPreviewTap(getSrc(item)) : ''"
+            >
+                <image
+                    :src="getSrc(item)"
+                    :mode="
+                        urls.length === 1
+                            ? imageHeight > 0
+                                ? singleMode
+                                : 'widthFix'
+                            : multipleMode
+                    "
+                    :style="[
+                        {
+                            width: imageWidth,
+                            height: imageHeight
+                        }
+                    ]"
+                ></image>
+                <view
+                    v-if="
+                        showMore &&
+                        urls.length > rowCount * showUrls.length &&
+                        index === showUrls.length - 1 &&
+                        index1 === showUrls[showUrls.length - 1].length - 1
+                    "
+                    class="u-album__row__wrapper__text"
+                >
+                    <u--text
+                        :text="`+${urls.length - maxCount}`"
+                        color="#fff"
+                        :size="multipleSize * 0.3"
+                        align="center"
+                        customStyle="justify-content: center"
+                    ></u--text>
+                </view>
+            </view>
+        </view>
+    </view>
+</template>
+
+<script>
+import props from './props.js'
+// #ifdef APP-NVUE
+// 由于weex为阿里的KPI业绩考核的产物,所以不支持百分比单位,这里需要通过dom查询组件的宽度
+const dom = uni.requireNativePlugin('dom')
+// #endif
+
+/**
+ * Album 相册
+ * @description 本组件提供一个类似相册的功能,让开发者开发起来更加得心应手。减少重复的模板代码
+ * @tutorial https://www.uviewui.com/components/album.html
+ *
+ * @property {Array}           urls             图片地址列表 Array<String>|Array<Object>形式
+ * @property {String}          keyName          指定从数组的对象元素中读取哪个属性作为图片地址
+ * @property {String | Number} singleSize       单图时,图片长边的长度  (默认 180 )
+ * @property {String | Number} multipleSize     多图时,图片边长 (默认 70 )
+ * @property {String | Number} space            多图时,图片水平和垂直之间的间隔 (默认 6 )
+ * @property {String}          singleMode       单图时,图片缩放裁剪的模式 (默认 'scaleToFill' )
+ * @property {String}          multipleMode     多图时,图片缩放裁剪的模式 (默认 'aspectFill' )
+ * @property {String | Number} maxCount         取消按钮的提示文字 (默认 9 )
+ * @property {Boolean}         previewFullImage 是否可以预览图片 (默认 true )
+ * @property {String | Number} rowCount         每行展示图片数量,如设置,singleSize和multipleSize将会无效	(默认 3 )
+ * @property {Boolean}         showMore         超出maxCount时是否显示查看更多的提示 (默认 true )
+ *
+ * @event    {Function}        albumWidth       某些特殊的情况下,需要让文字与相册的宽度相等,这里事件的形式对外发送  (回调参数 width )
+ * @example <u-album :urls="urls2" @albumWidth="width => albumWidth = width" multipleSize="68" ></u-album>
+ */
+export default {
+    name: 'u-album',
+    mixins: [uni.$u.mpMixin, uni.$u.mixin, props],
+    data() {
+        return {
+            // 单图的宽度
+            singleWidth: 0,
+            // 单图的高度
+            singleHeight: 0,
+            // 单图时,如果无法获取图片的尺寸信息,让图片宽度默认为容器的一定百分比
+            singlePercent: 0.6
+        }
+    },
+    watch: {
+        urls: {
+            immediate: true,
+            handler(newVal) {
+                if (newVal.length === 1) {
+                    this.getImageRect()
+                }
+            }
+        }
+    },
+    computed: {
+        imageStyle() {
+            return (index1, index2) => {
+                const { space, rowCount, multipleSize, urls } = this,
+                    { addUnit, addStyle } = uni.$u,
+                    rowLen = this.showUrls.length,
+                    allLen = this.urls.length
+                const style = {
+                    marginRight: addUnit(space),
+                    marginBottom: addUnit(space)
+                }
+                // 如果为最后一行,则每个图片都无需下边框
+                if (index1 === rowLen) style.marginBottom = 0
+                // 每行的最右边一张和总长度的最后一张无需右边框
+                if (
+                    index2 === rowCount ||
+                    (index1 === rowLen &&
+                        index2 === this.showUrls[index1 - 1].length)
+                )
+                    style.marginRight = 0
+                return style
+            }
+        },
+        // 将数组划分为二维数组
+        showUrls() {
+            const arr = []
+            this.urls.map((item, index) => {
+                // 限制最大展示数量
+                if (index + 1 <= this.maxCount) {
+                    // 计算该元素为第几个素组内
+                    const itemIndex = Math.floor(index / this.rowCount)
+                    // 判断对应的索引是否存在
+                    if (!arr[itemIndex]) {
+                        arr[itemIndex] = []
+                    }
+                    arr[itemIndex].push(item)
+                }
+            })
+            return arr
+        },
+        imageWidth() {
+            return uni.$u.addUnit(
+                this.urls.length === 1 ? this.singleWidth : this.multipleSize
+            )
+        },
+        imageHeight() {
+            return uni.$u.addUnit(
+                this.urls.length === 1 ? this.singleHeight : this.multipleSize
+            )
+        },
+        // 此变量无实际用途,仅仅是为了利用computed特性,让其在urls长度等变化时,重新计算图片的宽度
+        // 因为用户在某些特殊的情况下,需要让文字与相册的宽度相等,所以这里事件的形式对外发送
+        albumWidth() {
+            let width = 0
+            if (this.urls.length === 1) {
+                width = this.singleWidth
+            } else {
+                width =
+                    this.showUrls[0].length * this.multipleSize +
+                    this.space * (this.showUrls[0].length - 1)
+            }
+            this.$emit('albumWidth', width)
+            return width
+        }
+    },
+    methods: {
+        // 预览图片
+        onPreviewTap(url) {
+            const urls = this.urls.map((item) => {
+                return this.getSrc(item)
+            })
+            uni.previewImage({
+                current: url,
+                urls
+            })
+        },
+        // 获取图片的路径
+        getSrc(item) {
+            return uni.$u.test.object(item)
+                ? (this.keyName && item[this.keyName]) || item.src
+                : item
+        },
+        // 单图时,获取图片的尺寸
+        // 在小程序中,需要将网络图片的的域名添加到小程序的download域名才可能获取尺寸
+        // 在没有添加的情况下,让单图宽度默认为盒子的一定宽度(singlePercent)
+        getImageRect() {
+            const src = this.getSrc(this.urls[0])
+            uni.getImageInfo({
+                src,
+                success: (res) => {
+                    // 判断图片横向还是竖向展示方式
+                    const isHorizotal = res.width >= res.height
+                    this.singleWidth = isHorizotal
+                        ? this.singleSize
+                        : (res.width / res.height) * this.singleSize
+                    this.singleHeight = !isHorizotal
+                        ? this.singleSize
+                        : (res.height / res.width) * this.singleWidth
+                },
+                fail: () => {
+                    this.getComponentWidth()
+                }
+            })
+        },
+        // 获取组件的宽度
+        async getComponentWidth() {
+            // 延时一定时间,以获取dom尺寸
+            await uni.$u.sleep(30)
+            // #ifndef APP-NVUE
+            this.$uGetRect('.u-album__row').then((size) => {
+                this.singleWidth = size.width * this.singlePercent
+            })
+            // #endif
+
+            // #ifdef APP-NVUE
+            // 这里ref="u-album__row"所在的标签为通过for循环出来,导致this.$refs['u-album__row']是一个数组
+            const ref = this.$refs['u-album__row'][0]
+            ref &&
+                dom.getComponentRect(ref, (res) => {
+                    this.singleWidth = res.size.width * this.singlePercent
+                })
+            // #endif
+        }
+    }
+}
+</script>
+
+<style lang="scss" scoped>
+@import '../../libs/css/components.scss';
+
+.u-album {
+    @include flex(column);
+
+    &__row {
+        @include flex(row);
+        flex-wrap: wrap;
+
+        &__wrapper {
+            position: relative;
+
+            &__text {
+                position: absolute;
+                top: 0;
+                left: 0;
+                right: 0;
+                bottom: 0;
+                background-color: rgba(0, 0, 0, 0.3);
+                @include flex(row);
+                justify-content: center;
+                align-items: center;
+            }
+        }
+    }
+}
+</style>

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

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

+ 44 - 0
uview-ui/components/u-alert/props.js

@@ -0,0 +1,44 @@
+export default {
+    props: {
+        // 显示文字
+        title: {
+            type: String,
+            default: uni.$u.props.alert.title
+        },
+        // 主题,success/warning/info/error
+        type: {
+            type: String,
+            default: uni.$u.props.alert.type
+        },
+        // 辅助性文字
+        description: {
+            type: String,
+            default: uni.$u.props.alert.description
+        },
+        // 是否可关闭
+        closable: {
+            type: Boolean,
+            default: uni.$u.props.alert.closable
+        },
+        // 是否显示图标
+        showIcon: {
+            type: Boolean,
+            default: uni.$u.props.alert.showIcon
+        },
+        // 浅或深色调,light-浅色,dark-深色
+        effect: {
+            type: String,
+            default: uni.$u.props.alert.effect
+        },
+        // 文字是否居中
+        center: {
+            type: Boolean,
+            default: uni.$u.props.alert.center
+        },
+        // 字体大小
+        fontSize: {
+            type: [String, Number],
+            default: uni.$u.props.alert.fontSize
+        }
+    }
+}

+ 243 - 0
uview-ui/components/u-alert/u-alert.vue

@@ -0,0 +1,243 @@
+<template>
+	<u-transition
+	    mode="fade"
+	    :show="show"
+	>
+		<view
+		    class="u-alert"
+		    :class="[`u-alert--${type}--${effect}`]"
+		    @tap.stop="clickHandler"
+		    :style="[$u.addStyle(customStyle)]"
+		>
+			<view
+			    class="u-alert__icon"
+			    v-if="showIcon"
+			>
+				<u-icon
+				    :name="iconName"
+				    size="18"
+				    :color="iconColor"
+				></u-icon>
+			</view>
+			<view
+			    class="u-alert__content"
+			    :style="[{
+					paddingRight: closable ? '20px' : 0
+				}]"
+			>
+				<text
+				    class="u-alert__content__title"
+				    v-if="title"
+					:style="[{
+						fontSize: $u.addUnit(fontSize),
+						textAlign: center ? 'center' : 'left'
+					}]"
+				    :class="[effect === 'dark' ? 'u-alert__text--dark' : `u-alert__text--${type}--light`]"
+				>{{ title }}</text>
+				<text
+				    class="u-alert__content__desc"
+					v-if="description"
+					:style="[{
+						fontSize: $u.addUnit(fontSize),
+						textAlign: center ? 'center' : 'left'
+					}]"
+				    :class="[effect === 'dark' ? 'u-alert__text--dark' : `u-alert__text--${type}--light`]"
+				>{{ description }}</text>
+			</view>
+			<view
+			    class="u-alert__close"
+			    v-if="closable"
+			    @tap.stop="closeHandler"
+			>
+				<u-icon
+				    name="close"
+				    :color="iconColor"
+				    size="15"
+				></u-icon>
+			</view>
+		</view>
+	</u-transition>
+</template>
+
+<script>
+	import props from './props.js';
+	/**
+	 * Alert  警告提示
+	 * @description 警告提示,展现需要关注的信息。
+	 * @tutorial https://www.uviewui.com/components/alertTips.html
+	 * 
+	 * @property {String}			title       显示的文字 
+	 * @property {String}			type        使用预设的颜色  (默认 'warning' )
+	 * @property {String}			description 辅助性文字,颜色比title浅一点,字号也小一点,可选  
+	 * @property {Boolean}			closable    关闭按钮(默认为叉号icon图标)  (默认 false )
+	 * @property {Boolean}			showIcon    是否显示左边的辅助图标   ( 默认 false )
+	 * @property {String}			effect      多图时,图片缩放裁剪的模式  (默认 'light' )
+	 * @property {Boolean}			center		文字是否居中  (默认 false )
+	 * @property {String | Number}	fontSize    字体大小  (默认 14 )
+	 * @property {Object}			customStyle	定义需要用到的外部样式
+	 * @event    {Function}        click       点击组件时触发
+	 * @example  <u-alert :title="title"  type = "warning" :closable="closable" :description = "description"></u-alert>
+	 */
+	export default {
+		name: 'u-alert',
+		mixins: [uni.$u.mpMixin, uni.$u.mixin, props],
+		data() {
+			return {
+				show: true
+			}
+		},
+		computed: {
+			iconColor() {
+				return this.effect === 'light' ? this.type : '#fff'
+			},
+			// 不同主题对应不同的图标
+			iconName() {
+				switch (this.type) {
+					case 'success':
+						return 'checkmark-circle-fill';
+						break;
+					case 'error':
+						return 'close-circle-fill';
+						break;
+					case 'warning':
+						return 'error-circle-fill';
+						break;
+					case 'info':
+						return 'info-circle-fill';
+						break;
+					case 'primary':
+						return 'more-circle-fill';
+						break;
+					default: 
+						return 'error-circle-fill';
+				}
+			}
+		},
+		methods: {
+			// 点击内容
+			clickHandler() {
+				this.$emit('click')
+			},
+			// 点击关闭按钮
+			closeHandler() {
+				this.show = false
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	@import "../../libs/css/components.scss";
+
+	.u-alert {
+		position: relative;
+		background-color: $u-primary;
+		padding: 8px 10px;
+		@include flex(row);
+		align-items: center;
+		border-top-left-radius: 4px;
+		border-top-right-radius: 4px;
+		border-bottom-left-radius: 4px;
+		border-bottom-right-radius: 4px;
+
+		&--primary--dark {
+			background-color: $u-primary;
+		}
+
+		&--primary--light {
+			background-color: #ecf5ff;
+		}
+
+		&--error--dark {
+			background-color: $u-error;
+		}
+
+		&--error--light {
+			background-color: #FEF0F0;
+		}
+
+		&--success--dark {
+			background-color: $u-success;
+		}
+
+		&--success--light {
+			background-color: #f5fff0;
+		}
+
+		&--warning--dark {
+			background-color: $u-warning;
+		}
+
+		&--warning--light {
+			background-color: #FDF6EC;
+		}
+
+		&--info--dark {
+			background-color: $u-info;
+		}
+
+		&--info--light {
+			background-color: #f4f4f5;
+		}
+
+		&__icon {
+			margin-right: 5px;
+		}
+
+		&__content {
+			@include flex(column);
+			flex: 1;
+
+			&__title {
+				color: $u-main-color;
+				font-size: 14px;
+				font-weight: bold;
+				color: #fff;
+				margin-bottom: 2px;
+			}
+
+			&__desc {
+				color: $u-main-color;
+				font-size: 14px;
+				flex-wrap: wrap;
+				color: #fff;
+			}
+		}
+
+		&__title--dark,
+		&__desc--dark {
+			color: #FFFFFF;
+		}
+
+		&__text--primary--light,
+		&__text--primary--light {
+			color: $u-primary;
+		}
+
+		&__text--success--light,
+		&__text--success--light {
+			color: $u-success;
+		}
+
+		&__text--warning--light,
+		&__text--warning--light {
+			color: $u-warning;
+		}
+
+		&__text--error--light,
+		&__text--error--light {
+			color: $u-error;
+		}
+
+		&__text--info--light,
+		&__text--info--light {
+			color: $u-info;
+		}
+
+		&__close {
+			position: absolute;
+			top: 11px;
+			right: 10px;
+		}
+	}
+</style>

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

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

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

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

+ 52 - 0
uview-ui/components/u-avatar-group/props.js

@@ -0,0 +1,52 @@
+export default {
+    props: {
+        // 头像图片组
+        urls: {
+            type: Array,
+            default: uni.$u.props.avatarGroup.urls
+        },
+        // 最多展示的头像数量
+        maxCount: {
+            type: [String, Number],
+            default: uni.$u.props.avatarGroup.maxCount
+        },
+        // 头像形状
+        shape: {
+            type: String,
+            default: uni.$u.props.avatarGroup.shape
+        },
+        // 图片裁剪模式
+        mode: {
+            type: String,
+            default: uni.$u.props.avatarGroup.mode
+        },
+        // 超出maxCount时是否显示查看更多的提示
+        showMore: {
+            type: Boolean,
+            default: uni.$u.props.avatarGroup.showMore
+        },
+        // 头像大小
+        size: {
+            type: [String, Number],
+            default: uni.$u.props.avatarGroup.size
+        },
+        // 指定从数组的对象元素中读取哪个属性作为图片地址
+        keyName: {
+            type: String,
+            default: uni.$u.props.avatarGroup.keyName
+        },
+		// 头像之间的遮挡比例
+        gap: {
+            type: [String, Number],
+            validator(value) {
+                return value >= 0 && value <= 1
+            },
+            default: uni.$u.props.avatarGroup.gap
+        },
+		// 需额外显示的值
+		extraValue: {
+			type: [Number, String],
+			default: uni.$u.props.avatarGroup.extraValue
+		}
+    }
+}

+ 103 - 0
uview-ui/components/u-avatar-group/u-avatar-group.vue

@@ -0,0 +1,103 @@
+<template>
+	<view class="u-avatar-group">
+		<view
+		    class="u-avatar-group__item"
+		    v-for="(item, index) in showUrl"
+		    :key="index"
+		    :style="{
+				marginLeft: index === 0 ? 0 : $u.addUnit(-size * gap)
+			}"
+		>
+			<u-avatar
+			    :size="size"
+			    :shape="shape"
+			    :mode="mode"
+			    :src="$u.test.object(item) ? keyName && item[keyName] || item.url : item"
+			></u-avatar>
+			<view
+			    class="u-avatar-group__item__show-more"
+			    v-if="showMore && index === showUrl.length - 1 && (urls.length > maxCount || extraValue > 0)"
+				@tap="clickHandler"
+			>
+				<u--text
+				    color="#ffffff"
+				    :size="size * 0.4"
+				    :text="`+${extraValue || urls.length - showUrl.length}`"
+					align="center"
+					customStyle="justify-content: center"
+				></u--text>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	import props from './props.js';
+	/**
+	 * AvatarGroup  头像组
+	 * @description 本组件一般用于展示头像的地方,如个人中心,或者评论列表页的用户头像展示等场所。
+	 * @tutorial https://www.uviewui.com/components/avatar.html
+	 * 
+	 * @property {Array}           urls     头像图片组 (默认 [] )
+	 * @property {String | Number} maxCount 最多展示的头像数量 ( 默认 5 )
+	 * @property {String}          shape    头像形状( 'circle' (默认) | 'square' )
+	 * @property {String}          mode     图片裁剪模式(默认 'scaleToFill' )
+	 * @property {Boolean}         showMore 超出maxCount时是否显示查看更多的提示 (默认 true )
+	 * @property {String | Number} size      头像大小 (默认 40 )
+	 * @property {String}          keyName  指定从数组的对象元素中读取哪个属性作为图片地址 
+	 * @property {String | Number} gap      头像之间的遮挡比例(0.4代表遮挡40%)  (默认 0.5 )
+	 * @property {String | Number} extraValue  需额外显示的值
+	 * @event    {Function}        showMore 头像组更多点击
+	 * @example  <u-avatar-group:urls="urls" size="35" gap="0.4" ></u-avatar-group:urls=>
+	 */
+	export default {
+		name: 'u-avatar-group',
+		mixins: [uni.$u.mpMixin, uni.$u.mixin, props],
+		data() {
+			return {
+
+			}
+		},
+		computed: {
+			showUrl() {
+				return this.urls.slice(0, this.maxCount)
+			}
+		},
+		methods: {
+			clickHandler() {
+				this.$emit('showMore')
+			}
+		},
+	}
+</script>
+
+<style lang="scss" scoped>
+	@import "../../libs/css/components.scss";
+
+	.u-avatar-group {
+		@include flex;
+
+		&__item {
+			margin-left: -10px;
+			position: relative;
+
+			&--no-indent {
+				// 如果你想质疑作者不会使用:first-child,说明你太年轻,因为nvue不支持
+				margin-left: 0;
+			}
+
+			&__show-more {
+				position: absolute;
+				top: 0;
+				bottom: 0;
+				left: 0;
+				right: 0;
+				background-color: rgba(0, 0, 0, 0.3);
+				@include flex;
+				align-items: center;
+				justify-content: center;
+				border-radius: 100px;
+			}
+		}
+	}
+</style>

+ 78 - 0
uview-ui/components/u-avatar/props.js

@@ -0,0 +1,78 @@
+export default {
+    props: {
+        // 头像图片路径(不能为相对路径)
+        src: {
+            type: String,
+            default: uni.$u.props.avatar.src
+        },
+        // 头像形状,circle-圆形,square-方形
+        shape: {
+            type: String,
+            default: uni.$u.props.avatar.shape
+        },
+        // 头像尺寸
+        size: {
+            type: [String, Number],
+            default: uni.$u.props.avatar.size
+        },
+        // 裁剪模式
+        mode: {
+            type: String,
+            default: uni.$u.props.avatar.mode
+        },
+        // 显示的文字
+        text: {
+            type: String,
+            default: uni.$u.props.avatar.text
+        },
+        // 背景色
+        bgColor: {
+            type: String,
+            default: uni.$u.props.avatar.bgColor
+        },
+        // 文字颜色
+        color: {
+            type: String,
+            default: uni.$u.props.avatar.color
+        },
+        // 文字大小
+        fontSize: {
+            type: [String, Number],
+            default: uni.$u.props.avatar.fontSize
+        },
+        // 显示的图标
+        icon: {
+            type: String,
+            default: uni.$u.props.avatar.icon
+        },
+        // 显示小程序头像,只对百度,微信,QQ小程序有效
+        mpAvatar: {
+            type: Boolean,
+            default: uni.$u.props.avatar.mpAvatar
+        },
+        // 是否使用随机背景色
+        randomBgColor: {
+            type: Boolean,
+            default: uni.$u.props.avatar.randomBgColor
+        },
+        // 加载失败的默认头像(组件有内置默认图片)
+        defaultUrl: {
+            type: String,
+            default: uni.$u.props.avatar.defaultUrl
+        },
+        // 如果配置了randomBgColor为true,且配置了此值,则从默认的背景色数组中取出对应索引的颜色值,取值0-19之间
+        colorIndex: {
+            type: [String, Number],
+            // 校验参数规则,索引在0-19之间
+            validator(n) {
+                return uni.$u.test.range(n, [0, 19]) || n === ''
+            },
+            default: uni.$u.props.avatar.colorIndex
+        },
+        // 组件标识符
+        name: {
+            type: String,
+            default: uni.$u.props.avatar.name
+        }
+    }
+}

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 51 - 19
uview-ui/components/u-avatar/u-avatar.vue


+ 54 - 0
uview-ui/components/u-back-top/props.js

@@ -0,0 +1,54 @@
+export default {
+    props: {
+        // 返回顶部的形状,circle-圆形,square-方形
+        mode: {
+            type: String,
+            default: uni.$u.props.backtop.mode
+        },
+        // 自定义图标
+        icon: {
+            type: String,
+            default: uni.$u.props.backtop.icon
+        },
+        // 提示文字
+        text: {
+            type: String,
+            default: uni.$u.props.backtop.text
+        },
+        // 返回顶部滚动时间
+        duration: {
+            type: [String, Number],
+            default: uni.$u.props.backtop.duration
+        },
+        // 滚动距离
+        scrollTop: {
+            type: [String, Number],
+            default: uni.$u.props.backtop.scrollTop
+        },
+        // 距离顶部多少距离显示,单位px
+        top: {
+            type: [String, Number],
+            default: uni.$u.props.backtop.top
+        },
+        // 返回顶部按钮到底部的距离,单位px
+        bottom: {
+            type: [String, Number],
+            default: uni.$u.props.backtop.bottom
+        },
+        // 返回顶部按钮到右边的距离,单位px
+        right: {
+            type: [String, Number],
+            default: uni.$u.props.backtop.right
+        },
+        // 层级
+        zIndex: {
+            type: [String, Number],
+            default: uni.$u.props.backtop.zIndex
+        },
+        // 图标的样式,对象形式
+        iconStyle: {
+            type: Object,
+            default: uni.$u.props.backtop.iconStyle
+        }
+    }
+}

+ 97 - 121
uview-ui/components/u-back-top/u-back-top.vue

@@ -1,153 +1,129 @@
 <template>
-	<view @tap="backToTop" class="u-back-top" :class="['u-back-top--mode--' + mode]" :style="[{
-		bottom: bottom + 'rpx',
-		right: right + 'rpx',
-		borderRadius: mode == 'circle' ? '10000rpx' : '8rpx',
-		zIndex: uZIndex,
-		opacity: opacity
-	}, customStyle]">
-		<view class="u-back-top__content" v-if="!$slots.default && !$slots.$default">
-			<u-icon @click="backToTop" :name="icon" :custom-style="iconStyle"></u-icon>
-			<view class="u-back-top__content__tips">
-				{{tips}}
-			</view>
+	<u-transition
+	    mode="fade"
+	    :customStyle="backTopStyle"
+	    :show="show"
+	>
+		<view
+		    class="u-back-top"
+			:style="[contentStyle]"
+		    v-if="!$slots.default && !$slots.$default"
+			@click="backToTop"
+		>
+			<u-icon
+			    :name="icon"
+			    :custom-style="iconStyle"
+			></u-icon>
+			<text
+			    v-if="text"
+			    class="u-back-top__text"
+			>{{text}}</text>
 		</view>
 		<slot v-else />
-	</view>
+	</u-transition>
 </template>
 
 <script>
+	import props from './props.js';
+	// #ifdef APP-NVUE
+	const dom = weex.requireModule('dom')
+	// #endif
+	/**
+	 * backTop 返回顶部
+	 * @description 本组件一个用于长页面,滑动一定距离后,出现返回顶部按钮,方便快速返回顶部的场景。
+	 * @tutorial https://uviewui.com/components/backTop.html
+	 * 
+	 * @property {String}			mode  		返回顶部的形状,circle-圆形,square-方形 (默认 'circle' )
+	 * @property {String} 			icon 		自定义图标 (默认 'arrow-upward' ) 见官方文档示例
+	 * @property {String} 			text 		提示文字 
+	 * @property {String | Number}  duration	返回顶部滚动时间 (默认 100)
+	 * @property {String | Number}  scrollTop	滚动距离 (默认 0 )
+	 * @property {String | Number}  top  		距离顶部多少距离显示,单位px (默认 400 )
+	 * @property {String | Number}  bottom  	返回顶部按钮到底部的距离,单位px (默认 100 )
+	 * @property {String | Number}  right  		返回顶部按钮到右边的距离,单位px (默认 20 )
+	 * @property {String | Number}  zIndex 		层级   (默认 9 )
+	 * @property {Object<Object>}  	iconStyle 	图标的样式,对象形式   (默认 {color: '#909399',fontSize: '19px'})
+	 * @property {Object}			customStyle	定义需要用到的外部样式
+	 * 
+	 * @example <u-back-top :scrollTop="scrollTop"></u-back-top>
+	 */
 	export default {
 		name: 'u-back-top',
-		props: {
-			// 返回顶部的形状,circle-圆形,square-方形
-			mode: {
-				type: String,
-				default: 'circle'
-			},
-			// 自定义图标
-			icon: {
-				type: String,
-				default: 'arrow-upward'
-			},
-			// 提示文字
-			tips: {
-				type: String,
-				default: ''
-			},
-			// 返回顶部滚动时间
-			duration: {
-				type: [Number, String],
-				default: 100
-			},
-			// 滚动距离
-			scrollTop: {
-				type: [Number, String],
-				default: 0
-			},
-			// 距离顶部多少距离显示,单位rpx
-			top: {
-				type: [Number, String],
-				default: 400
-			},
-			// 返回顶部按钮到底部的距离,单位rpx
-			bottom: {
-				type: [Number, String],
-				default: 200
-			},
-			// 返回顶部按钮到右边的距离,单位rpx
-			right: {
-				type: [Number, String],
-				default: 40
-			},
-			// 层级
-			zIndex: {
-				type: [Number, String],
-				default: '9'
-			},
-			// 图标的样式,对象形式
-			iconStyle: {
-				type: Object,
-				default() {
-					return {
-						color: '#909399',
-						fontSize: '38rpx'
-					}
+		mixins: [uni.$u.mpMixin, uni.$u.mixin,props],
+		computed: {
+			backTopStyle() {
+				// 动画组件样式
+				const style = {
+					bottom: uni.$u.addUnit(this.bottom),
+					right: uni.$u.addUnit(this.right),
+					width: '40px',
+					height: '40px',
+					position: 'fixed',
+					zIndex: 10,
 				}
+				return style
 			},
-			// 整个组件的样式
-			customStyle: {
-				type: Object,
-				default() {
-					return {}
-				}
-			}
-		},
-		watch: {
-			showBackTop(nVal, oVal) {
-				// 当组件的显示与隐藏状态发生跳变时,修改组件的层级和不透明度
-				// 让组件有显示和消失的动画效果,如果用v-if控制组件状态,将无设置动画效果
-				if(nVal) {
-					this.uZIndex = this.zIndex;
-					this.opacity = 1;
+			show() {
+				return uni.$u.getPx(this.scrollTop) > uni.$u.getPx(this.top)
+			},
+			contentStyle() {
+				const style = {}
+				let radius = 0
+				// 是否圆形
+				if(this.mode === 'circle') {
+					radius = '100px'
 				} else {
-					this.uZIndex = -1;
-					this.opacity = 0;
+					radius = '4px'
 				}
-			}
-		},
-		computed: {
-			showBackTop() {
-				// 由于scrollTop为页面的滚动距离,默认为px单位,这里将用于传入的top(rpx)值
-				// 转为px用于比较,如果滚动条到顶的距离大于设定的距离,就显示返回顶部的按钮
-				return this.scrollTop > uni.upx2px(this.top);
-			},
-		},
-		data() {
-			return {
-				// 不透明度,为了让组件有一个显示和隐藏的过渡动画
-				opacity: 0,
-				// 组件的z-index值,隐藏时设置为-1,就会看不到
-				uZIndex: -1
+				// 为了兼容安卓nvue,只能这么分开写
+				style.borderTopLeftRadius = radius
+				style.borderTopRightRadius = radius
+				style.borderBottomLeftRadius = radius
+				style.borderBottomRightRadius = radius
+				return uni.$u.deepMerge(style, uni.$u.addStyle(this.customStyle))
 			}
 		},
 		methods: {
 			backToTop() {
+				// #ifdef APP-NVUE
+				if (!this.$parent.$refs['u-back-top']) {
+					uni.$u.error(`nvue页面需要给页面最外层元素设置"ref='u-back-top'`)
+				}
+				dom.scrollToElement(this.$parent.$refs['u-back-top'], {
+					offset: 0
+				})
+				// #endif
+				
+				// #ifndef APP-NVUE
 				uni.pageScrollTo({
 					scrollTop: 0,
 					duration: this.duration
 				});
+				// #endif
+				this.$emit('click')
 			}
 		}
 	}
 </script>
 
 <style lang="scss" scoped>
-	@import "../../libs/css/style.components.scss";
-	
+	@import '../../libs/css/components.scss';
+     $u-back-top-flex:1 !default;
+     $u-back-top-height:100% !default;
+     $u-back-top-background-color:#E1E1E1 !default;
+     $u-back-top-tips-font-size:12px !default;
 	.u-back-top {
-		width: 80rpx;
-		height: 80rpx;
-		position: fixed;
-		z-index: 9;
-		@include vue-flex;
+		@include flex;
 		flex-direction: column;
-		justify-content: center;
-		background-color: #E1E1E1;
-		color: $u-content-color;
 		align-items: center;
-		transition: opacity 0.4s;
-		
-		&__content {
-			@include vue-flex;
-			flex-direction: column;
-			align-items: center;
-			
-			&__tips {
-				font-size: 24rpx;
-				transform: scale(0.8);
-				line-height: 1;
-			}
+		flex:$u-back-top-flex;
+		height: $u-back-top-height;
+		justify-content: center;
+		background-color: $u-back-top-background-color;
+
+		&__tips {
+			font-size:$u-back-top-tips-font-size;
+			transform: scale(0.8);
 		}
 	}
 </style>

+ 72 - 0
uview-ui/components/u-badge/props.js

@@ -0,0 +1,72 @@
+export default {
+    props: {
+        // 是否显示圆点
+        isDot: {
+            type: Boolean,
+            default: uni.$u.props.badge.isDot
+        },
+        // 显示的内容
+        value: {
+            type: [Number, String],
+            default: uni.$u.props.badge.value
+        },
+        // 是否显示
+        show: {
+            type: Boolean,
+            default: uni.$u.props.badge.show
+        },
+        // 最大值,超过最大值会显示 '{max}+'
+        max: {
+            type: [Number, String],
+            default: uni.$u.props.badge.max
+        },
+        // 主题类型,error|warning|success|primary
+        type: {
+            type: String,
+            default: uni.$u.props.badge.type
+        },
+        // 当数值为 0 时,是否展示 Badge
+        showZero: {
+            type: Boolean,
+            default: uni.$u.props.badge.showZero
+        },
+        // 背景颜色,优先级比type高,如设置,type参数会失效
+        bgColor: {
+            type: [String, null],
+            default: uni.$u.props.badge.bgColor
+        },
+        // 字体颜色
+        color: {
+            type: [String, null],
+            default: uni.$u.props.badge.color
+        },
+        // 徽标形状,circle-四角均为圆角,horn-左下角为直角
+        shape: {
+            type: String,
+            default: uni.$u.props.badge.shape
+        },
+        // 设置数字的显示方式,overflow|ellipsis|limit
+        // overflow会根据max字段判断,超出显示`${max}+`
+        // ellipsis会根据max判断,超出显示`${max}...`
+        // limit会依据1000作为判断条件,超出1000,显示`${value/1000}K`,比如2.2k、3.34w,最多保留2位小数
+        numberType: {
+            type: String,
+            default: uni.$u.props.badge.numberType
+        },
+        // 设置badge的位置偏移,格式为 [x, y],也即设置的为top和right的值,absolute为true时有效
+        offset: {
+            type: Array,
+            default: uni.$u.props.badge.offset
+        },
+        // 是否反转背景和字体颜色
+        inverted: {
+            type: Boolean,
+            default: uni.$u.props.badge.inverted
+        },
+        // 是否绝对定位
+        absolute: {
+            type: Boolean,
+            default: uni.$u.props.badge.absolute
+        }
+    }
+}

+ 137 - 182
uview-ui/components/u-badge/u-badge.vue

@@ -1,216 +1,171 @@
 <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>
+	<text
+		v-if="show && ((Number(value) === 0 ? showZero : true) || isDot)"
+		:class="[isDot ? 'u-badge--dot' : 'u-badge--not-dot', inverted && 'u-badge--inverted', shape === 'horn' && 'u-badge--horn', `u-badge--${type}${inverted ? '--inverted' : ''}`]"
+		:style="[$u.addStyle(customStyle), badgeStyle]"
+		class="u-badge"
+	>{{ isDot ? '' :showValue }}</text>
 </template>
 
 <script>
+	import props from './props.js';
 	/**
-	 * 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>
+	 * badge 徽标数
+	 * @description 该组件一般用于图标右上角显示未读的消息数量,提示用户点击,有圆点和圆包含文字两种形式。
+	 * @tutorial https://uviewui.com/components/badge.html
+	 * 
+	 * @property {Boolean} 			isDot 		是否显示圆点 (默认 false )
+	 * @property {String | Number} 	value 		显示的内容
+	 * @property {Boolean} 			show 		是否显示 (默认 true )
+	 * @property {String | Number} 	max 		最大值,超过最大值会显示 '{max}+'  (默认999)
+	 * @property {String} 			type 		主题类型,error|warning|success|primary (默认 'error' )
+	 * @property {Boolean} 			showZero	当数值为 0 时,是否展示 Badge (默认 false )
+	 * @property {String} 			bgColor 	背景颜色,优先级比type高,如设置,type参数会失效
+	 * @property {String} 			color 		字体颜色 (默认 '#ffffff' )
+	 * @property {String} 			shape 		徽标形状,circle-四角均为圆角,horn-左下角为直角 (默认 'circle' )
+	 * @property {String} 			numberType	设置数字的显示方式,overflow|ellipsis|limit  (默认 'overflow' )
+	 * @property {Array}} 			offset		设置badge的位置偏移,格式为 [x, y],也即设置的为top和right的值,absolute为true时有效
+	 * @property {Boolean} 			inverted	是否反转背景和字体颜色(默认 false )
+	 * @property {Boolean} 			absolute	是否绝对定位(默认 false )
+	 * @property {Object}			customStyle	定义需要用到的外部样式
+	 * @example <u-badge :type="type" :count="count"></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
-			}
-		},
+		mixins: [uni.$u.mpMixin, props, uni.$u.mixin],
 		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)";
+				return style;
+			},
+			// 整个组件的样式
+			badgeStyle() {
+				const style = {}
+				if(this.color) {
+					style.color = this.color
 				}
-				// 如果尺寸为mini,后接上scal()
-				if(this.size == 'mini') {
-					style.transform = style.transform + " scale(0.8)";
+				if (this.bgColor && !this.inverted) {
+					style.backgroundColor = this.bgColor
 				}
-				return style;
+				if (this.absolute) {
+					style.position = 'absolute'
+					// 如果有设置offset参数
+					if(this.offset.length) {
+						// top和right分为为offset的第一个和第二个值,如果没有第二个值,则right等于top
+						const top = this.offset[0]
+						const right = this.offset[1] || top
+						style.top = uni.$u.addUnit(top)
+						style.right = uni.$u.addUnit(right)
+					}
+				}
+				return style
 			},
-			// isDot类型时,不显示文字
-			showText() {
-				if(this.isDot) return '';
-				else {
-					if(this.count > this.overflowCount) return `${this.overflowCount}+`;
-					else return this.count;
+			showValue() {
+				switch (this.numberType) {
+					case "overflow":
+						return Number(this.value) > Number(this.max) ? this.max + "+" : this.value
+						break;
+					case "ellipsis":
+						return Number(this.value) > Number(this.max) ? "..." : this.value
+						break;
+					case "limit":
+						return Number(this.value) > 999 ? Number(this.value) >= 9999 ?
+							Math.floor(this.value / 1e4 * 100) / 100 + "w" : Math.floor(this.value /
+								1e3 * 100) / 100 + "k" : this.value
+						break;
+					default:
+						return Number(this.value)
 				}
 			},
-			// 是否显示组件
-			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";
-	
+	@import "../../libs/css/components.scss";
+
+	$u-badge-primary: $u-primary !default;
+	$u-badge-error: $u-error !default;
+	$u-badge-success: $u-success !default;
+	$u-badge-info: $u-info !default;
+	$u-badge-warning: $u-warning !default;
+	$u-badge-dot-radius: 100px !default;
+	$u-badge-dot-size: 8px !default;
+	$u-badge-dot-right: 4px !default;
+	$u-badge-dot-top: 0 !default;
+	$u-badge-text-font-size: 11px !default;
+	$u-badge-text-right: 10px !default;
+	$u-badge-text-padding: 2px 5px !default;
+	$u-badge-text-align: center !default;
+	$u-badge-text-color: #FFFFFF !default;
+
 	.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;
+		border-top-right-radius: $u-badge-dot-radius;
+		border-top-left-radius: $u-badge-dot-radius;
+		border-bottom-left-radius: $u-badge-dot-radius;
+		border-bottom-right-radius: $u-badge-dot-radius;
+		@include flex;
+		line-height: $u-badge-text-font-size;
+		text-align: $u-badge-text-align;
+		font-size: $u-badge-text-font-size;
+		color: $u-badge-text-color;
+
+		&--dot {
+			height: $u-badge-dot-size;
+			width: $u-badge-dot-size;
+		}
 		
-		&--bg--primary {
-			background-color: $u-type-primary;
+		&--inverted {
+			font-size: 13px;
 		}
 		
-		&--bg--error {
-			background-color: $u-type-error;
+		&--not-dot {
+			padding: $u-badge-text-padding;
+		}
+
+		&--horn {
+			border-bottom-left-radius: 0;
+		}
+
+		&--primary {
+			background-color: $u-badge-primary;
 		}
 		
-		&--bg--success {
-			background-color: $u-type-success;
+		&--primary--inverted {
+			color: $u-badge-primary;
+		}
+
+		&--error {
+			background-color: $u-badge-error;
 		}
 		
-		&--bg--info {
-			background-color: $u-type-info;
+		&--error--inverted {
+			color: $u-badge-error;
+		}
+
+		&--success {
+			background-color: $u-badge-success;
 		}
 		
-		&--bg--warning {
-			background-color: $u-type-warning;
+		&--success--inverted {
+			color: $u-badge-success;
+		}
+
+		&--info {
+			background-color: $u-badge-info;
+		}
+		
+		&--info--inverted {
+			color: $u-badge-info;
+		}
+
+		&--warning {
+			background-color: $u-badge-warning;
+		}
+		
+		&--warning--inverted {
+			color: $u-badge-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>
+</style>

+ 46 - 0
uview-ui/components/u-button/nvue.scss

@@ -0,0 +1,46 @@
+$u-button-active-opacity:0.75 !default;
+$u-button-loading-text-margin-left:4px !default;
+$u-button-text-color: #FFFFFF !default;
+$u-button-text-plain-error-color:$u-error !default;
+$u-button-text-plain-warning-color:$u-warning !default;
+$u-button-text-plain-success-color:$u-success !default;
+$u-button-text-plain-info-color:$u-info !default;
+$u-button-text-plain-primary-color:$u-primary !default;
+.u-button {
+	&--active {
+		opacity: $u-button-active-opacity;
+	}
+	
+	&--active--plain {
+		background-color: rgb(217, 217, 217);
+	}
+	
+	&__loading-text {
+		margin-left:$u-button-loading-text-margin-left;
+	}
+	
+	&__text,
+	&__loading-text {
+		color:$u-button-text-color;
+	}
+	
+	&__text--plain--error {
+		color:$u-button-text-plain-error-color;
+	}
+	
+	&__text--plain--warning {
+		color:$u-button-text-plain-warning-color;
+	}
+	
+	&__text--plain--success{
+		color:$u-button-text-plain-success-color;
+	}
+	
+	&__text--plain--info {
+		color:$u-button-text-plain-info-color;
+	}
+	
+	&__text--plain--primary {
+		color:$u-button-text-plain-primary-color;
+	}
+}

+ 161 - 0
uview-ui/components/u-button/props.js

@@ -0,0 +1,161 @@
+/*
+ * @Author       : LQ
+ * @Description  :
+ * @version      : 1.0
+ * @Date         : 2021-08-16 10:04:04
+ * @LastAuthor   : LQ
+ * @lastTime     : 2021-08-16 10:04:24
+ * @FilePath     : /u-view2.0/uview-ui/components/u-button/props.js
+ */
+export default {
+    props: {
+        // 是否细边框
+        hairline: {
+            type: Boolean,
+            default: uni.$u.props.button.hairline
+        },
+        // 按钮的预置样式,info,primary,error,warning,success
+        type: {
+            type: String,
+            default: uni.$u.props.button.type
+        },
+        // 按钮尺寸,large,normal,small,mini
+        size: {
+            type: String,
+            default: uni.$u.props.button.size
+        },
+        // 按钮形状,circle(两边为半圆),square(带圆角)
+        shape: {
+            type: String,
+            default: uni.$u.props.button.shape
+        },
+        // 按钮是否镂空
+        plain: {
+            type: Boolean,
+            default: uni.$u.props.button.plain
+        },
+        // 是否禁止状态
+        disabled: {
+            type: Boolean,
+            default: uni.$u.props.button.disabled
+        },
+        // 是否加载中
+        loading: {
+            type: Boolean,
+            default: uni.$u.props.button.loading
+        },
+        // 加载中提示文字
+        loadingText: {
+            type: [String, Number],
+            default: uni.$u.props.button.loadingText
+        },
+        // 加载状态图标类型
+        loadingMode: {
+            type: String,
+            default: uni.$u.props.button.loadingMode
+        },
+        // 加载图标大小
+        loadingSize: {
+            type: [String, Number],
+            default: uni.$u.props.button.loadingSize
+        },
+        // 开放能力,具体请看uniapp稳定关于button组件部分说明
+        // https://uniapp.dcloud.io/component/button
+        openType: {
+            type: String,
+            default: uni.$u.props.button.openType
+        },
+        // 用于 <form> 组件,点击分别会触发 <form> 组件的 submit/reset 事件
+        // 取值为submit(提交表单),reset(重置表单)
+        formType: {
+            type: String,
+            default: uni.$u.props.button.formType
+        },
+        // 打开 APP 时,向 APP 传递的参数,open-type=launchApp时有效
+        // 只微信小程序、QQ小程序有效
+        appParameter: {
+            type: String,
+            default: uni.$u.props.button.appParameter
+        },
+        // 指定是否阻止本节点的祖先节点出现点击态,微信小程序有效
+        hoverStopPropagation: {
+            type: Boolean,
+            default: uni.$u.props.button.hoverStopPropagation
+        },
+        // 指定返回用户信息的语言,zh_CN 简体中文,zh_TW 繁体中文,en 英文。只微信小程序有效
+        lang: {
+            type: String,
+            default: uni.$u.props.button.lang
+        },
+        // 会话来源,open-type="contact"时有效。只微信小程序有效
+        sessionFrom: {
+            type: String,
+            default: uni.$u.props.button.sessionFrom
+        },
+        // 会话内消息卡片标题,open-type="contact"时有效
+        // 默认当前标题,只微信小程序有效
+        sendMessageTitle: {
+            type: String,
+            default: uni.$u.props.button.sendMessageTitle
+        },
+        // 会话内消息卡片点击跳转小程序路径,open-type="contact"时有效
+        // 默认当前分享路径,只微信小程序有效
+        sendMessagePath: {
+            type: String,
+            default: uni.$u.props.button.sendMessagePath
+        },
+        // 会话内消息卡片图片,open-type="contact"时有效
+        // 默认当前页面截图,只微信小程序有效
+        sendMessageImg: {
+            type: String,
+            default: uni.$u.props.button.sendMessageImg
+        },
+        // 是否显示会话内消息卡片,设置此参数为 true,用户进入客服会话会在右下角显示"可能要发送的小程序"提示,
+        // 用户点击后可以快速发送小程序消息,open-type="contact"时有效
+        showMessageCard: {
+            type: Boolean,
+            default: uni.$u.props.button.showMessageCard
+        },
+        // 额外传参参数,用于小程序的data-xxx属性,通过target.dataset.name获取
+        dataName: {
+            type: String,
+            default: uni.$u.props.button.dataName
+        },
+        // 节流,一定时间内只能触发一次
+        throttleTime: {
+            type: [String, Number],
+            default: uni.$u.props.button.throttleTime
+        },
+        // 按住后多久出现点击态,单位毫秒
+        hoverStartTime: {
+            type: [String, Number],
+            default: uni.$u.props.button.hoverStartTime
+        },
+        // 手指松开后点击态保留时间,单位毫秒
+        hoverStayTime: {
+            type: [String, Number],
+            default: uni.$u.props.button.hoverStayTime
+        },
+        // 按钮文字,之所以通过props传入,是因为slot传入的话
+        // nvue中无法控制文字的样式
+        text: {
+            type: [String, Number],
+            default: uni.$u.props.button.text
+        },
+        // 按钮图标
+        icon: {
+            type: String,
+            default: uni.$u.props.button.icon
+        },
+        // 按钮图标
+        iconColor: {
+            type: String,
+            default: uni.$u.props.button.icon
+        },
+        // 按钮颜色,支持传入linear-gradient渐变色
+        color: {
+            type: String,
+            default: uni.$u.props.button.color
+        }
+    }
+}

+ 452 - 558
uview-ui/components/u-button/u-button.vue

@@ -1,596 +1,490 @@
 <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>
+    <!-- #ifndef APP-NVUE -->
+    <button
+        :hover-start-time="Number(hoverStartTime)"
+        :hover-stay-time="Number(hoverStayTime)"
+        :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"
+        :hover-class="!disabled && !loading ? 'u-button--active' : ''"
+        class="u-button u-reset-button"
+        :style="[baseColor, $u.addStyle(customStyle)]"
+        @tap="clickHandler"
+        :class="bemClass"
+    >
+        <template v-if="loading">
+            <u-loading-icon
+                :mode="loadingMode"
+                :size="loadingSize * 1.15"
+                :color="loadingColor"
+            ></u-loading-icon>
+            <text
+                class="u-button__loading-text"
+                :style="[{ fontSize: textSize + 'px' }]"
+                >{{ loadingText || text }}</text
+            >
+        </template>
+        <template v-else>
+            <u-icon
+                v-if="icon"
+                :name="icon"
+                :color="iconColorCom"
+                :size="textSize * 1.35"
+                :customStyle="{ marginRight: '2px' }"
+            ></u-icon>
+            <slot>
+                <text
+                    class="u-button__text"
+                    :style="[{ fontSize: textSize + 'px' }]"
+                    >{{ text }}</text
+                >
+            </slot>
+        </template>
+    </button>
+    <!-- #endif -->
+
+    <!-- #ifdef APP-NVUE -->
+    <view
+        :hover-start-time="Number(hoverStartTime)"
+        :hover-stay-time="Number(hoverStayTime)"
+        class="u-button"
+        :hover-class="
+            !disabled && !loading && !color && (plain || type === 'info')
+                ? 'u-button--active--plain'
+                : !disabled && !loading && !plain
+                ? 'u-button--active'
+                : ''
+        "
+        @tap="clickHandler"
+        :class="bemClass"
+        :style="[baseColor, $u.addStyle(customStyle)]"
+    >
+        <template v-if="loading">
+            <u-loading-icon
+                :mode="loadingMode"
+                :size="loadingSize * 1.15"
+                :color="loadingColor"
+            ></u-loading-icon>
+            <text
+                class="u-button__loading-text"
+                :style="[nvueTextStyle]"
+                :class="[plain && `u-button__text--plain--${type}`]"
+                >{{ loadingText || text }}</text
+            >
+        </template>
+        <template v-else>
+            <u-icon
+                v-if="icon"
+                :name="icon"
+                :color="iconColorCom"
+                :size="textSize * 1.35"
+            ></u-icon>
+            <text
+                class="u-button__text"
+                :style="[
+                    {
+                        marginLeft: icon ? '2px' : 0,
+                    },
+                    nvueTextStyle,
+                ]"
+                :class="[plain && `u-button__text--plain--${type}`]"
+                >{{ text }}</text
+            >
+        </template>
+    </view>
+    <!-- #endif -->
 </template>
 
 <script>
+import button from "../../libs/mixin/button.js";
+import openType from "../../libs/mixin/openType.js";
+import props from "./props.js";
 /**
  * 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 成功的回调
+ *
+ * @property {Boolean}			hairline				是否显示按钮的细边框 (默认 true )
+ * @property {String}			type					按钮的预置样式,info,primary,error,warning,success (默认 'info' )
+ * @property {String}			size					按钮尺寸,large,normal,mini (默认 normal)
+ * @property {String}			shape					按钮形状,circle(两边为半圆),square(带圆角) (默认 'square' )
+ * @property {Boolean}			plain					按钮是否镂空,背景色透明 (默认 false)
+ * @property {Boolean}			disabled				是否禁用 (默认 false)
+ * @property {Boolean}			loading					按钮名称前是否带 loading 图标(App-nvue 平台,在 ios 上为雪花,Android上为圆圈) (默认 false)
+ * @property {String | Number}	loadingText				加载中提示文字
+ * @property {String}			loadingMode				加载状态图标类型 (默认 'spinner' )
+ * @property {String | Number}	loadingSize				加载图标大小 (默认 15 )
+ * @property {String}			openType				开放能力,具体请看uniapp稳定关于button组件部分说明
+ * @property {String}			formType				用于 <form> 组件,点击分别会触发 <form> 组件的 submit/reset 事件
+ * @property {String}			appParameter			打开 APP 时,向 APP 传递的参数,open-type=launchApp时有效 (注:只微信小程序、QQ小程序有效)
+ * @property {Boolean}			hoverStopPropagation	指定是否阻止本节点的祖先节点出现点击态,微信小程序有效(默认 true )
+ * @property {String}			lang					指定返回用户信息的语言,zh_CN 简体中文,zh_TW 繁体中文,en 英文(默认 en )
+ * @property {String}			sessionFrom				会话来源,openType="contact"时有效
+ * @property {String}			sendMessageTitle		会话内消息卡片标题,openType="contact"时有效
+ * @property {String}			sendMessagePath			会话内消息卡片点击跳转小程序路径,openType="contact"时有效
+ * @property {String}			sendMessageImg			会话内消息卡片图片,openType="contact"时有效
+ * @property {Boolean}			showMessageCard			是否显示会话内消息卡片,设置此参数为 true,用户进入客服会话会在右下角显示"可能要发送的小程序"提示,用户点击后可以快速发送小程序消息,openType="contact"时有效(默认false)
+ * @property {String}			dataName				额外传参参数,用于小程序的data-xxx属性,通过target.dataset.name获取
+ * @property {String | Number}	throttleTime			节流,一定时间内只能触发一次 (默认 0 )
+ * @property {String | Number}	hoverStartTime			按住后多久出现点击态,单位毫秒 (默认 0 )
+ * @property {String | Number}	hoverStayTime			手指松开后点击态保留时间,单位毫秒 (默认 200 )
+ * @property {String | Number}	text					按钮文字,之所以通过props传入,是因为slot传入的话(注:nvue中无法控制文字的样式)
+ * @property {String}			icon					按钮图标
+ * @property {String}			iconColor				按钮图标颜色
+ * @property {String}			color					按钮颜色,支持传入linear-gradient渐变色
+ * @property {Object}			customStyle				定义需要用到的外部样式
+ *
+ * @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);
-		}
-	}
+    name: "u-button",
+    // #ifdef MP
+    mixins: [uni.$u.mpMixin, uni.$u.mixin, button, openType, props],
+    // #endif
+    // #ifndef MP
+    mixins: [uni.$u.mpMixin, uni.$u.mixin, props],
+    // #endif
+    data() {
+        return {};
+    },
+    computed: {
+        // 生成bem风格的类名
+        bemClass() {
+            // this.bem为一个computed变量,在mixin中
+            if (!this.color) {
+                return this.bem(
+                    "button",
+                    ["type", "shape", "size"],
+                    ["disabled", "plain", "hairline"]
+                );
+            } else {
+                // 由于nvue的原因,在有color参数时,不需要传入type,否则会生成type相关的类型,影响最终的样式
+                return this.bem(
+                    "button",
+                    ["shape", "size"],
+                    ["disabled", "plain", "hairline"]
+                );
+            }
+        },
+        loadingColor() {
+            if (this.plain) {
+                // 如果有设置color值,则用color值,否则使用type主题颜色
+                return this.color
+                    ? this.color
+                    : uni.$u.config.color[`u-${this.type}`];
+            }
+            if (this.type === "info") {
+                return "#c9c9c9";
+            }
+            return "rgb(200, 200, 200)";
+        },
+        iconColorCom() {
+            // 如果是镂空状态,设置了color就用color值,否则使用主题颜色,
+            // u-icon的color能接受一个主题颜色的值
+			if (this.iconColor) return this.iconColor;
+			if (this.plain) {
+                return this.color ? this.color : this.type;
+            } else {
+                return this.type === "info" ? "#000000" : "#ffffff";
+            }
+        },
+        baseColor() {
+            let style = {};
+            if (this.color) {
+                // 针对自定义了color颜色的情况,镂空状态下,就是用自定义的颜色
+                style.color = this.plain ? this.color : "white";
+                if (!this.plain) {
+                    // 非镂空,背景色使用自定义的颜色
+                    style["background-color"] = this.color;
+                }
+                if (this.color.indexOf("gradient") !== -1) {
+                    // 如果自定义的颜色为渐变色,不显示边框,以及通过backgroundImage设置渐变色
+                    // weex文档说明可以写borderWidth的形式,为什么这里需要分开写?
+                    // 因为weex是阿里巴巴为了部门业绩考核而做的你懂的东西,所以需要这么写才有效
+                    style.borderTopWidth = 0;
+                    style.borderRightWidth = 0;
+                    style.borderBottomWidth = 0;
+                    style.borderLeftWidth = 0;
+                    if (!this.plain) {
+                        style.backgroundImage = this.color;
+                    }
+                } else {
+                    // 非渐变色,则设置边框相关的属性
+                    style.borderColor = this.color;
+                    style.borderWidth = "1px";
+                    style.borderStyle = "solid";
+                }
+            }
+            return style;
+        },
+        // nvue版本按钮的字体不会继承父组件的颜色,需要对每一个text组件进行单独的设置
+        nvueTextStyle() {
+            let style = {};
+            // 针对自定义了color颜色的情况,镂空状态下,就是用自定义的颜色
+            if (this.type === "info") {
+                style.color = "#323233";
+            }
+            if (this.color) {
+                style.color = this.plain ? this.color : "white";
+            }
+            style.fontSize = this.textSize + "px";
+            return style;
+        },
+        // 字体大小
+        textSize() {
+            let fontSize = 14,
+                { size } = this;
+            if (size === "large") fontSize = 16;
+            if (size === "normal") fontSize = 14;
+            if (size === "small") fontSize = 12;
+            if (size === "mini") fontSize = 10;
+            return fontSize;
+        },
+    },
+    methods: {
+        clickHandler() {
+            // 非禁止并且非加载中,才能点击
+            if (!this.disabled && !this.loading) {
+				// 进行节流控制,每this.throttle毫秒内,只在开始处执行
+				uni.$u.throttle(() => {
+					this.$emit("click");
+				}, this.throttleTime);
+            }
+        },
+        // 下面为对接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;
-}
+<style lang="scss" scoped>
+@import "../../libs/css/components.scss";
 
-.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;
-	}
-}
+/* #ifndef APP-NVUE */
+@import "./vue.scss";
+/* #endif */
 
-.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;
-}
+/* #ifdef APP-NVUE */
+@import "./nvue.scss";
+/* #endif */
 
-.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-button-u-button-height: 40px !default;
+$u-button-text-font-size: 15px !default;
+$u-button-loading-text-font-size: 15px !default;
+$u-button-loading-text-margin-left: 4px !default;
+$u-button-large-width: 100% !default;
+$u-button-large-height: 50px !default;
+$u-button-normal-padding: 0 12px !default;
+$u-button-large-padding: 0 15px !default;
+$u-button-normal-font-size: 14px !default;
+$u-button-small-min-width: 60px !default;
+$u-button-small-height: 30px !default;
+$u-button-small-padding: 0px 8px !default;
+$u-button-mini-padding: 0px 8px !default;
+$u-button-small-font-size: 12px !default;
+$u-button-mini-height: 22px !default;
+$u-button-mini-font-size: 10px !default;
+$u-button-mini-min-width: 50px !default;
+$u-button-disabled-opacity: 0.5 !default;
+$u-button-info-color: #323233 !default;
+$u-button-info-background-color: #fff !default;
+$u-button-info-border-color: #ebedf0 !default;
+$u-button-info-border-width: 1px !default;
+$u-button-info-border-style: solid !default;
+$u-button-success-color: #fff !default;
+$u-button-success-background-color: $u-success !default;
+$u-button-success-border-color: $u-button-success-background-color !default;
+$u-button-success-border-width: 1px !default;
+$u-button-success-border-style: solid !default;
+$u-button-primary-color: #fff !default;
+$u-button-primary-background-color: $u-primary !default;
+$u-button-primary-border-color: $u-button-primary-background-color !default;
+$u-button-primary-border-width: 1px !default;
+$u-button-primary-border-style: solid !default;
+$u-button-error-color: #fff !default;
+$u-button-error-background-color: $u-error !default;
+$u-button-error-border-color: $u-button-error-background-color !default;
+$u-button-error-border-width: 1px !default;
+$u-button-error-border-style: solid !default;
+$u-button-warning-color: #fff !default;
+$u-button-warning-background-color: $u-warning !default;
+$u-button-warning-border-color: $u-button-warning-background-color !default;
+$u-button-warning-border-width: 1px !default;
+$u-button-warning-border-style: solid !default;
+$u-button-block-width: 100% !default;
+$u-button-circle-border-top-right-radius: 100px !default;
+$u-button-circle-border-top-left-radius: 100px !default;
+$u-button-circle-border-bottom-left-radius: 100px !default;
+$u-button-circle-border-bottom-right-radius: 100px !default;
+$u-button-square-border-top-right-radius: 3px !default;
+$u-button-square-border-top-left-radius: 3px !default;
+$u-button-square-border-bottom-left-radius: 3px !default;
+$u-button-square-border-bottom-right-radius: 3px !default;
+$u-button-icon-min-width: 1em !default;
+$u-button-plain-background-color: #fff !default;
+$u-button-hairline-border-width: 0.5px !default;
 
-.u-wave-ripple.u-wave-active {
-	opacity: 0;
-	transform: scale(2);
-	transition: opacity 1s linear, transform 0.4s linear;
-}
+.u-button {
+    height: $u-button-u-button-height;
+    position: relative;
+    align-items: center;
+    justify-content: center;
+    @include flex;
+    /* #ifndef APP-NVUE */
+    box-sizing: border-box;
+    /* #endif */
+    flex-direction: row;
 
-.u-round-circle {
-	border-radius: 100rpx;
-}
+    &__text {
+        font-size: $u-button-text-font-size;
+    }
 
-.u-round-circle::after {
-	border-radius: 100rpx;
-}
+    &__loading-text {
+        font-size: $u-button-loading-text-font-size;
+        margin-left: $u-button-loading-text-margin-left;
+    }
 
-.u-loading::after {
-	background-color: hsla(0, 0%, 100%, 0.35);
-}
+    &--large {
+        /* #ifndef APP-NVUE */
+        width: $u-button-large-width;
+        /* #endif */
+        height: $u-button-large-height;
+        padding: $u-button-large-padding;
+    }
 
-.u-size-default {
-	font-size: 30rpx;
-	height: 80rpx;
-	line-height: 80rpx;
-}
+    &--normal {
+        padding: $u-button-normal-padding;
+        font-size: $u-button-normal-font-size;
+    }
 
-.u-size-medium {
-	/* #ifndef APP-NVUE */
-	display: inline-flex;		
-	/* #endif */
-	width: auto;
-	font-size: 26rpx;
-	height: 70rpx;
-	line-height: 70rpx;
-	padding: 0 80rpx;
-}
+    &--small {
+        /* #ifndef APP-NVUE */
+        min-width: $u-button-small-min-width;
+        /* #endif */
+        height: $u-button-small-height;
+        padding: $u-button-small-padding;
+        font-size: $u-button-small-font-size;
+    }
 
-.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;
-}
+    &--mini {
+        height: $u-button-mini-height;
+        font-size: $u-button-mini-font-size;
+        /* #ifndef APP-NVUE */
+        min-width: $u-button-mini-min-width;
+        /* #endif */
+        padding: $u-button-mini-padding;
+    }
 
-.u-primary-plain-hover {
-	color: #ffffff !important;
-	background: $u-type-primary-dark !important;
-}
+    &--disabled {
+        opacity: $u-button-disabled-opacity;
+    }
 
-.u-default-plain-hover {
-	color: $u-type-primary-dark !important;
-	background: $u-type-primary-light !important;
-}
+    &--info {
+        color: $u-button-info-color;
+        background-color: $u-button-info-background-color;
+        border-color: $u-button-info-border-color;
+        border-width: $u-button-info-border-width;
+        border-style: $u-button-info-border-style;
+    }
 
-.u-success-plain-hover {
-	color: #ffffff !important;
-	background: $u-type-success-dark !important;
-}
+    &--success {
+        color: $u-button-success-color;
+        background-color: $u-button-success-background-color;
+        border-color: $u-button-success-border-color;
+        border-width: $u-button-success-border-width;
+        border-style: $u-button-success-border-style;
+    }
 
-.u-warning-plain-hover {
-	color: #ffffff !important;
-	background: $u-type-warning-dark !important;
-}
+    &--primary {
+        color: $u-button-primary-color;
+        background-color: $u-button-primary-background-color;
+        border-color: $u-button-primary-border-color;
+        border-width: $u-button-primary-border-width;
+        border-style: $u-button-primary-border-style;
+    }
 
-.u-error-plain-hover {
-	color: #ffffff !important;
-	background: $u-type-error-dark !important;
-}
+    &--error {
+        color: $u-button-error-color;
+        background-color: $u-button-error-background-color;
+        border-color: $u-button-error-border-color;
+        border-width: $u-button-error-border-width;
+        border-style: $u-button-error-border-style;
+    }
 
-.u-info-plain-hover {
-	color: #ffffff !important;
-	background: $u-type-info-dark !important;
-}
+    &--warning {
+        color: $u-button-warning-color;
+        background-color: $u-button-warning-background-color;
+        border-color: $u-button-warning-border-color;
+        border-width: $u-button-warning-border-width;
+        border-style: $u-button-warning-border-style;
+    }
 
-.u-default-hover {
-	color: $u-type-primary-dark !important;
-	border-color: $u-type-primary-dark !important;
-	background-color: $u-type-primary-light !important;
-}
+    &--block {
+        @include flex;
+        width: $u-button-block-width;
+    }
 
-.u-primary-hover {
-	background: $u-type-primary-dark !important;
-	color: #fff;
-}
+    &--circle {
+        border-top-right-radius: $u-button-circle-border-top-right-radius;
+        border-top-left-radius: $u-button-circle-border-top-left-radius;
+        border-bottom-left-radius: $u-button-circle-border-bottom-left-radius;
+        border-bottom-right-radius: $u-button-circle-border-bottom-right-radius;
+    }
 
-.u-success-hover {
-	background: $u-type-success-dark !important;
-	color: #fff;
-}
+    &--square {
+        border-bottom-left-radius: $u-button-square-border-top-right-radius;
+        border-bottom-right-radius: $u-button-square-border-top-left-radius;
+        border-top-left-radius: $u-button-square-border-bottom-left-radius;
+        border-top-right-radius: $u-button-square-border-bottom-right-radius;
+    }
 
-.u-info-hover {
-	background: $u-type-info-dark !important;
-	color: #fff;
-}
+    &__icon {
+        /* #ifndef APP-NVUE */
+        min-width: $u-button-icon-min-width;
+        line-height: inherit !important;
+        vertical-align: top;
+        /* #endif */
+    }
 
-.u-warning-hover {
-	background: $u-type-warning-dark !important;
-	color: #fff;
-}
+    &--plain {
+        background-color: $u-button-plain-background-color;
+    }
 
-.u-error-hover {
-	background: $u-type-error-dark !important;
-	color: #fff;
+    &--hairline {
+        border-width: $u-button-hairline-border-width !important;
+    }
 }
 </style>

+ 80 - 0
uview-ui/components/u-button/vue.scss

@@ -0,0 +1,80 @@
+// nvue下hover-class无效
+$u-button-before-top:50% !default;
+$u-button-before-left:50% !default;
+$u-button-before-width:100% !default;
+$u-button-before-height:100% !default;
+$u-button-before-transform:translate(-50%, -50%) !default;
+$u-button-before-opacity:0 !default;
+$u-button-before-background-color:#000 !default;
+$u-button-before-border-color:#000 !default;
+$u-button-active-before-opacity:.15 !default;
+$u-button-icon-margin-left:4px !default;
+$u-button-plain-u-button-info-color:$u-info;
+$u-button-plain-u-button-success-color:$u-success;
+$u-button-plain-u-button-error-color:$u-error;
+$u-button-plain-u-button-warning-color:$u-error;
+
+.u-button {
+	width: 100%;
+	
+	&__text {
+		white-space: nowrap;
+		line-height: 1;
+	}
+	
+	&:before {
+		position: absolute;
+		top:$u-button-before-top;
+		left:$u-button-before-left;
+		width:$u-button-before-width;
+		height:$u-button-before-height;
+		border: inherit;
+		border-radius: inherit;
+		transform:$u-button-before-transform;
+		opacity:$u-button-before-opacity;
+		content: " ";
+		background-color:$u-button-before-background-color;
+		border-color:$u-button-before-border-color;
+	}
+	
+	&--active {
+		&:before {
+			opacity: .15
+		}
+	}
+	
+	&__icon+&__text:not(:empty),
+	&__loading-text {
+		margin-left:$u-button-icon-margin-left;
+	}
+	
+	&--plain {
+		&.u-button--primary {
+			color: $u-primary;
+		}
+	}
+	
+	&--plain {
+		&.u-button--info {
+			color:$u-button-plain-u-button-info-color;
+		}
+	}
+	
+	&--plain {
+		&.u-button--success {
+			color:$u-button-plain-u-button-success-color;
+		}
+	}
+	
+	&--plain {
+		&.u-button--error {
+			color:$u-button-plain-u-button-error-color;
+		}
+	}
+	
+	&--plain {
+		&.u-button--warning {
+			color:$u-button-plain-u-button-warning-color;
+		}
+	}
+}

+ 99 - 0
uview-ui/components/u-calendar/header.vue

@@ -0,0 +1,99 @@
+<template>
+	<view class="u-calendar-header u-border-bottom">
+		<text
+			class="u-calendar-header__title"
+			v-if="showTitle"
+		>{{ title }}</text>
+		<text
+			class="u-calendar-header__subtitle"
+			v-if="showSubtitle"
+		>{{ subtitle }}</text>
+		<view class="u-calendar-header__weekdays">
+			<text class="u-calendar-header__weekdays__weekday">一</text>
+			<text class="u-calendar-header__weekdays__weekday">二</text>
+			<text class="u-calendar-header__weekdays__weekday">三</text>
+			<text class="u-calendar-header__weekdays__weekday">四</text>
+			<text class="u-calendar-header__weekdays__weekday">五</text>
+			<text class="u-calendar-header__weekdays__weekday">六</text>
+			<text class="u-calendar-header__weekdays__weekday">日</text>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		name: 'u-calendar-header',
+		mixins: [uni.$u.mpMixin, uni.$u.mixin],
+		props: {
+			// 标题
+			title: {
+				type: String,
+				default: ''
+			},
+			// 副标题
+			subtitle: {
+				type: String,
+				default: ''
+			},
+			// 是否显示标题
+			showTitle: {
+				type: Boolean,
+				default: true
+			},
+			// 是否显示副标题
+			showSubtitle: {
+				type: Boolean,
+				default: true
+			},
+		},
+		data() {
+			return {
+
+			}
+		},
+		methods: {
+			name() {
+
+			}
+		},
+	}
+</script>
+
+<style lang="scss" scoped>
+	@import "../../libs/css/components.scss";
+
+	.u-calendar-header {
+		padding-bottom: 4px;
+
+		&__title {
+			font-size: 16px;
+			color: $u-main-color;
+			text-align: center;
+			height: 42px;
+			line-height: 42px;
+			font-weight: bold;
+		}
+
+		&__subtitle {
+			font-size: 14px;
+			color: $u-main-color;
+			height: 40px;
+			text-align: center;
+			line-height: 40px;
+			font-weight: bold;
+		}
+
+		&__weekdays {
+			@include flex;
+			justify-content: space-between;
+
+			&__weekday {
+				font-size: 13px;
+				color: $u-main-color;
+				line-height: 30px;
+				flex: 1;
+				text-align: center;
+			}
+		}
+	}
+</style>

+ 579 - 0
uview-ui/components/u-calendar/month.vue

@@ -0,0 +1,579 @@
+<template>
+	<view class="u-calendar-month-wrapper" ref="u-calendar-month-wrapper">
+		<view v-for="(item, index) in months" :key="index" :class="[`u-calendar-month-${index}`]"
+			:ref="`u-calendar-month-${index}`" :id="`month-${index}`">
+			<text v-if="index !== 0" class="u-calendar-month__title">{{ item.year }}年{{ item.month }}月</text>
+			<view class="u-calendar-month__days">
+				<view v-if="showMark" class="u-calendar-month__days__month-mark-wrapper">
+					<text class="u-calendar-month__days__month-mark-wrapper__text">{{ item.month }}</text>
+				</view>
+				<view class="u-calendar-month__days__day" v-for="(item1, index1) in item.date" :key="index1"
+					:style="[dayStyle(index, index1, item1)]" @tap="clickHandler(index, index1, item1)"
+					:class="[item1.selected && 'u-calendar-month__days__day__select--selected']">
+					<view class="u-calendar-month__days__day__select" :style="[daySelectStyle(index, index1, item1)]">
+						<text class="u-calendar-month__days__day__select__info"
+							:class="[item1.disabled && 'u-calendar-month__days__day__select__info--disabled']"
+							:style="[textStyle(item1)]">{{ item1.day }}</text>
+						<text v-if="getBottomInfo(index, index1, item1)"
+							class="u-calendar-month__days__day__select__buttom-info"
+							:class="[item1.disabled && 'u-calendar-month__days__day__select__buttom-info--disabled']"
+							:style="[textStyle(item1)]">{{ getBottomInfo(index, index1, item1) }}</text>
+						<text v-if="item1.dot" class="u-calendar-month__days__day__select__dot"></text>
+					</view>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	// #ifdef APP-NVUE
+	// 由于nvue不支持百分比单位,需要查询宽度来计算每个日期的宽度
+	const dom = uni.requireNativePlugin('dom')
+	// #endif
+	import dayjs from '../../libs/util/dayjs.js';
+	export default {
+		name: 'u-calendar-month',
+		mixins: [uni.$u.mpMixin, uni.$u.mixin],
+		props: {
+			// 是否显示月份背景色
+			showMark: {
+				type: Boolean,
+				default: true
+			},
+			// 主题色,对底部按钮和选中日期有效
+			color: {
+				type: String,
+				default: '#3c9cff'
+			},
+			// 月份数据
+			months: {
+				type: Array,
+				default: () => []
+			},
+			// 日期选择类型
+			mode: {
+				type: String,
+				default: 'single'
+			},
+			// 日期行高
+			rowHeight: {
+				type: [String, Number],
+				default: 58
+			},
+			// mode=multiple时,最多可选多少个日期
+			maxCount: {
+				type: [String, Number],
+				default: Infinity
+			},
+			// mode=range时,第一个日期底部的提示文字
+			startText: {
+				type: String,
+				default: '开始'
+			},
+			// mode=range时,最后一个日期底部的提示文字
+			endText: {
+				type: String,
+				default: '结束'
+			},
+			// 默认选中的日期,mode为multiple或range是必须为数组格式
+			defaultDate: {
+				type: [Array, String, Date],
+				default: null
+			},
+			// 最小的可选日期
+			minDate: {
+				type: [String, Number],
+				default: 0
+			},
+			// 最大可选日期
+			maxDate: {
+				type: [String, Number],
+				default: 0
+			},
+			// 如果没有设置maxDate,则往后推多少个月
+			maxMonth: {
+				type: [String, Number],
+				default: 2
+			},
+			// 是否为只读状态,只读状态下禁止选择日期
+			readonly: {
+				type: Boolean,
+				default: uni.$u.props.calendar.readonly
+			},
+			// 日期区间最多可选天数,默认无限制,mode = range时有效
+			maxRange: {
+				type: [Number, String],
+				default: Infinity
+			},
+			// 范围选择超过最多可选天数时的提示文案,mode = range时有效
+			rangePrompt: {
+				type: String,
+				default: ''
+			},
+			// 范围选择超过最多可选天数时,是否展示提示文案,mode = range时有效
+			showRangePrompt: {
+				type: Boolean,
+				default: true
+			},
+			// 是否允许日期范围的起止时间为同一天,mode = range时有效
+			allowSameDay: {
+				type: Boolean,
+				default: false
+			}
+		},
+		data() {
+			return {
+				// 每个日期的宽度
+				width: 0,
+				// 当前选中的日期item
+				item: {},
+				selected: []
+			}
+		},
+		watch: {
+			selectedChange: {
+				immediate: true,
+				handler(n) {
+					this.setDefaultDate()
+				}
+			}
+		},
+		computed: {
+			// 多个条件的变化,会引起选中日期的变化,这里统一管理监听
+			selectedChange() {
+				return [this.minDate, this.maxDate, this.defaultDate]
+			},
+			dayStyle(index1, index2, item) {
+				return (index1, index2, item) => {
+					const style = {}
+					let week = item.week
+					// 不进行四舍五入的形式保留2位小数
+					const dayWidth = Number(parseFloat(this.width / 7).toFixed(3).slice(0, -1))
+					// 得出每个日期的宽度
+					// #ifdef APP-NVUE
+					style.width = uni.$u.addUnit(dayWidth)
+					// #endif
+					style.height = uni.$u.addUnit(this.rowHeight)
+					if (index2 === 0) {
+						// 获取当前为星期几,如果为0,则为星期天,减一为每月第一天时,需要向左偏移的item个数
+						week = (week === 0 ? 7 : week) - 1
+						style.marginLeft = uni.$u.addUnit(week * dayWidth)
+					}
+					if (this.mode === 'range') {
+						// 之所以需要这么写,是因为DCloud公司的iOS客户端的开发者能力有限导致的bug
+						style.paddingLeft = 0
+						style.paddingRight = 0
+						style.paddingBottom = 0
+						style.paddingTop = 0
+					}
+					return style
+				}
+			},
+			daySelectStyle() {
+				return (index1, index2, item) => {
+					let date = dayjs(item.date).format("YYYY-MM-DD"),
+						style = {}
+					// 判断date是否在selected数组中,因为月份可能会需要补0,所以使用dateSame判断,而不用数组的includes判断
+					if (this.selected.some(item => this.dateSame(item, date))) {
+						style.backgroundColor = this.color
+					}
+					if (this.mode === 'single') {
+						if (date === this.selected[0]) {
+							// 因为需要对nvue的兼容,只能这么写,无法缩写,也无法通过类名控制等等
+							style.borderTopLeftRadius = '3px'
+							style.borderBottomLeftRadius = '3px'
+							style.borderTopRightRadius = '3px'
+							style.borderBottomRightRadius = '3px'
+						}
+					} else if (this.mode === 'range') {
+						if (this.selected.length >= 2) {
+							const len = this.selected.length - 1
+							// 第一个日期设置左上角和左下角的圆角
+							if (this.dateSame(date, this.selected[0])) {
+								style.borderTopLeftRadius = '3px'
+								style.borderBottomLeftRadius = '3px'
+							}
+							// 最后一个日期设置右上角和右下角的圆角
+							if (this.dateSame(date, this.selected[len])) {
+								style.borderTopRightRadius = '3px'
+								style.borderBottomRightRadius = '3px'
+							}
+							// 处于第一和最后一个之间的日期,背景色设置为浅色,通过将对应颜色进行等分,再取其尾部的颜色值
+							if (dayjs(date).isAfter(dayjs(this.selected[0])) && dayjs(date).isBefore(dayjs(this
+									.selected[len]))) {
+								style.backgroundColor = uni.$u.colorGradient(this.color, '#ffffff', 100)[90]
+								// 增加一个透明度,让范围区间的背景色也能看到底部的mark水印字符
+								style.opacity = 0.7
+							}
+						} else if (this.selected.length === 1) {
+							// 之所以需要这么写,是因为DCloud公司的iOS客户端的开发者能力有限导致的bug
+							// 进行还原操作,否则在nvue的iOS,uni-app有bug,会导致诡异的表现
+							style.borderTopLeftRadius = '3px'
+							style.borderBottomLeftRadius = '3px'
+						}
+					} else {
+						if (this.selected.some(item => this.dateSame(item, date))) {
+							style.borderTopLeftRadius = '3px'
+							style.borderBottomLeftRadius = '3px'
+							style.borderTopRightRadius = '3px'
+							style.borderBottomRightRadius = '3px'
+						}
+					}
+					return style
+				}
+			},
+			// 某个日期是否被选中
+			textStyle() {
+				return (item) => {
+					const date = dayjs(item.date).format("YYYY-MM-DD"),
+						style = {}
+					// 选中的日期,提示文字设置白色
+					if (this.selected.some(item => this.dateSame(item, date))) {
+						style.color = '#ffffff'
+					}
+					if (this.mode === 'range') {
+						const len = this.selected.length - 1
+						// 如果是范围选择模式,第一个和最后一个之间的日期,文字颜色设置为高亮的主题色
+						if (dayjs(date).isAfter(dayjs(this.selected[0])) && dayjs(date).isBefore(dayjs(this
+								.selected[len]))) {
+							style.color = this.color
+						}
+					}
+					return style
+				}
+			},
+			// 获取底部的提示文字
+			getBottomInfo() {
+				return (index1, index2, item) => {
+					const date = dayjs(item.date).format("YYYY-MM-DD")
+					const bottomInfo = item.bottomInfo
+					// 当为日期范围模式时,且选择的日期个数大于0时
+					if (this.mode === 'range' && this.selected.length > 0) {
+						if (this.selected.length === 1) {
+							// 选择了一个日期时,如果当前日期为数组中的第一个日期,则显示底部文字为“开始”
+							if (this.dateSame(date, this.selected[0])) return this.startText
+							else return bottomInfo
+						} else {
+							const len = this.selected.length - 1
+							// 如果数组中的日期大于2个时,第一个和最后一个显示为开始和结束日期
+							if (this.dateSame(date, this.selected[0]) && this.dateSame(date, this.selected[1]) &&
+								len === 1) {
+								// 如果长度为2,且第一个等于第二个日期,则提示语放在同一个item中
+								return `${this.startText}/${this.endText}`
+							} else if (this.dateSame(date, this.selected[0])) {
+								return this.startText
+							} else if (this.dateSame(date, this.selected[len])) {
+								return this.endText
+							} else {
+								return bottomInfo
+							}
+						}
+					} else {
+						return bottomInfo
+					}
+				}
+			}
+		},
+		mounted() {
+			this.init()
+		},
+		methods: {
+			init() {
+				// 初始化默认选中
+				this.$emit('monthSelected', this.selected)
+				this.$nextTick(() => {
+					// 这里需要另一个延时,因为获取宽度后,会进行月份数据渲染,只有渲染完成之后,才有真正的高度
+					// 因为nvue下,$nextTick并不是100%可靠的
+					uni.$u.sleep(10).then(() => {
+						this.getWrapperWidth()
+						this.getMonthRect()
+					})
+				})
+			},
+			// 判断两个日期是否相等
+			dateSame(date1, date2) {
+				return dayjs(date1).isSame(dayjs(date2))
+			},
+			// 获取月份数据区域的宽度,因为nvue不支持百分比,所以无法通过css设置每个日期item的宽度
+			getWrapperWidth() {
+				// #ifdef APP-NVUE
+				dom.getComponentRect(this.$refs['u-calendar-month-wrapper'], res => {
+					this.width = res.size.width
+				})
+				// #endif
+				// #ifndef APP-NVUE
+				this.$uGetRect('.u-calendar-month-wrapper').then(size => {
+					this.width = size.width
+				})
+				// #endif
+			},
+			getMonthRect() {
+				// 获取每个月份数据的尺寸,用于父组件在scroll-view滚动事件中,监听当前滚动到了第几个月份
+				const promiseAllArr = this.months.map((item, index) => this.getMonthRectByPromise(
+					`u-calendar-month-${index}`))
+				// 一次性返回
+				Promise.all(promiseAllArr).then(
+					sizes => {
+						let height = 1
+						const topArr = []
+						for (let i = 0; i < this.months.length; i++) {
+							// 添加到months数组中,供scroll-view滚动事件中,判断当前滚动到哪个月份
+							topArr[i] = height
+							height += sizes[i].height
+						}
+						// 由于微信下,无法通过this.months[i].top的形式(引用类型)去修改父组件的month的top值,所以使用事件形式对外发出
+						this.$emit('updateMonthTop', topArr)
+					})
+			},
+			// 获取每个月份区域的尺寸
+			getMonthRectByPromise(el) {
+				// #ifndef APP-NVUE
+				// $uGetRect为uView自带的节点查询简化方法,详见文档介绍:https://www.uviewui.com/js/getRect.html
+				// 组件内部一般用this.$uGetRect,对外的为uni.$u.getRect,二者功能一致,名称不同
+				return new Promise(resolve => {
+					this.$uGetRect(`.${el}`).then(size => {
+						resolve(size)
+					})
+				})
+				// #endif
+
+				// #ifdef APP-NVUE
+				// nvue下,使用dom模块查询元素高度
+				// 返回一个promise,让调用此方法的主体能使用then回调
+				return new Promise(resolve => {
+					dom.getComponentRect(this.$refs[el][0], res => {
+						resolve(res.size)
+					})
+				})
+				// #endif
+			},
+			// 点击某一个日期
+			clickHandler(index1, index2, item) {
+				if (this.readonly) {
+					return;
+				}
+				this.item = item
+				const date = dayjs(item.date).format("YYYY-MM-DD")
+				if (item.disabled) return
+				// 对上一次选择的日期数组进行深度克隆
+				let selected = uni.$u.deepClone(this.selected)
+				if (this.mode === 'single') {
+					// 单选情况下,让数组中的元素为当前点击的日期
+					selected = [date]
+				} else if (this.mode === 'multiple') {
+					if (selected.some(item => this.dateSame(item, date))) {
+						// 如果点击的日期已在数组中,则进行移除操作,也就是达到反选的效果
+						const itemIndex = selected.findIndex(item => item === date)
+						selected.splice(itemIndex, 1)
+					} else {
+						// 如果点击的日期不在数组中,且已有的长度小于总可选长度时,则添加到数组中去
+						if (selected.length < this.maxCount) selected.push(date)
+					}
+				} else {
+					// 选择区间形式
+					if (selected.length === 0 || selected.length >= 2) {
+						// 如果原来就为0或者大于2的长度,则当前点击的日期,就是开始日期
+						selected = [date]
+					} else if (selected.length === 1) {
+						// 如果已经选择了开始日期
+						const existsDate = selected[0]
+						// 如果当前选择的日期小于上一次选择的日期,则当前的日期定为开始日期
+						if (dayjs(date).isBefore(existsDate)) {
+							selected = [date]
+						} else if (dayjs(date).isAfter(existsDate)) {
+							// 当前日期减去最大可选的日期天数,如果大于起始时间,则进行提示
+							if(dayjs(dayjs(date).subtract(this.maxRange, 'day')).isAfter(dayjs(selected[0])) && this.showRangePrompt) {
+								if(this.rangePrompt) {
+									uni.$u.toast(this.rangePrompt)
+								} else {
+									uni.$u.toast(`选择天数不能超过 ${this.maxRange} 天`)
+								}
+								return
+							}
+							// 如果当前日期大于已有日期,将当前的添加到数组尾部
+							selected.push(date)
+							const startDate = selected[0]
+							const endDate = selected[1]
+							const arr = []
+							let i = 0
+							do {
+								// 将开始和结束日期之间的日期添加到数组中
+								arr.push(dayjs(startDate).add(i, 'day').format("YYYY-MM-DD"))
+								i++
+								// 累加的日期小于结束日期时,继续下一次的循环
+							} while (dayjs(startDate).add(i, 'day').isBefore(dayjs(endDate)))
+							// 为了一次性修改数组,避免computed中多次触发,这里才用arr变量一次性赋值的方式,同时将最后一个日期添加近来
+							arr.push(endDate)
+							selected = arr
+						} else {
+							// 选择区间时,只有一个日期的情况下,且不允许选择起止为同一天的话,不允许选择自己
+							if (selected[0] === date && !this.allowSameDay) return
+							selected.push(date)
+						}
+					}
+				}
+				this.setSelected(selected)
+			},
+			// 设置默认日期
+			setDefaultDate() {
+				if (!this.defaultDate) {
+					// 如果没有设置默认日期,则将当天日期设置为默认选中的日期
+					const selected = [dayjs().format("YYYY-MM-DD")]
+					return this.setSelected(selected, false)
+				}
+				let defaultDate = []
+				const minDate = this.minDate || dayjs().format("YYYY-MM-DD")
+				const maxDate = this.maxDate || dayjs(minDate).add(this.maxMonth - 1, 'month').format("YYYY-MM-DD")
+				if (this.mode === 'single') {
+					// 单选模式,可以是字符串或数组,Date对象等
+					if (!uni.$u.test.array(this.defaultDate)) {
+						defaultDate = [dayjs(this.defaultDate).format("YYYY-MM-DD")]
+					} else {
+						defaultDate = [this.defaultDate[0]]
+					}
+				} else {
+					// 如果为非数组,则不执行
+					if (!uni.$u.test.array(this.defaultDate)) return
+					defaultDate = this.defaultDate
+				}
+				// 过滤用户传递的默认数组,取出只在可允许最大值与最小值之间的元素
+				defaultDate = defaultDate.filter(item => {
+					return dayjs(item).isAfter(dayjs(minDate).subtract(1, 'day')) && dayjs(item).isBefore(dayjs(
+						maxDate).add(1, 'day'))
+				})
+				this.setSelected(defaultDate, false)
+			},
+			setSelected(selected, event = true) {
+				this.selected = selected
+				event && this.$emit('monthSelected', this.selected)
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	@import "../../libs/css/components.scss";
+
+	.u-calendar-month-wrapper {
+		margin-top: 4px;
+	}
+
+	.u-calendar-month {
+
+		&__title {
+			font-size: 14px;
+			line-height: 42px;
+			height: 42px;
+			color: $u-main-color;
+			text-align: center;
+			font-weight: bold;
+		}
+
+		&__days {
+			position: relative;
+			@include flex;
+			flex-wrap: wrap;
+
+			&__month-mark-wrapper {
+				position: absolute;
+				top: 0;
+				bottom: 0;
+				left: 0;
+				right: 0;
+				@include flex;
+				justify-content: center;
+				align-items: center;
+
+				&__text {
+					font-size: 155px;
+					color: rgba(231, 232, 234, 0.83);
+				}
+			}
+
+			&__day {
+				@include flex;
+				padding: 2px;
+				/* #ifndef APP-NVUE */
+				// vue下使用css进行宽度计算,因为某些安卓机会无法进行js获取父元素宽度进行计算得出,会有偏移
+				width: calc(100% / 7);
+				box-sizing: border-box;
+				/* #endif */
+
+				&__select {
+					flex: 1;
+					@include flex;
+					align-items: center;
+					justify-content: center;
+					position: relative;
+
+					&__dot {
+						width: 7px;
+						height: 7px;
+						border-radius: 100px;
+						background-color: $u-error;
+						position: absolute;
+						top: 12px;
+						right: 7px;
+					}
+
+					&__buttom-info {
+						color: $u-content-color;
+						text-align: center;
+						position: absolute;
+						bottom: 5px;
+						font-size: 10px;
+						text-align: center;
+						left: 0;
+						right: 0;
+
+						&--selected {
+							color: #ffffff;
+						}
+
+						&--disabled {
+							color: #cacbcd;
+						}
+					}
+
+					&__info {
+						text-align: center;
+						font-size: 16px;
+
+						&--selected {
+							color: #ffffff;
+						}
+
+						&--disabled {
+							color: #cacbcd;
+						}
+					}
+
+					&--selected {
+						background-color: $u-primary;
+						@include flex;
+						justify-content: center;
+						align-items: center;
+						flex: 1;
+						border-radius: 3px;
+					}
+
+					&--range-selected {
+						opacity: 0.3;
+						border-radius: 0;
+					}
+
+					&--range-start-selected {
+						border-top-right-radius: 0;
+						border-bottom-right-radius: 0;
+					}
+
+					&--range-end-selected {
+						border-top-left-radius: 0;
+						border-bottom-left-radius: 0;
+					}
+				}
+			}
+		}
+	}
+</style>

+ 144 - 0
uview-ui/components/u-calendar/props.js

@@ -0,0 +1,144 @@
+export default {
+    props: {
+        // 日历顶部标题
+        title: {
+            type: String,
+            default: uni.$u.props.calendar.title
+        },
+        // 是否显示标题
+        showTitle: {
+            type: Boolean,
+            default: uni.$u.props.calendar.showTitle
+        },
+        // 是否显示副标题
+        showSubtitle: {
+            type: Boolean,
+            default: uni.$u.props.calendar.showSubtitle
+        },
+        // 日期类型选择,single-选择单个日期,multiple-可以选择多个日期,range-选择日期范围
+        mode: {
+            type: String,
+            default: uni.$u.props.calendar.mode
+        },
+        // mode=range时,第一个日期底部的提示文字
+        startText: {
+            type: String,
+            default: uni.$u.props.calendar.startText
+        },
+        // mode=range时,最后一个日期底部的提示文字
+        endText: {
+            type: String,
+            default: uni.$u.props.calendar.endText
+        },
+        // 自定义列表
+        customList: {
+            type: Array,
+            default: uni.$u.props.calendar.customList
+        },
+        // 主题色,对底部按钮和选中日期有效
+        color: {
+            type: String,
+            default: uni.$u.props.calendar.color
+        },
+        // 最小的可选日期
+        minDate: {
+            type: [String, Number],
+            default: uni.$u.props.calendar.minDate
+        },
+        // 最大可选日期
+        maxDate: {
+            type: [String, Number],
+            default: uni.$u.props.calendar.maxDate
+        },
+        // 默认选中的日期,mode为multiple或range是必须为数组格式
+        defaultDate: {
+            type: [Array, String, Date, null],
+            default: uni.$u.props.calendar.defaultDate
+        },
+        // mode=multiple时,最多可选多少个日期
+        maxCount: {
+            type: [String, Number],
+            default: uni.$u.props.calendar.maxCount
+        },
+        // 日期行高
+        rowHeight: {
+            type: [String, Number],
+            default: uni.$u.props.calendar.rowHeight
+        },
+        // 日期格式化函数
+        formatter: {
+            type: [Function, null],
+            default: uni.$u.props.calendar.formatter
+        },
+        // 是否显示农历
+        showLunar: {
+            type: Boolean,
+            default: uni.$u.props.calendar.showLunar
+        },
+        // 是否显示月份背景色
+        showMark: {
+            type: Boolean,
+            default: uni.$u.props.calendar.showMark
+        },
+        // 确定按钮的文字
+        confirmText: {
+            type: String,
+            default: uni.$u.props.calendar.confirmText
+        },
+        // 确认按钮处于禁用状态时的文字
+        confirmDisabledText: {
+            type: String,
+            default: uni.$u.props.calendar.confirmDisabledText
+        },
+        // 是否显示日历弹窗
+        show: {
+            type: Boolean,
+            default: uni.$u.props.calendar.show
+        },
+        // 是否允许点击遮罩关闭日历
+        closeOnClickOverlay: {
+            type: Boolean,
+            default: uni.$u.props.calendar.closeOnClickOverlay
+        },
+        // 是否为只读状态,只读状态下禁止选择日期
+        readonly: {
+            type: Boolean,
+            default: uni.$u.props.calendar.readonly
+        },
+        // 	是否展示确认按钮
+        showConfirm: {
+            type: Boolean,
+            default: uni.$u.props.calendar.showConfirm
+        },
+        // 日期区间最多可选天数,默认无限制,mode = range时有效
+        maxRange: {
+            type: [Number, String],
+            default: uni.$u.props.calendar.maxRange
+        },
+        // 范围选择超过最多可选天数时的提示文案,mode = range时有效
+        rangePrompt: {
+            type: String,
+            default: uni.$u.props.calendar.rangePrompt
+        },
+        // 范围选择超过最多可选天数时,是否展示提示文案,mode = range时有效
+        showRangePrompt: {
+            type: Boolean,
+            default: uni.$u.props.calendar.showRangePrompt
+        },
+        // 是否允许日期范围的起止时间为同一天,mode = range时有效
+        allowSameDay: {
+            type: Boolean,
+            default: uni.$u.props.calendar.allowSameDay
+        },
+		// 圆角值
+		round: {
+		    type: [Boolean, String, Number],
+		    default: uni.$u.props.calendar.round
+		},
+		// 最多展示月份数量
+		monthNum: {
+			type: [Number, String],
+			default: 3
+		}	
+    }
+}

+ 355 - 611
uview-ui/components/u-calendar/u-calendar.vue

@@ -1,639 +1,383 @@
 <template>
-	<u-popup closeable :maskCloseAble="maskCloseAble" mode="bottom" :popup="false" v-model="value" length="auto"
-	 :safeAreaInsetBottom="safeAreaInsetBottom" @close="close" :z-index="uZIndex" :border-radius="borderRadius" :closeable="closeable">
+	<u-popup
+		:show="show"
+		mode="bottom"
+		closeable
+		@close="close"
+		:round="round"
+		:closeOnClickOverlay="closeOnClickOverlay"
+	>
 		<view class="u-calendar">
-			<view class="u-calendar__header">
-				<view class="u-calendar__header__text" v-if="!$slots['tooltip']">
-					{{toolTip}}
+			<uHeader
+				:title="title"
+				:subtitle="subtitle"
+				:showSubtitle="showSubtitle"
+				:showTitle="showTitle"
+			></uHeader>
+			<scroll-view
+				:style="{
+                    height: $u.addUnit(listHeight)
+                }"
+				scroll-y
+				@scroll="onScroll"
+				:scroll-top="scrollTop"
+				:scrollIntoView="scrollIntoView"
+			>
+				<uMonth
+					:color="color"
+					:rowHeight="rowHeight"
+					:showMark="showMark"
+					:months="months"
+					:mode="mode"
+					:maxCount="maxCount"
+					:startText="startText"
+					:endText="endText"
+					:defaultDate="defaultDate"
+					:minDate="innerMinDate"
+					:maxDate="innerMaxDate"
+					:maxMonth="monthNum"
+					:readonly="readonly"
+					:maxRange="maxRange"
+					:rangePrompt="rangePrompt"
+					:showRangePrompt="showRangePrompt"
+					:allowSameDay="allowSameDay"
+					ref="month"
+					@monthSelected="monthSelected"
+					@updateMonthTop="updateMonthTop"
+				></uMonth>
+			</scroll-view>
+			<slot name="footer" v-if="showConfirm">
+				<view class="u-calendar__confirm">
+					<u-button
+						shape="circle"
+						:text="
+                            buttonDisabled ? confirmDisabledText : confirmText
+                        "
+						:color="color"
+						@click="confirm"
+						:disabled="buttonDisabled"
+					></u-button>
 				</view>
-				<slot v-else name="tooltip" />
-			</view>
-			<view class="u-calendar__action u-flex u-row-center">
-				<view class="u-calendar__action__icon">
-					<u-icon v-if="changeYear" name="arrow-left-double" :color="yearArrowColor" @click="changeYearHandler(0)"></u-icon>
-				</view>
-				<view class="u-calendar__action__icon">
-					<u-icon v-if="changeMonth" name="arrow-left" :color="monthArrowColor" @click="changeMonthHandler(0)"></u-icon>
-				</view>
-				<view class="u-calendar__action__text">{{ showTitle }}</view>
-				<view class="u-calendar__action__icon">
-					<u-icon v-if="changeMonth" name="arrow-right" :color="monthArrowColor" @click="changeMonthHandler(1)"></u-icon>
-				</view>
-				<view class="u-calendar__action__icon">
-					<u-icon v-if="changeYear" name="arrow-right-double" :color="yearArrowColor" @click="changeYearHandler(1)"></u-icon>
-				</view>
-			</view>
-			<view class="u-calendar__week-day">
-				<view class="u-calendar__week-day__text" v-for="(item, index) in weekDayZh" :key="index">{{item}}</view>
-			</view>
-			<view class="u-calendar__content">
-				<!-- 前置空白部分 -->
-				<block v-for="(item, index) in weekdayArr" :key="index">
-					<view class="u-calendar__content__item"></view>
-				</block>
-				<view class="u-calendar__content__item" :class="{
-					'u-hover-class':openDisAbled(year,month,index+1),
-					'u-calendar__content--start-date': (mode == 'range' && startDate==`${year}-${month}-${index+1}`) || mode== 'date',
-					'u-calendar__content--end-date':(mode== 'range' && endDate==`${year}-${month}-${index+1}`) || mode == 'date'
-				}" :style="{backgroundColor: getColor(index,1)}" v-for="(item, index) in daysArr" :key="index"
-				 @tap="dateClick(index)">
-					<view class="u-calendar__content__item__inner" :style="{color: getColor(index,2)}">
-						<view>{{ index + 1 }}</view>
-					</view>
-					<view class="u-calendar__content__item__tips" :style="{color:activeColor}" v-if="mode== 'range' && startDate==`${year}-${month}-${index+1}` && startDate!=endDate">{{startText}}</view>
-					<view class="u-calendar__content__item__tips" :style="{color:activeColor}" v-if="mode== 'range' && endDate==`${year}-${month}-${index+1}`">{{endText}}</view>
-				</view>
-				<view class="u-calendar__content__bg-month">{{month}}</view>
-			</view>
-			<view class="u-calendar__bottom">
-				<view class="u-calendar__bottom__choose">
-					<text>{{mode == 'date' ? activeDate : startDate}}</text>
-					<text v-if="endDate">至{{endDate}}</text>
-				</view>
-				<view class="u-calendar__bottom__btn">
-					<u-button :type="btnType" shape="circle" size="default" @click="btnFix(false)">确定</u-button>
-				</view>
-			</view>
+			</slot>
 		</view>
 	</u-popup>
 </template>
+
 <script>
-	/**
-	 * calendar 日历
-	 * @description 此组件用于单个选择日期,范围选择日期等,日历被包裹在底部弹起的容器中。
-	 * @tutorial http://uviewui.com/components/calendar.html
-	 * @property {String} mode 选择日期的模式,date-为单个日期,range-为选择日期范围
-	 * @property {Boolean} v-model 布尔值变量,用于控制日历的弹出与收起
-	 * @property {Boolean} safe-area-inset-bottom 是否开启底部安全区适配(默认false)
-	 * @property {Boolean} change-year 是否显示顶部的切换年份方向的按钮(默认true)
-	 * @property {Boolean} change-month 是否显示顶部的切换月份方向的按钮(默认true)
-	 * @property {String Number} max-year 可切换的最大年份(默认2050)
-	 * @property {String Number} min-year 可切换的最小年份(默认1950)
-	 * @property {String Number} min-date 最小可选日期(默认1950-01-01)
-	 * @property {String Number} max-date 最大可选日期(默认当前日期)
-	 * @property {String Number} 弹窗顶部左右两边的圆角值,单位rpx(默认20)
-	 * @property {Boolean} mask-close-able 是否允许通过点击遮罩关闭日历(默认true)
-	 * @property {String} month-arrow-color 月份切换按钮箭头颜色(默认#606266)
-	 * @property {String} year-arrow-color 年份切换按钮箭头颜色(默认#909399)
-	 * @property {String} color 日期字体的默认颜色(默认#303133)
-	 * @property {String} active-bg-color 起始/结束日期按钮的背景色(默认#2979ff)
-	 * @property {String Number} z-index 弹出时的z-index值(默认10075)
-	 * @property {String} active-color 起始/结束日期按钮的字体颜色(默认#ffffff)
-	 * @property {String} range-bg-color 起始/结束日期之间的区域的背景颜色(默认rgba(41,121,255,0.13))
-	 * @property {String} range-color 选择范围内字体颜色(默认#2979ff)
-	 * @property {String} start-text 起始日期底部的提示文字(默认 '开始')
-	 * @property {String} end-text 结束日期底部的提示文字(默认 '结束')
-	 * @property {String} btn-type 底部确定按钮的主题(默认 'primary')
-	 * @property {String} toolTip 顶部提示文字,如设置名为tooltip的slot,此参数将失效(默认 '选择日期')
-	 * @property {Boolean} closeable 是否显示右上角的关闭图标(默认true)
-	 * @example <u-calendar v-model="show" :mode="mode"></u-calendar>
-	 */
-	
-	export default {
-		name: 'u-calendar',
-		props: {
-			safeAreaInsetBottom: {
-				type: Boolean,
-				default: false
-			},
-			// 是否允许通过点击遮罩关闭Picker
-			maskCloseAble: {
-				type: Boolean,
-				default: true
-			},
-			// 通过双向绑定控制组件的弹出与收起
-			value: {
-				type: Boolean,
-				default: false
-			},
-			// 弹出的z-index值
-			zIndex: {
-				type: [String, Number],
-				default: 0
-			},
-			// 是否允许切换年份
-			changeYear: {
-				type: Boolean,
-				default: true
-			},
-			// 是否允许切换月份
-			changeMonth: {
-				type: Boolean,
-				default: true
-			},
-			// date-单个日期选择,range-开始日期+结束日期选择
-			mode: {
-				type: String,
-				default: 'date'
-			},
-			// 可切换的最大年份
-			maxYear: {
-				type: [Number, String],
-				default: 2050
-			},
-			// 可切换的最小年份
-			minYear: {
-				type: [Number, String],
-				default: 1950
-			},
-			// 最小可选日期(不在范围内日期禁用不可选)
-			minDate: {
-				type: [Number, String],
-				default: '1950-01-01'
-			},
-			/**
-			 * 最大可选日期
-			 * 默认最大值为今天,之后的日期不可选
-			 * 2030-12-31
-			 * */
-			maxDate: {
-				type: [Number, String],
-				default: ''
-			},
-			// 弹窗顶部左右两边的圆角值
-			borderRadius: {
-				type: [String, Number],
-				default: 20
-			},
-			// 月份切换按钮箭头颜色
-			monthArrowColor: {
-				type: String,
-				default: '#606266'
-			},
-			// 年份切换按钮箭头颜色
-			yearArrowColor: {
-				type: String,
-				default: '#909399'
-			},
-			// 默认日期字体颜色
-			color: {
-				type: String,
-				default: '#303133'
-			},
-			// 选中|起始结束日期背景色
-			activeBgColor: {
-				type: String,
-				default: '#2979ff'
-			},
-			// 选中|起始结束日期字体颜色
-			activeColor: {
-				type: String,
-				default: '#ffffff'
-			},
-			// 范围内日期背景色
-			rangeBgColor: {
-				type: String,
-				default: 'rgba(41,121,255,0.13)'
-			}, 
-			// 范围内日期字体颜色
-			rangeColor: {
-				type: String,
-				default: '#2979ff'
-			},
-			// mode=range时生效,起始日期自定义文案
-			startText: {
-				type: String,
-				default: '开始'
-			},
-			// mode=range时生效,结束日期自定义文案
-			endText: {
-				type: String,
-				default: '结束'
-			},
-			//按钮样式类型
-			btnType: {
-				type: String,
-				default: 'primary'
-			},
-			// 当前选中日期带选中效果
-			isActiveCurrent: {
-				type: Boolean,
-				default: true
-			},
-			// 切换年月是否触发事件 mode=date时生效
-			isChange: {
-				type: Boolean,
-				default: false
-			},
-			// 是否显示右上角的关闭图标
-			closeable: {
-				type: Boolean,
-				default: true
-			},
-			// 顶部的提示文字
-			toolTip: {
-				type: String,
-				default: '选择日期'
+import uHeader from './header.vue'
+import uMonth from './month.vue'
+import props from './props.js'
+import util from './util.js'
+import dayjs from '../../libs/util/dayjs.js'
+import Calendar from '../../libs/util/calendar.js'
+/**
+ * Calendar 日历
+ * @description  此组件用于单个选择日期,范围选择日期等,日历被包裹在底部弹起的容器中.
+ * @tutorial https://www.uviewui.com/components/calendar.html
+ *
+ * @property {String}				title				标题内容 (默认 日期选择 )
+ * @property {Boolean}				showTitle			是否显示标题  (默认 true )
+ * @property {Boolean}				showSubtitle		是否显示副标题	(默认 true )
+ * @property {String}				mode				日期类型选择  single-选择单个日期,multiple-可以选择多个日期,range-选择日期范围 ( 默认 'single' )
+ * @property {String}				startText			mode=range时,第一个日期底部的提示文字  (默认 '开始' )
+ * @property {String}				endText				mode=range时,最后一个日期底部的提示文字 (默认 '结束' )
+ * @property {Array}				customList			自定义列表
+ * @property {String}				color				主题色,对底部按钮和选中日期有效  (默认 ‘#3c9cff' )
+ * @property {String | Number}		minDate				最小的可选日期	 (默认 0 )
+ * @property {String | Number}		maxDate				最大可选日期  (默认 0 )
+ * @property {Array | String| Date}	defaultDate			默认选中的日期,mode为multiple或range是必须为数组格式
+ * @property {String | Number}		maxCount			mode=multiple时,最多可选多少个日期  (默认 	Number.MAX_SAFE_INTEGER  )
+ * @property {String | Number}		rowHeight			日期行高 (默认 56 )
+ * @property {Function}				formatter			日期格式化函数
+ * @property {Boolean}				showLunar			是否显示农历  (默认 false )
+ * @property {Boolean}				showMark			是否显示月份背景色 (默认 true )
+ * @property {String}				confirmText			确定按钮的文字 (默认 '确定' )
+ * @property {String}				confirmDisabledText	确认按钮处于禁用状态时的文字 (默认 '确定' )
+ * @property {Boolean}				show				是否显示日历弹窗 (默认 false )
+ * @property {Boolean}				closeOnClickOverlay	是否允许点击遮罩关闭日历 (默认 false )
+ * @property {Boolean}				readonly	        是否为只读状态,只读状态下禁止选择日期 (默认 false )
+ * @property {String | Number}		maxRange	        日期区间最多可选天数,默认无限制,mode = range时有效
+ * @property {String}				rangePrompt	        范围选择超过最多可选天数时的提示文案,mode = range时有效
+ * @property {Boolean}				showRangePrompt	    范围选择超过最多可选天数时,是否展示提示文案,mode = range时有效 (默认 true )
+ * @property {Boolean}				allowSameDay	    是否允许日期范围的起止时间为同一天,mode = range时有效 (默认 false )
+ * @property {Number|String}	    round				圆角值,默认无圆角  (默认 0 )
+ * @property {Number|String}	    monthNum			最多展示的月份数量  (默认 3 )
+ *
+ * @event {Function()} confirm 		点击确定按钮时触发		选择日期相关的返回参数
+ * @event {Function()} close 		日历关闭时触发			可定义页面关闭时的回调事件
+ * @example <u-calendar  :defaultDate="defaultDateMultiple" :show="show" mode="multiple" @confirm="confirm">
+	</u-calendar>
+ * */
+export default {
+	name: 'u-calendar',
+	mixins: [uni.$u.mpMixin, uni.$u.mixin, props],
+	components: {
+		uHeader,
+		uMonth
+	},
+	data() {
+		return {
+			// 需要显示的月份的数组
+			months: [],
+			// 在月份滚动区域中,当前视图中月份的index索引
+			monthIndex: 0,
+			// 月份滚动区域的高度
+			listHeight: 0,
+			// month组件中选择的日期数组
+			selected: [],
+			scrollIntoView: '',
+			scrollTop:0,
+			// 过滤处理方法
+			innerFormatter: (value) => value
+		}
+	},
+	watch: {
+		selectedChange: {
+			immediate: true,
+			handler(n) {
+				this.setMonth()
 			}
 		},
-		data() {
-			return {
-				// 星期几,值为1-7
-				weekday: 1, 
-				weekdayArr:[],
-				// 当前月有多少天
-				days: 0, 
-				daysArr:[],
-				showTitle: '',
-				year: 2020,
-				month: 0,
-				day: 0,
-				startYear: 0,
-				startMonth: 0,
-				startDay: 0,
-				endYear: 0,
-				endMonth: 0,
-				endDay: 0,
-				today: '',
-				activeDate: '',
-				startDate: '',
-				endDate: '',
-				isStart: true,
-				min: null,
-				max: null,
-				weekDayZh: ['日', '一', '二', '三', '四', '五', '六']
-			};
-		},
-		computed: {
-			dataChange() {
-				return `${this.mode}-${this.minDate}-${this.maxDate}`;
-			},
-			uZIndex() {
-				// 如果用户有传递z-index值,优先使用
-				return this.zIndex ? this.zIndex : this.$u.zIndex.popup;
+		// 打开弹窗时,设置月份数据
+		show: {
+			immediate: true,
+			handler(n) {
+				this.setMonth()
 			}
+		}
+	},
+	computed: {
+		// 由于maxDate和minDate可以为字符串(2021-10-10),或者数值(时间戳),但是dayjs如果接受字符串形式的时间戳会有问题,这里进行处理
+		innerMaxDate() {
+			return uni.$u.test.number(this.maxDate)
+				? Number(this.maxDate)
+				: this.maxDate
 		},
-		watch: {
-			dataChange(val) {
-				this.init()
-			}
+		innerMinDate() {
+			return uni.$u.test.number(this.minDate)
+				? Number(this.minDate)
+				: this.minDate
 		},
-		created() {
-			this.init()
+		// 多个条件的变化,会引起选中日期的变化,这里统一管理监听
+		selectedChange() {
+			return [this.innerMinDate, this.innerMaxDate, this.defaultDate]
 		},
-		methods: {
-			getColor(index, type) {
-				let color = type == 1 ? '' : this.color;
-				let day = index + 1
-				let date = `${this.year}-${this.month}-${day}`
-				let timestamp = new Date(date.replace(/\-/g, '/')).getTime();
-				let start = this.startDate.replace(/\-/g, '/')
-				let end = this.endDate.replace(/\-/g, '/')
-				if ((this.isActiveCurrent && this.activeDate == date) || this.startDate == date || this.endDate == date) {
-					color = type == 1 ? this.activeBgColor : this.activeColor;
-				} else if (this.endDate && timestamp > new Date(start).getTime() && timestamp < new Date(end).getTime()) {
-					color = type == 1 ? this.rangeBgColor : this.rangeColor;
-				}
-				return color;
-			},
-			init() {
-				let now = new Date();
-				this.year = now.getFullYear();
-				this.month = now.getMonth() + 1;
-				this.day = now.getDate();
-				this.today = `${now.getFullYear()}-${now.getMonth() + 1}-${now.getDate()}`;
-				this.activeDate = this.today;
-				this.min = this.initDate(this.minDate);
-				this.max = this.initDate(this.maxDate || this.today);
-				this.startDate = "";
-				this.startYear = 0;
-				this.startMonth = 0;
-				this.startDay = 0;
-				this.endYear = 0;
-				this.endMonth = 0;
-				this.endDay = 0;
-				this.endDate = "";
-				this.isStart = true;
-				this.changeData();
-			},
-			//日期处理
-			initDate(date) {
-				let fdate = date.split('-');
-				return {
-					year: Number(fdate[0] || 1920),
-					month: Number(fdate[1] || 1),
-					day: Number(fdate[2] || 1)
-				}
-			},
-			openDisAbled: function(year, month, day) {
-				let bool = true;
-				let date = `${year}/${month}/${day}`;
-				// let today = this.today.replace(/\-/g, '/');
-				let min = `${this.min.year}/${this.min.month}/${this.min.day}`;
-				let max = `${this.max.year}/${this.max.month}/${this.max.day}`;
-				let timestamp = new Date(date).getTime();
-				if (timestamp >= new Date(min).getTime() && timestamp <= new Date(max).getTime()) {
-					bool = false;
-				}
-				return bool;
-			},
-			generateArray: function(start, end) {
-				return Array.from(new Array(end + 1).keys()).slice(start);
-			},
-			formatNum: function(num) {
-				return num < 10 ? '0' + num : num + '';
-			},
-			//一个月有多少天
-			getMonthDay(year, month) {
-				let days = new Date(year, month, 0).getDate();
-				return days;
-			},
-			getWeekday(year, month) {
-				let date = new Date(`${year}/${month}/01 00:00:00`);
-				return date.getDay();
-			},
-			checkRange(year) {
-				let overstep = false;
-				if (year < this.minYear || year > this.maxYear) {
-					uni.showToast({
-						title: "日期超出范围啦~",
-						icon: 'none'
-					})
-					overstep = true;
-				}
-				return overstep;
-			},
-			changeMonthHandler(isAdd) {
-				if (isAdd) {
-					let month = this.month + 1;
-					let year = month > 12 ? this.year + 1 : this.year;
-					if (!this.checkRange(year)) {
-						this.month = month > 12 ? 1 : month;
-						this.year = year;
-						this.changeData();
-					}
-
-				} else {
-					let month = this.month - 1;
-					let year = month < 1 ? this.year - 1 : this.year;
-					if (!this.checkRange(year)) {
-						this.month = month < 1 ? 12 : month;
-						this.year = year;
-						this.changeData();
-					}
-				}
-			},
-			changeYearHandler(isAdd) {
-				let year = isAdd ? this.year + 1 : this.year - 1;
-				if (!this.checkRange(year)) {
-					this.year = year;
-					this.changeData();
-				}
-			},
-			changeData() {
-				this.days = this.getMonthDay(this.year, this.month);
-				this.daysArr=this.generateArray(1,this.days)
-				this.weekday = this.getWeekday(this.year, this.month);
-				this.weekdayArr=this.generateArray(1,this.weekday)
-				this.showTitle = `${this.year}年${this.month}月`;
-				if (this.isChange && this.mode == 'date') {
-					this.btnFix(true);
-				}
-			},
-			dateClick: function(day) {
-				day += 1;
-				if (!this.openDisAbled(this.year, this.month, day)) {
-					this.day = day;
-					let date = `${this.year}-${this.month}-${day}`;
-					if (this.mode == 'date') {
-						this.activeDate = date;
-					} else {
-						let compare = new Date(date.replace(/\-/g, '/')).getTime() < new Date(this.startDate.replace(/\-/g, '/')).getTime()
-						if (this.isStart || compare) {
-							this.startDate = date;
-							this.startYear = this.year;
-							this.startMonth = this.month;
-							this.startDay = this.day;
-							this.endYear = 0;
-							this.endMonth = 0;
-							this.endDay = 0;
-							this.endDate = "";
-							this.activeDate = "";
-							this.isStart = false;
-						} else {
-							this.endDate = date;
-							this.endYear = this.year;
-							this.endMonth = this.month;
-							this.endDay = this.day;
-							this.isStart = true;
-						}
-					}
-				}
-			},
-			close() {
-				// 修改通过v-model绑定的父组件变量的值为false,从而隐藏日历弹窗
-				this.$emit('input', false);
-			},
-			getWeekText(date) {
-				date = new Date(`${date.replace(/\-/g, '/')} 00:00:00`);
-				let week = date.getDay();
-				return '星期' + ['日', '一', '二', '三', '四', '五', '六'][week];
-			},
-			btnFix(show) {
-				if (!show) {
-					this.close();
-				}
-				if (this.mode == 'date') {
-					let arr = this.activeDate.split('-')
-					let year = this.isChange ? this.year : Number(arr[0]);
-					let month = this.isChange ? this.month : Number(arr[1]);
-					let day = this.isChange ? this.day : Number(arr[2]);
-					//当前月有多少天
-					let days = this.getMonthDay(year, month);
-					let result = `${year}-${this.formatNum(month)}-${this.formatNum(day)}`;
-					let weekText = this.getWeekText(result);
-					let isToday = false;
-					if (`${year}-${month}-${day}` == this.today) {
-						//今天
-						isToday = true;
-					}
-					this.$emit('change', {
-						year: year,
-						month: month,
-						day: day,
-						days: days,
-						result: result,
-						week: weekText,
-						isToday: isToday,
-						// switch: show //是否是切换年月操作
-					});
+		subtitle() {
+			// 初始化时,this.months为空数组,所以需要特别判断处理
+			if (this.months.length) {
+				return `${this.months[this.monthIndex].year}年${
+					this.months[this.monthIndex].month
+				}月`
+			} else {
+				return ''
+			}
+		},
+		buttonDisabled() {
+			// 如果为range类型,且选择的日期个数不足1个时,让底部的按钮出于disabled状态
+			if (this.mode === 'range') {
+				if (this.selected.length <= 1) {
+					return true
 				} else {
-					if (!this.startDate || !this.endDate) return;
-					let startMonth = this.formatNum(this.startMonth);
-					let startDay = this.formatNum(this.startDay);
-					let startDate = `${this.startYear}-${startMonth}-${startDay}`;
-					let startWeek = this.getWeekText(startDate)
-
-					let endMonth = this.formatNum(this.endMonth);
-					let endDay = this.formatNum(this.endDay);
-					let endDate = `${this.endYear}-${endMonth}-${endDay}`;
-					let endWeek = this.getWeekText(endDate);
-					this.$emit('change', {
-						startYear: this.startYear,
-						startMonth: this.startMonth,
-						startDay: this.startDay,
-						startDate: startDate,
-						startWeek: startWeek,
-						endYear: this.endYear,
-						endMonth: this.endMonth,
-						endDay: this.endDay,
-						endDate: endDate,
-						endWeek: endWeek
-					});
+					return false
 				}
+			} else {
+				return false
 			}
 		}
-	};
-</script>
-
-<style scoped lang="scss">
-	@import "../../libs/css/style.components.scss";
-	
-	.u-calendar {
-		color: $u-content-color;
-		
-		&__header {
-			width: 100%;
-			box-sizing: border-box;
-			font-size: 30rpx;
-			background-color: #fff;
-			color: $u-main-color;
-			
-			&__text {
-				margin-top: 30rpx;
-				padding: 0 60rpx;
-				@include vue-flex;
-				justify-content: center;
-				align-items: center;
-			}
-		}
-		
-		&__action {
-			padding: 40rpx 0 40rpx 0;
-			
-			&__icon {
-				margin: 0 16rpx;
+	},
+	mounted() {
+		this.start = Date.now()
+		this.init()
+	},
+	methods: {
+		// 在微信小程序中,不支持将函数当做props参数,故只能通过ref形式调用
+		setFormatter(e) {
+			this.innerFormatter = e
+		},
+		// month组件内部选择日期后,通过事件通知给父组件
+		monthSelected(e) {
+			this.selected = e
+			if (!this.showConfirm) {
+				// 在不需要确认按钮的情况下,如果为单选,或者范围多选且已选长度大于2,则直接进行返还
+				if (
+					this.mode === 'multiple' ||
+					this.mode === 'single' ||
+					(this.mode === 'range' && this.selected.length >= 2)
+				) {
+					this.$emit('confirm', this.selected)
+				}
 			}
-			
-			&__text {
-				padding: 0 16rpx;
-				color: $u-main-color;
-				font-size: 32rpx;
-				line-height: 32rpx;
-				font-weight: bold;
+		},
+		init() {
+			// 校验maxDate,不能小于当前时间
+			if (
+				this.innerMaxDate &&
+				new Date(this.innerMaxDate).getTime() <= Date.now()
+			) {
+				return uni.$u.error('maxDate不能小于当前时间')
 			}
-		}
-	
-		&__week-day {
-			@include vue-flex;
-			align-items: center;
-			justify-content: center;
-			padding: 6px 0;
-			overflow: hidden;
-			
-			&__text {
-				flex: 1;
-				text-align: center;
+			// 滚动区域的高度
+			this.listHeight = this.rowHeight * 5 + 30
+			this.setMonth()
+		},
+		close() {
+			this.$emit('close')
+		},
+		// 点击确定按钮
+		confirm() {
+			if (!this.buttonDisabled) {
+				this.$emit('confirm', this.selected)
 			}
-		}
-	
-		&__content {
-			width: 100%;
-			@include vue-flex;
-			flex-wrap: wrap;
-			padding: 6px 0;
-			box-sizing: border-box;
-			background-color: #fff;
-			position: relative;
-			
-			&--end-date {
-				border-top-right-radius: 8rpx;
-				border-bottom-right-radius: 8rpx;
+		},
+		// 获得两个日期之间的月份数
+		getMonths(minDate, maxDate) {
+			const minYear = dayjs(minDate).year()
+			const minMonth = dayjs(minDate).month() + 1
+			const maxYear = dayjs(maxDate).year()
+			const maxMonth = dayjs(maxDate).month() + 1
+			return (maxYear - minYear) * 12 + (maxMonth - minMonth) + 1
+		},
+		// 设置月份数据
+		setMonth() {
+			// 最小日期的毫秒数
+			const minDate = this.innerMinDate || dayjs().valueOf()
+			// 如果没有指定最大日期,则往后推3个月
+			const maxDate =
+				this.innerMaxDate ||
+				dayjs(minDate)
+					.add(this.monthNum - 1, 'month')
+					.valueOf()
+			// 最大最小月份之间的共有多少个月份,
+			const months = uni.$u.range(
+				1,
+				this.monthNum,
+				this.getMonths(minDate, maxDate)
+			)
+			// 先清空数组
+			this.months = []
+			for (let i = 0; i < months; i++) {
+				this.months.push({
+					date: new Array(
+						dayjs(minDate).add(i, 'month').daysInMonth()
+					)
+						.fill(1)
+						.map((item, index) => {
+							// 日期,取值1-31
+							let day = index + 1
+							// 星期,0-6,0为周日
+							const week = dayjs(minDate)
+								.add(i, 'month')
+								.date(day)
+								.day()
+							const date = dayjs(minDate)
+								.add(i, 'month')
+								.date(day)
+								.format('YYYY-MM-DD')
+							let bottomInfo = ''
+							if (this.showLunar) {
+								// 将日期转为农历格式
+								const lunar = Calendar.solar2lunar(
+									dayjs(date).year(),
+									dayjs(date).month() + 1,
+									dayjs(date).date()
+								)
+								bottomInfo = lunar.IDayCn
+							}
+							let config = {
+								day,
+								week,
+								// 小于最小允许的日期,或者大于最大的日期,则设置为disabled状态
+								disabled:
+									dayjs(date).isBefore(
+										dayjs(minDate).format('YYYY-MM-DD')
+									) ||
+									dayjs(date).isAfter(
+										dayjs(maxDate).format('YYYY-MM-DD')
+									),
+								// 返回一个日期对象,供外部的formatter获取当前日期的年月日等信息,进行加工处理
+								date: new Date(date),
+								bottomInfo,
+								dot: false,
+								month:
+									dayjs(minDate).add(i, 'month').month() + 1
+							}
+							const formatter =
+								this.formatter || this.innerFormatter
+							return formatter(config)
+						}),
+					// 当前所属的月份
+					month: dayjs(minDate).add(i, 'month').month() + 1,
+					// 当前年份
+					year: dayjs(minDate).add(i, 'month').year()
+				})
 			}
-			
-			&--start-date {
-				border-top-left-radius: 8rpx;
-				border-bottom-left-radius: 8rpx;
+
+		},
+		// 滚动到默认设置的月份
+		scrollIntoDefaultMonth(selected) {
+			// 查询默认日期在可选列表的下标
+			const _index = this.months.findIndex(({
+				  year,
+				  month
+			  }) => {
+				month = uni.$u.padZero(month)
+				return `${year}-${month}` === selected
+			})
+			if (_index !== -1) {
+				// #ifndef MP-WEIXIN
+				this.$nextTick(() => {
+					this.scrollIntoView = `month-${_index}`
+				})
+				// #endif
+				// #ifdef MP-WEIXIN
+				this.scrollTop = this.months[_index].top || 0;
+				// #endif
 			}
-			
-			&__item {
-				width: 14.2857%;
-				@include vue-flex;
-				align-items: center;
-				justify-content: center;
-				padding: 6px 0;
-				overflow: hidden;
-				position: relative;
-				z-index: 2;
-				
-				&__inner {
-					height: 84rpx;
-					@include vue-flex;
-					align-items: center;
-					justify-content: center;
-					flex-direction: column;
-					font-size: 32rpx;
-					position: relative;
-					border-radius: 50%;
-					
-					&__desc {
-						width: 100%;
-						font-size: 24rpx;
-						line-height: 24rpx;
-						transform: scale(0.75);
-						transform-origin: center center;
-						position: absolute;
-						left: 0;
-						text-align: center;
-						bottom: 2rpx;
-					}
-				}
-				
-				&__tips {
-					width: 100%;
-					font-size: 24rpx;
-					line-height: 24rpx;
-					position: absolute;
-					left: 0;
-					transform: scale(0.8);
-					transform-origin: center center;
-					text-align: center;
-					bottom: 8rpx;
-					z-index: 2;
+		},
+		// scroll-view滚动监听
+		onScroll(event) {
+			// 不允许小于0的滚动值,如果scroll-view到顶了,继续下拉,会出现负数值
+			const scrollTop = Math.max(0, event.detail.scrollTop)
+			// 将当前滚动条数值,除以滚动区域的高度,可以得出当前滚动到了哪一个月份的索引
+			for (let i = 0; i < this.months.length; i++) {
+				if (scrollTop >= (this.months[i].top || this.listHeight)) {
+					this.monthIndex = i
 				}
 			}
-			
-			&__bg-month {
-				position: absolute;
-				font-size: 130px;
-				line-height: 130px;
-				left: 50%;
-				top: 50%;
-				transform: translate(-50%, -50%);
-				color: #e4e7ed;
-				z-index: 1;
-			}
-		}
-	
-		&__bottom {
-			width: 100%;
-			@include vue-flex;
-			align-items: center;
-			justify-content: center;
-			flex-direction: column;
-			background-color: #fff;
-			padding: 0 40rpx 30rpx;
-			box-sizing: border-box;
-			font-size: 24rpx;
-			color: $u-tips-color;
-			
-			&__choose {
-				height: 50rpx;
+		},
+		// 更新月份的top值
+		updateMonthTop(topArr = []) {
+			// 设置对应月份的top值,用于onScroll方法更新月份
+			topArr.map((item, index) => {
+				this.months[index].top = item
+			})
+
+			// 获取默认日期的下标
+			if (!this.defaultDate) {
+				// 如果没有设置默认日期,则将当天日期设置为默认选中的日期
+				const selected = dayjs().format("YYYY-MM")
+				this.scrollIntoDefaultMonth(selected)
+				return
 			}
-			
-			&__btn {
-				width: 100%;
+			let selected = dayjs().format("YYYY-MM");
+			// 单选模式,可以是字符串或数组,Date对象等
+			if (!uni.$u.test.array(this.defaultDate)) {
+				selected = dayjs(this.defaultDate).format("YYYY-MM")
+			} else {
+				selected = dayjs(this.defaultDate[0]).format("YYYY-MM");
 			}
+			this.scrollIntoDefaultMonth(selected)
 		}
 	}
-</style>
+}
+</script>
+
+<style lang="scss" scoped>
+@import '../../libs/css/components.scss';
+
+.u-calendar {
+	&__confirm {
+		padding: 7px 18px;
+	}
+}
+</style>

+ 85 - 0
uview-ui/components/u-calendar/util.js

@@ -0,0 +1,85 @@
+export default {
+    methods: {
+        // 设置月份数据
+        setMonth() {
+            // 月初是周几
+            const day = dayjs(this.date).date(1).day()
+            const start = day == 0 ? 6 : day - 1
+
+            // 本月天数
+            const days = dayjs(this.date).endOf('month').format('D')
+
+            // 上个月天数
+            const prevDays = dayjs(this.date).endOf('month').subtract(1, 'month').format('D')
+
+            // 日期数据
+            const arr = []
+            // 清空表格
+            this.month = []
+
+            // 添加上月数据
+            arr.push(
+                ...new Array(start).fill(1).map((e, i) => {
+                    const day = prevDays - start + i + 1
+
+                    return {
+                        value: day,
+                        disabled: true,
+                        date: dayjs(this.date).subtract(1, 'month').date(day).format('YYYY-MM-DD')
+                    }
+                })
+            )
+
+            // 添加本月数据
+            arr.push(
+                ...new Array(days - 0).fill(1).map((e, i) => {
+                    const day = i + 1
+
+                    return {
+                        value: day,
+                        date: dayjs(this.date).date(day).format('YYYY-MM-DD')
+                    }
+                })
+            )
+
+            // 添加下个月
+            arr.push(
+                ...new Array(42 - days - start).fill(1).map((e, i) => {
+                    const day = i + 1
+
+                    return {
+                        value: day,
+                        disabled: true,
+                        date: dayjs(this.date).add(1, 'month').date(day).format('YYYY-MM-DD')
+                    }
+                })
+            )
+
+            // 分割数组
+            for (let n = 0; n < arr.length; n += 7) {
+                this.month.push(
+                    arr.slice(n, n + 7).map((e, i) => {
+                        e.index = i + n
+
+                        // 自定义信息
+                        const custom = this.customList.find((c) => c.date == e.date)
+
+                        // 农历
+                        if (this.lunar) {
+                            const {
+                                IDayCn,
+                                IMonthCn
+                            } = this.getLunar(e.date)
+                            e.lunar = IDayCn == '初一' ? IMonthCn : IDayCn
+                        }
+
+                        return {
+                            ...e,
+                            ...custom
+                        }
+                    })
+                )
+            }
+        }
+    }
+}

+ 14 - 0
uview-ui/components/u-car-keyboard/props.js

@@ -0,0 +1,14 @@
+export default {
+    props: {
+        // 是否打乱键盘按键的顺序
+        random: {
+            type: Boolean,
+            default: false
+        },
+        // 输入一个中文后,是否自动切换到英文
+        autoChange: {
+            type: Boolean,
+            default: false
+        }
+    }
+}

+ 156 - 102
uview-ui/components/u-car-keyboard/u-car-keyboard.vue

@@ -1,37 +1,86 @@
 <template>
-	<view class="u-keyboard" @touchmove.stop.prevent="() => {}">
-		<view class="u-keyboard-grids">
-			<block>
-				<view class="u-keyboard-grids-item" v-for="(group, i) in abc ? EngKeyBoardList : areaList" :key="i">
-					<view :hover-stay-time="100" @tap="carInputClick(i, j)" hover-class="u-carinput-hover" class="u-keyboard-grids-btn"
-					 v-for="(item, j) in group" :key="j">
-						{{ item }}
-					</view>
+	<view
+		class="u-keyboard"
+		@touchmove.stop.prevent="noop"
+	>
+		<view
+			v-for="(group, i) in abc ? engKeyBoardList : areaList"
+			:key="i"
+			class="u-keyboard__button"
+			:index="i"
+			:class="[i + 1 === 4 && 'u-keyboard__button--center']"
+		>
+			<view
+				v-if="i === 3"
+				class="u-keyboard__button__inner-wrapper"
+			>
+				<view
+					class="u-keyboard__button__inner-wrapper__left"
+					hover-class="u-hover-class"
+					:hover-stay-time="200"
+					@tap="changeCarInputMode"
+				>
+					<text
+						class="u-keyboard__button__inner-wrapper__left__lang"
+						:class="[!abc && 'u-keyboard__button__inner-wrapper__left__lang--active']"
+					>中</text>
+					<text class="u-keyboard__button__inner-wrapper__left__line">/</text>
+					<text
+						class="u-keyboard__button__inner-wrapper__left__lang"
+						:class="[abc && 'u-keyboard__button__inner-wrapper__left__lang--active']"
+					>英</text>
 				</view>
-				<view @touchstart="backspaceClick" @touchend="clearTimer" :hover-stay-time="100" class="u-keyboard-back"
-				 hover-class="u-hover-class">
-					<u-icon :size="38" name="backspace" :bold="true"></u-icon>
+			</view>
+			<view
+				class="u-keyboard__button__inner-wrapper"
+				v-for="(item, j) in group"
+				:key="j"
+			>
+				<view
+					class="u-keyboard__button__inner-wrapper__inner"
+					:hover-stay-time="200"
+					@tap="carInputClick(i, j)"
+					hover-class="u-hover-class"
+				>
+					<text class="u-keyboard__button__inner-wrapper__inner__text">{{ item }}</text>
 				</view>
-				<view :hover-stay-time="100" class="u-keyboard-change" hover-class="u-carinput-hover" @tap="changeCarInputMode">
-					<text class="zh" :class="[!abc ? 'active' : 'inactive']">中</text>
-					/
-					<text class="en" :class="[abc ? 'active' : 'inactive']">英</text>
+			</view>
+			<view
+				v-if="i === 3"
+				@touchstart="backspaceClick"
+				@touchend="clearTimer"
+				class="u-keyboard__button__inner-wrapper"
+			>
+				<view
+					class="u-keyboard__button__inner-wrapper__right"
+					hover-class="u-hover-class"
+					:hover-stay-time="200"
+				>
+					<u-icon
+						size="28"
+						name="backspace"
+						color="#303133"
+					></u-icon>
 				</view>
-			</block>
+			</view>
 		</view>
 	</view>
 </template>
 
 <script>
+	import props from './props.js';
+	/**
+	 * keyboard 键盘组件
+	 * @description 此为uView自定义的键盘面板,内含了数字键盘,车牌号键,身份证号键盘3种模式,都有可以打乱按键顺序的选项。
+	 * @tutorial https://uviewui.com/components/keyboard.html
+	 * @property {Boolean} random 是否打乱键盘的顺序
+	 * @event {Function} change 点击键盘触发
+	 * @event {Function} backspace 点击退格键触发
+	 * @example <u-keyboard ref="uKeyboard" mode="car" v-model="show"></u-keyboard>
+	 */
 	export default {
 		name: "u-keyboard",
-		props: {
-			// 是否打乱键盘按键的顺序
-			random: {
-				type: Boolean,
-				default: false
-			}
-		},
+		mixins: [uni.$u.mpMixin, uni.$u.mixin, props],
 		data() {
 			return {
 				// 车牌输入时,abc=true为输入车牌号码,bac=false为输入省份中文简称
@@ -80,7 +129,7 @@
 				];
 				let tmp = [];
 				// 打乱顺序
-				if (this.random) data = this.$u.randomArray(data);
+				if (this.random) data = uni.$u.randomArray(data);
 				// 切割成二维数组
 				tmp[0] = data.slice(0, 10);
 				tmp[1] = data.slice(10, 20);
@@ -88,7 +137,7 @@
 				tmp[3] = data.slice(30, 36);
 				return tmp;
 			},
-			EngKeyBoardList() {
+			engKeyBoardList() {
 				let data = [
 					1,
 					2,
@@ -128,7 +177,7 @@
 					'M'
 				];
 				let tmp = [];
-				if (this.random) data = this.$u.randomArray(data);
+				if (this.random) data = uni.$u.randomArray(data);
 				tmp[0] = data.slice(0, 10);
 				tmp[1] = data.slice(10, 20);
 				tmp[2] = data.slice(20, 30);
@@ -141,8 +190,10 @@
 			carInputClick(i, j) {
 				let value = '';
 				// 不同模式,获取不同数组的值
-				if (this.abc) value = this.EngKeyBoardList[i][j];
+				if (this.abc) value = this.engKeyBoardList[i][j];
 				else value = this.areaList[i][j];
+				// 如果允许自动切换,则将中文状态切换为英文
+				if (!this.abc && this.autoChange) uni.$u.sleep(200).then(() => this.abc = true)
 				this.$emit('change', value);
 			},
 			// 修改汽车牌键盘的输入模式,中文|英文
@@ -167,91 +218,94 @@
 </script>
 
 <style lang="scss" scoped>
-	@import "../../libs/css/style.components.scss";
+	@import "../../libs/css/components.scss";
+	$u-car-keyboard-background-color: rgb(224, 228, 230) !default;
+	$u-car-keyboard-padding:6px 0 6px !default;
+	$u-car-keyboard-button-inner-width:64rpx !default;
+	$u-car-keyboard-button-inner-background-color:#FFFFFF !default;
+	$u-car-keyboard-button-height:80rpx !default;
+	$u-car-keyboard-button-inner-box-shadow:0 1px 0px #999992 !default;
+	$u-car-keyboard-button-border-radius:4px !default;
+	$u-car-keyboard-button-inner-margin:8rpx 5rpx !default;
+	$u-car-keyboard-button-text-font-size:16px !default;
+	$u-car-keyboard-button-text-color:$u-main-color !default;
+	$u-car-keyboard-center-inner-margin: 0 4rpx !default;
+	$u-car-keyboard-special-button-width:134rpx !default;
+	$u-car-keyboard-lang-font-size:16px !default;
+	$u-car-keyboard-lang-color:$u-main-color !default;
+	$u-car-keyboard-active-color:$u-primary !default;
+	$u-car-keyboard-line-font-size:15px !default;
+	$u-car-keyboard-line-color:$u-main-color !default;
+	$u-car-keyboard-line-margin:0 1px !default;
+	$u-car-keyboard-u-hover-class-background-color:#BBBCC6 !default;
 
-	.u-keyboard-grids {
-		background: rgb(215, 215, 217);
-		padding: 24rpx 0;
-		position: relative;
-	}
+	.u-keyboard {
+		@include flex(column);
+		justify-content: space-around;
+		background-color: $u-car-keyboard-background-color;
+		align-items: stretch;
+		padding: $u-car-keyboard-padding;
 
-	.u-keyboard-grids-item {
-		@include vue-flex;
-		align-items: center;
-		justify-content: center;
-	}
+		&__button {
+			@include flex;
+			justify-content: center;
+			flex: 1;
+			/* #ifndef APP-NVUE */
+			/* #endif */
 
-	.u-keyboard-grids-btn {
-		text-decoration: none;
-		width: 62rpx;
-		flex: 0 0 64rpx;
-		height: 80rpx;
-		/* #ifndef APP-NVUE */
-		display: inline-flex;		
-		/* #endif */
-		font-size: 36rpx;
-		text-align: center;
-		line-height: 80rpx;
-		background-color: #fff;
-		margin: 8rpx 5rpx;
-		border-radius: 8rpx;
-		box-shadow: 0 2rpx 0rpx #888992;
-		font-weight: 500;
-		justify-content: center;
-	}
-
-	.u-carinput-hover {
-		background-color: rgb(185, 188, 195) !important;
-	}
+			&__inner-wrapper {
+				box-shadow: $u-car-keyboard-button-inner-box-shadow;
+				margin: $u-car-keyboard-button-inner-margin;
+				border-radius: $u-car-keyboard-button-border-radius;
 
-	.u-keyboard-back {
-		position: absolute;
-		width: 96rpx;
-		right: 22rpx;
-		bottom: 32rpx;
-		height: 80rpx;
-		background-color: rgb(185, 188, 195);
-		@include vue-flex;
-		align-items: center;
-		border-radius: 8rpx;
-		justify-content: center;
-		box-shadow: 0 2rpx 0rpx #888992;
-	}
+				&__inner {
+					@include flex;
+					justify-content: center;
+					align-items: center;
+					width: $u-car-keyboard-button-inner-width;
+					background-color: $u-car-keyboard-button-inner-background-color;
+					height: $u-car-keyboard-button-height;
+					border-radius: $u-car-keyboard-button-border-radius;
 
-	.u-keyboard-change {
-		font-size: 24rpx;
-		box-shadow: 0 2rpx 0rpx #888992;
-		position: absolute;
-		width: 96rpx;
-		left: 22rpx;
-		line-height: 1;
-		bottom: 32rpx;
-		height: 80rpx;
-		background-color: #ffffff;
-		@include vue-flex;
-		align-items: center;
-		border-radius: 8rpx;
-		justify-content: center;
-	}
+					&__text {
+						font-size: $u-car-keyboard-button-text-font-size;
+						color: $u-car-keyboard-button-text-color;
+					}
+				}
 
-	.u-keyboard-change .inactive.zh {
-		transform: scale(0.85) translateY(-10rpx);
-	}
+				&__left,
+				&__right {
+					border-radius: $u-car-keyboard-button-border-radius;
+					width: $u-car-keyboard-special-button-width;
+					height: $u-car-keyboard-button-height;
+					background-color: $u-car-keyboard-u-hover-class-background-color;
+					@include flex;
+					justify-content: center;
+					align-items: center;
+					box-shadow: $u-car-keyboard-button-inner-box-shadow;
+				}
 
-	.u-keyboard-change .inactive.en {
-		transform: scale(0.85) translateY(10rpx);
-	}
+				&__left {
+					&__line {
+						font-size: $u-car-keyboard-line-font-size;
+						color: $u-car-keyboard-line-color;
+						margin: $u-car-keyboard-line-margin;
+					}
 
-	.u-keyboard-change .active {
-		color: rgb(237, 112, 64);
-		font-size: 30rpx;
-	}
+					&__lang {
+						font-size: $u-car-keyboard-lang-font-size;
+						color: $u-car-keyboard-lang-color;
 
-	.u-keyboard-change .zh {
-		transform: translateY(-10rpx);
+						&--active {
+							color: $u-car-keyboard-active-color;
+						}
+					}
+				}
+			}
+		}
 	}
 
-	.u-keyboard-change .en {
-		transform: translateY(10rpx);
+	.u-hover-class {
+		background-color: $u-car-keyboard-u-hover-class-background-color;
 	}
 </style>

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

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

+ 14 - 0
uview-ui/components/u-cell-group/props.js

@@ -0,0 +1,14 @@
+export default {
+    props: {
+        // 分组标题
+        title: {
+            type: String,
+            default: uni.$u.props.cellGroup.title
+        },
+        // 是否显示外边框
+        border: {
+            type: Boolean,
+            default: uni.$u.props.cellGroup.border
+        }
+    }
+}

+ 45 - 54
uview-ui/components/u-cell-group/u-cell-group.vue

@@ -1,70 +1,61 @@
 <template>
-	<view class="u-cell-box">
-		<view class="u-cell-title" v-if="title" :style="[titleStyle]">
-			{{title}}
-		</view>
-		<view class="u-cell-item-box" :class="{'u-border-bottom u-border-top': border}">
-			<slot />
-		</view>
-	</view>
+    <view :style="[$u.addStyle(customStyle)]" :class="[customClass]" class="u-cell-group">
+        <view v-if="title" class="u-cell-group__title">
+            <slot name="title">
+				<text class="u-cell-group__title__text">{{ title }}</text>
+			</slot>
+        </view>
+        <view class="u-cell-group__wrapper">
+			<u-line v-if="border"></u-line>
+            <slot />
+        </view>
+    </view>
 </template>
 
 <script>
+	import props from './props.js';
 	/**
-	 * cellGroup 单元格父组件Group
-	 * @description cell单元格一般用于一组列表的情况,比如个人中心页,设置页等。搭配u-cell-item
-	 * @tutorial https://www.uviewui.com/components/cell.html
-	 * @property {String} title 分组标题
-	 * @property {Boolean} border 是否显示外边框(默认true)
-	 * @property {Object} title-style 分组标题的的样式,对象形式,如{'font-size': '24rpx'} 或 {'fontSize': '24rpx'}
+	 * cellGroup  单元格
+	 * @description cell单元格一般用于一组列表的情况,比如个人中心页,设置页等。
+	 * @tutorial https://uviewui.com/components/cell.html
+	 * 
+	 * @property {String}	title		分组标题
+	 * @property {Boolean}	border		是否显示外边框 (默认 true )
+	 * @property {Object}	customStyle	定义需要用到的外部样式
+	 * 
+	 * @event {Function} click 	点击cell列表时触发
 	 * @example <u-cell-group title="设置喜好">
 	 */
 	export default {
-		name: "u-cell-group",
-		props: {
-			// 分组标题
-			title: {
-				type: String,
-				default: ''
-			},
-			// 是否显示分组list上下边框
-			border: {
-				type: Boolean,
-				default: true
-			},
-			// 分组标题的样式,对象形式,注意驼峰属性写法
-			// 类似 {'font-size': '24rpx'} 和 {'fontSize': '24rpx'}
-			titleStyle: {
-				type: Object,
-				default () {
-					return {};
-				}
-			}
-		},
-		data() {
-			return {
-				index: 0,
-			}
-		},
+		name: 'u-cell-group',
+		mixins: [uni.$u.mpMixin, uni.$u.mixin,props],
 	}
 </script>
 
 <style lang="scss" scoped>
-	@import "../../libs/css/style.components.scss";
+	@import "../../libs/css/components.scss";
 	
-	.u-cell-box {
-		width: 100%;
-	}
+	$u-cell-group-title-padding: 16px 16px 8px !default;
+	$u-cell-group-title-font-size: 15px !default;
+	$u-cell-group-title-line-height: 16px !default;
+	$u-cell-group-title-color: $u-main-color !default;
 
-	.u-cell-title {
-		padding: 30rpx 32rpx 10rpx 32rpx;
-		font-size: 30rpx;
-		text-align: left;
-		color: $u-tips-color;
-	}
+    .u-cell-group {
+		flex: 1;
+		
+        &__title {
+            padding: $u-cell-group-title-padding;
 
-	.u-cell-item-box {
-		background-color: #FFFFFF;
-		flex-direction: row;
-	}
+            &__text {
+                font-size: $u-cell-group-title-font-size;
+                line-height: $u-cell-group-title-line-height;
+                color: $u-cell-group-title-color;
+            }
+        }
+		
+		&__wrapper {
+			position: relative;
+		}
+    }
 </style>
+

+ 0 - 316
uview-ui/components/u-cell-item/u-cell-item.vue

@@ -1,316 +0,0 @@
-<template>
-	<view
-		@tap="click"
-		class="u-cell"
-		:class="{ 'u-border-bottom': borderBottom, 'u-border-top': borderTop, 'u-col-center': center, 'u-cell--required': required }"
-		hover-stay-time="150"
-		:hover-class="hoverClass"
-		:style="{
-			backgroundColor: bgColor
-		}"
-	>
-		<u-icon :size="iconSize" :name="icon" v-if="icon" :custom-style="iconStyle" class="u-cell__left-icon-wrap"></u-icon>
-		<view class="u-flex" v-else>
-			<slot name="icon"></slot>
-		</view>
-		<view
-			class="u-cell_title"
-			:style="[
-				{
-					width: titleWidth ? titleWidth + 'rpx' : 'auto'
-				},
-				titleStyle
-			]"
-		>
-			<block v-if="title !== ''">{{ title }}</block>
-			<slot name="title" v-else></slot>
-
-			<view class="u-cell__label" v-if="label || $slots.label" :style="[labelStyle]">
-				<block v-if="label !== ''">{{ label }}</block>
-				<slot name="label" v-else></slot>
-			</view>
-		</view>
-
-		<view class="u-cell__value" :style="[valueStyle]">
-			<block class="u-cell__value" v-if="value !== ''">{{ value }}</block>
-			<slot v-else></slot>
-		</view>
-		<view class="u-flex u-cell_right" v-if="$slots['right-icon']">
-			<slot name="right-icon"></slot>
-		</view>
-		<u-icon v-if="arrow" name="arrow-right" :style="[arrowStyle]" class="u-icon-wrap u-cell__right-icon-wrap"></u-icon>
-	</view>
-</template>
-
-<script>
-/**
- * cellItem 单元格Item
- * @description cell单元格一般用于一组列表的情况,比如个人中心页,设置页等。搭配u-cell-group使用
- * @tutorial https://www.uviewui.com/components/cell.html
- * @property {String} title 左侧标题
- * @property {String} icon 左侧图标名,只支持uView内置图标,见Icon 图标
- * @property {Object} icon-style 左边图标的样式,对象形式
- * @property {String} value 右侧内容
- * @property {String} label 标题下方的描述信息
- * @property {Boolean} border-bottom 是否显示cell的下边框(默认true)
- * @property {Boolean} border-top 是否显示cell的上边框(默认false)
- * @property {Boolean} center 是否使内容垂直居中(默认false)
- * @property {String} hover-class 是否开启点击反馈,none为无效果(默认true)
- * // @property {Boolean} border-gap border-bottom为true时,Cell列表中间的条目的下边框是否与左边有一个间隔(默认true)
- * @property {Boolean} arrow 是否显示右侧箭头(默认true)
- * @property {Boolean} required 箭头方向,可选值(默认right)
- * @property {Boolean} arrow-direction 是否显示左边表示必填的星号(默认false)
- * @property {Object} title-style 标题样式,对象形式
- * @property {Object} value-style 右侧内容样式,对象形式
- * @property {Object} label-style 标题下方描述信息的样式,对象形式
- * @property {String} bg-color 背景颜色(默认transparent)
- * @property {String Number} index 用于在click事件回调中返回,标识当前是第几个Item
- * @property {String Number} title-width 标题的宽度,单位rpx
- * @example <u-cell-item icon="integral-fill" title="会员等级" value="新版本"></u-cell-item>
- */
-export default {
-	name: 'u-cell-item',
-	props: {
-		// 左侧图标名称(只能uView内置图标),或者图标src
-		icon: {
-			type: String,
-			default: ''
-		},
-		// 左侧标题
-		title: {
-			type: [String, Number],
-			default: ''
-		},
-		// 右侧内容
-		value: {
-			type: [String, Number],
-			default: ''
-		},
-		// 标题下方的描述信息
-		label: {
-			type: [String, Number],
-			default: ''
-		},
-		// 是否显示下边框
-		borderBottom: {
-			type: Boolean,
-			default: true
-		},
-		// 是否显示上边框
-		borderTop: {
-			type: Boolean,
-			default: false
-		},
-		// 多个cell中,中间的cell显示下划线时,下划线是否给一个到左边的距离
-		// 1.4.0版本废除此参数,默认边框由border-top和border-bottom提供,此参数会造成干扰
-		// borderGap: {
-		// 	type: Boolean,
-		// 	default: true
-		// },
-		// 是否开启点击反馈,即点击时cell背景为灰色,none为无效果
-		hoverClass: {
-			type: String,
-			default: 'u-cell-hover'
-		},
-		// 是否显示右侧箭头
-		arrow: {
-			type: Boolean,
-			default: true
-		},
-		// 内容是否垂直居中
-		center: {
-			type: Boolean,
-			default: false
-		},
-		// 是否显示左边表示必填的星号
-		required: {
-			type: Boolean,
-			default: false
-		},
-		// 标题的宽度,单位rpx
-		titleWidth: {
-			type: [Number, String],
-			default: ''
-		},
-		// 右侧箭头方向,可选值:right|up|down,默认为right
-		arrowDirection: {
-			type: String,
-			default: 'right'
-		},
-		// 控制标题的样式
-		titleStyle: {
-			type: Object,
-			default() {
-				return {};
-			}
-		},
-		// 右侧显示内容的样式
-		valueStyle: {
-			type: Object,
-			default() {
-				return {};
-			}
-		},
-		// 描述信息的样式
-		labelStyle: {
-			type: Object,
-			default() {
-				return {};
-			}
-		},
-		// 背景颜色
-		bgColor: {
-			type: String,
-			default: 'transparent'
-		},
-		// 用于识别被点击的是第几个cell
-		index: {
-			type: [String, Number],
-			default: ''
-		},
-		// 是否使用lable插槽
-		useLabelSlot: {
-			type: Boolean,
-			default: false
-		},
-		// 左边图标的大小,单位rpx,只对传入icon字段时有效
-		iconSize: {
-			type: [Number, String],
-			default: 34
-		},
-		// 左边图标的样式,对象形式
-		iconStyle: {
-			type: Object,
-			default() {
-				return {}
-			}
-		},
-	},
-	data() {
-		return {
-
-		};
-	},
-	computed: {
-		arrowStyle() {
-			let style = {};
-			if (this.arrowDirection == 'up') style.transform = 'rotate(-90deg)';
-			else if (this.arrowDirection == 'down') style.transform = 'rotate(90deg)';
-			else style.transform = 'rotate(0deg)';
-			return style;
-		}
-	},
-	methods: {
-		click() {
-			this.$emit('click', this.index);
-		}
-	}
-};
-</script>
-
-<style lang="scss" scoped>
-@import "../../libs/css/style.components.scss";
-.u-cell {
-	@include vue-flex;
-	align-items: center;
-	position: relative;
-	/* #ifndef APP-NVUE */
-	box-sizing: border-box;
-	/* #endif */
-	width: 100%;
-	padding: 26rpx 32rpx;
-	font-size: 28rpx;
-	line-height: 54rpx;
-	color: $u-content-color;
-	background-color: #fff;
-	text-align: left;
-}
-
-.u-cell_title {
-	font-size: 28rpx;
-}
-
-.u-cell__left-icon-wrap {
-	margin-right: 10rpx;
-	font-size: 32rpx;
-}
-
-.u-cell__right-icon-wrap {
-	margin-left: 10rpx;
-	color: #969799;
-	font-size: 28rpx;
-}
-
-.u-cell__left-icon-wrap,
-.u-cell__right-icon-wrap {
-	@include vue-flex;
-	align-items: center;
-	height: 48rpx;
-}
-
-.u-cell-border:after {
-	position: absolute; 
-	/* #ifndef APP-NVUE */
-	box-sizing: border-box;
-	content: ' ';
-	pointer-events: none;
-	border-bottom: 1px solid $u-border-color;
-	/* #endif */
-	right: 0;
-	left: 0;
-	top: 0;
-	transform: scaleY(0.5);
-}
-
-.u-cell-border {
-	position: relative;
-}
-
-.u-cell__label {
-	margin-top: 6rpx;
-	font-size: 26rpx;
-	line-height: 36rpx;
-	color: $u-tips-color;
-	/* #ifndef APP-NVUE */
-	word-wrap: break-word;
-	/* #endif */
-}
-
-.u-cell__value {
-	overflow: hidden;
-	text-align: right;
-	/* #ifndef APP-NVUE */
-	vertical-align: middle;
-	/* #endif */
-	color: $u-tips-color;
-	font-size: 26rpx;
-}
-
-.u-cell__title,
-.u-cell__value {
-	flex: 1;
-}
-
-.u-cell--required {
-	/* #ifndef APP-NVUE */
-	overflow: visible;
-	/* #endif */
-	@include vue-flex;
-	align-items: center;
-}
-
-.u-cell--required:before {
-	position: absolute;
-	/* #ifndef APP-NVUE */
-	content: '*';
-	/* #endif */
-	left: 8px;
-	margin-top: 4rpx;
-	font-size: 14px;
-	color: $u-type-error;
-}
-
-.u-cell_right {
-	line-height: 1;
-}
-</style>

+ 110 - 0
uview-ui/components/u-cell/props.js

@@ -0,0 +1,110 @@
+export default {
+    props: {
+        // 标题
+        title: {
+            type: [String, Number],
+            default: uni.$u.props.cell.title
+        },
+        // 标题下方的描述信息
+        label: {
+            type: [String, Number],
+            default: uni.$u.props.cell.label
+        },
+        // 右侧的内容
+        value: {
+            type: [String, Number],
+            default: uni.$u.props.cell.value
+        },
+        // 左侧图标名称,或者图片链接(本地文件建议使用绝对地址)
+        icon: {
+            type: String,
+            default: uni.$u.props.cell.icon
+        },
+        // 是否禁用cell
+        disabled: {
+            type: Boolean,
+            default: uni.$u.props.cell.disabled
+        },
+        // 是否显示下边框
+        border: {
+            type: Boolean,
+            default: uni.$u.props.cell.border
+        },
+        // 内容是否垂直居中(主要是针对右侧的value部分)
+        center: {
+            type: Boolean,
+            default: uni.$u.props.cell.center
+        },
+        // 点击后跳转的URL地址
+        url: {
+            type: String,
+            default: uni.$u.props.cell.url
+        },
+        // 链接跳转的方式,内部使用的是uView封装的route方法,可能会进行拦截操作
+        linkType: {
+            type: String,
+            default: uni.$u.props.cell.linkType
+        },
+        // 是否开启点击反馈(表现为点击时加上灰色背景)
+        clickable: {
+            type: Boolean,
+            default: uni.$u.props.cell.clickable
+        },
+        // 是否展示右侧箭头并开启点击反馈
+        isLink: {
+            type: Boolean,
+            default: uni.$u.props.cell.isLink
+        },
+        // 是否显示表单状态下的必填星号(此组件可能会内嵌入input组件)
+        required: {
+            type: Boolean,
+            default: uni.$u.props.cell.required
+        },
+        // 右侧的图标箭头
+        rightIcon: {
+            type: String,
+            default: uni.$u.props.cell.rightIcon
+        },
+        // 右侧箭头的方向,可选值为:left,up,down
+        arrowDirection: {
+            type: String,
+            default: uni.$u.props.cell.arrowDirection
+        },
+        // 左侧图标样式
+        iconStyle: {
+            type: [Object, String],
+            default: () => {
+				return uni.$u.props.cell.iconStyle
+			}
+        },
+        // 右侧箭头图标的样式
+        rightIconStyle: {
+            type: [Object, String],
+            default: () => {
+				return uni.$u.props.cell.rightIconStyle
+			}
+        },
+        // 标题的样式
+        titleStyle: {
+            type: [Object, String],
+			default: () => {
+				return uni.$u.props.cell.titleStyle
+			}
+        },
+        // 单位元的大小,可选值为large
+        size: {
+            type: String,
+            default: uni.$u.props.cell.size
+        },
+        // 点击cell是否阻止事件传播
+        stop: {
+            type: Boolean,
+            default: uni.$u.props.cell.stop
+        },
+        // 标识符,cell被点击时返回
+        name: {
+            type: [Number, String],
+            default: uni.$u.props.cell.name
+        }
+    }
+}

+ 229 - 0
uview-ui/components/u-cell/u-cell.vue

@@ -0,0 +1,229 @@
+<template>
+	<view class="u-cell" :class="[customClass]" :style="[$u.addStyle(customStyle)]"
+		:hover-class="(!disabled && (clickable || isLink)) ? 'u-cell--clickable' : ''" :hover-stay-time="250"
+		@tap="clickHandler">
+		<view class="u-cell__body" :class="[ center && 'u-cell--center', size === 'large' && 'u-cell__body--large']">
+			<view class="u-cell__body__content">
+				<view class="u-cell__left-icon-wrap" v-if="$slots.icon || icon">
+					<slot name="icon" v-if="$slots.icon">
+					</slot>
+					<u-icon v-else :name="icon" :custom-style="iconStyle" :size="size === 'large' ? 22 : 18"></u-icon>
+				</view>
+				<view class="u-cell__title">
+					<slot name="title">
+						<text v-if="title" class="u-cell__title-text" :style="[titleTextStyle]"
+							:class="[disabled && 'u-cell--disabled', size === 'large' && 'u-cell__title-text--large']">{{ title }}</text>
+					</slot>
+					<slot name="label">
+						<text class="u-cell__label" v-if="label"
+							:class="[disabled && 'u-cell--disabled', size === 'large' && 'u-cell__label--large']">{{ label }}</text>
+					</slot>
+				</view>
+			</view>
+			<slot name="value">
+				<text class="u-cell__value"
+					:class="[disabled && 'u-cell--disabled', size === 'large' && 'u-cell__value--large']"
+					v-if="!$u.test.empty(value)">{{ value }}</text>
+			</slot>
+			<view class="u-cell__right-icon-wrap" v-if="$slots['right-icon'] || isLink"
+				:class="[`u-cell__right-icon-wrap--${arrowDirection}`]">
+				<slot name="right-icon" v-if="$slots['right-icon']">
+				</slot>
+				<u-icon v-else :name="rightIcon" :custom-style="rightIconStyle" :color="disabled ? '#c8c9cc' : 'info'"
+					:size="size === 'large' ? 18 : 16"></u-icon>
+			</view>
+		</view>
+		<u-line v-if="border"></u-line>
+	</view>
+</template>
+
+<script>
+	import props from './props.js';
+	/**
+	 * cell  单元格
+	 * @description cell单元格一般用于一组列表的情况,比如个人中心页,设置页等。
+	 * @tutorial https://uviewui.com/components/cell.html
+	 * @property {String | Number}	title			标题
+	 * @property {String | Number}	label			标题下方的描述信息
+	 * @property {String | Number}	value			右侧的内容
+	 * @property {String}			icon			左侧图标名称,或者图片链接(本地文件建议使用绝对地址)
+	 * @property {Boolean}			disabled		是否禁用cell	
+	 * @property {Boolean}			border			是否显示下边框 (默认 true )
+	 * @property {Boolean}			center			内容是否垂直居中(主要是针对右侧的value部分) (默认 false )
+	 * @property {String}			url				点击后跳转的URL地址
+	 * @property {String}			linkType		链接跳转的方式,内部使用的是uView封装的route方法,可能会进行拦截操作 (默认 'navigateTo' )
+	 * @property {Boolean}			clickable		是否开启点击反馈(表现为点击时加上灰色背景) (默认 false ) 
+	 * @property {Boolean}			isLink			是否展示右侧箭头并开启点击反馈 (默认 false )
+	 * @property {Boolean}			required		是否显示表单状态下的必填星号(此组件可能会内嵌入input组件) (默认 false )
+	 * @property {String}			rightIcon		右侧的图标箭头 (默认 'arrow-right')
+	 * @property {String}			arrowDirection	右侧箭头的方向,可选值为:left,up,down
+	 * @property {Object | String}			rightIconStyle	右侧箭头图标的样式
+	 * @property {Object | String}			titleStyle		标题的样式
+	 * @property {Object | String}			iconStyle		左侧图标样式
+	 * @property {String}			size			单位元的大小,可选值为 large,normal,mini 
+	 * @property {Boolean}			stop			点击cell是否阻止事件传播 (默认 true )
+	 * @property {Object}			customStyle		定义需要用到的外部样式
+	 * 
+	 * @event {Function}			click			点击cell列表时触发
+	 * @example 该组件需要搭配cell-group组件使用,见官方文档示例
+	 */
+	export default {
+		name: 'u-cell',
+		data() {
+			return {
+
+			}
+		},
+		mixins: [uni.$u.mpMixin, uni.$u.mixin, props],
+		computed: {
+			titleTextStyle() {
+				return uni.$u.addStyle(this.titleStyle)
+			}
+		},
+		methods: {
+			// 点击cell
+			clickHandler(e) {
+				if (this.disabled) return
+				this.$emit('click', {
+					name: this.name
+				})
+				// 如果配置了url(此props参数通过mixin引入)参数,跳转页面
+				this.openPage()
+				// 是否阻止事件传播
+				this.stop && this.preventEvent(e)
+			},
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	@import "../../libs/css/components.scss";
+
+	$u-cell-padding: 10px 15px !default;
+	$u-cell-font-size: 15px !default;
+	$u-cell-line-height: 24px !default;
+	$u-cell-color: $u-main-color !default;
+	$u-cell-icon-size: 16px !default;
+	$u-cell-title-font-size: 15px !default;
+	$u-cell-title-line-height: 22px !default;
+	$u-cell-title-color: $u-main-color !default;
+	$u-cell-label-font-size: 12px !default;
+	$u-cell-label-color: $u-tips-color !default;
+	$u-cell-label-line-height: 18px !default;
+	$u-cell-value-font-size: 14px !default;
+	$u-cell-value-color: $u-content-color !default;
+	$u-cell-clickable-color: $u-bg-color !default;
+	$u-cell-disabled-color: #c8c9cc !default;
+	$u-cell-padding-top-large: 13px !default;
+	$u-cell-padding-bottom-large: 13px !default;
+	$u-cell-value-font-size-large: 15px !default;
+	$u-cell-label-font-size-large: 14px !default;
+	$u-cell-title-font-size-large: 16px !default;
+	$u-cell-left-icon-wrap-margin-right: 4px !default;
+	$u-cell-right-icon-wrap-margin-left: 4px !default;
+	$u-cell-title-flex:1 !default;
+	$u-cell-label-margin-top:5px !default;
+
+
+	.u-cell {
+		&__body {
+			@include flex();
+			/* #ifndef APP-NVUE */
+			box-sizing: border-box;
+			/* #endif */
+			padding: $u-cell-padding;
+			font-size: $u-cell-font-size;
+			color: $u-cell-color;
+			// line-height: $u-cell-line-height;
+			align-items: center;
+
+			&__content {
+				@include flex(row);
+				align-items: center;
+				flex: 1;
+			}
+
+			&--large {
+				padding-top: $u-cell-padding-top-large;
+				padding-bottom: $u-cell-padding-bottom-large;
+			}
+		}
+
+		&__left-icon-wrap,
+		&__right-icon-wrap {
+			@include flex();
+			align-items: center;
+			// height: $u-cell-line-height;
+			font-size: $u-cell-icon-size;
+		}
+
+		&__left-icon-wrap {
+			margin-right: $u-cell-left-icon-wrap-margin-right;
+		}
+
+		&__right-icon-wrap {
+			margin-left: $u-cell-right-icon-wrap-margin-left;
+			transition: transform 0.3s;
+
+			&--up {
+				transform: rotate(-90deg);
+			}
+
+			&--down {
+				transform: rotate(90deg);
+			}
+		}
+
+		&__title {
+			flex: $u-cell-title-flex;
+
+			&-text {
+				font-size: $u-cell-title-font-size;
+				line-height: $u-cell-title-line-height;
+				color: $u-cell-title-color;
+
+				&--large {
+					font-size: $u-cell-title-font-size-large;
+				}
+			}
+
+		}
+
+		&__label {
+			margin-top: $u-cell-label-margin-top;
+			font-size: $u-cell-label-font-size;
+			color: $u-cell-label-color;
+			line-height: $u-cell-label-line-height;
+
+			&--large {
+				font-size: $u-cell-label-font-size-large;
+			}
+		}
+
+		&__value {
+			text-align: right;
+			font-size: $u-cell-value-font-size;
+			line-height: $u-cell-line-height;
+			color: $u-cell-value-color;
+
+			&--large {
+				font-size: $u-cell-value-font-size-large;
+			}
+		}
+
+		&--clickable {
+			background-color: $u-cell-clickable-color;
+		}
+
+		&--disabled {
+			color: $u-cell-disabled-color;
+			/* #ifndef APP-NVUE */
+			cursor: not-allowed;
+			/* #endif */
+		}
+
+		&--center {
+			align-items: center;
+		}
+	}
+</style>

+ 82 - 0
uview-ui/components/u-checkbox-group/props.js

@@ -0,0 +1,82 @@
+export default {
+    props: {
+        // 标识符
+        name: {
+            type: String,
+            default: uni.$u.props.checkboxGroup.name
+        },
+        // 绑定的值
+        value: {
+            type: Array,
+            default: uni.$u.props.checkboxGroup.value
+        },
+        // 形状,circle-圆形,square-方形
+        shape: {
+            type: String,
+            default: uni.$u.props.checkboxGroup.shape
+        },
+        // 是否禁用全部checkbox
+        disabled: {
+            type: Boolean,
+            default: uni.$u.props.checkboxGroup.disabled
+        },
+
+        // 选中状态下的颜色,如设置此值,将会覆盖parent的activeColor值
+        activeColor: {
+            type: String,
+            default: uni.$u.props.checkboxGroup.activeColor
+        },
+        // 未选中的颜色
+        inactiveColor: {
+            type: String,
+            default: uni.$u.props.checkboxGroup.inactiveColor
+        },
+
+        // 整个组件的尺寸,默认px
+        size: {
+            type: [String, Number],
+            default: uni.$u.props.checkboxGroup.size
+        },
+        // 布局方式,row-横向,column-纵向
+        placement: {
+            type: String,
+            default: uni.$u.props.checkboxGroup.placement
+        },
+        // label的字体大小,px单位
+        labelSize: {
+            type: [String, Number],
+            default: uni.$u.props.checkboxGroup.labelSize
+        },
+        // label的字体颜色
+        labelColor: {
+            type: [String],
+            default: uni.$u.props.checkboxGroup.labelColor
+        },
+        // 是否禁止点击文本操作
+        labelDisabled: {
+            type: Boolean,
+            default: uni.$u.props.checkboxGroup.labelDisabled
+        },
+        // 图标颜色
+        iconColor: {
+            type: String,
+            default: uni.$u.props.checkboxGroup.iconColor
+        },
+        // 图标的大小,单位px
+        iconSize: {
+            type: [String, Number],
+            default: uni.$u.props.checkboxGroup.iconSize
+        },
+        // 勾选图标的对齐方式,left-左边,right-右边
+        iconPlacement: {
+            type: String,
+            default: uni.$u.props.checkboxGroup.iconPlacement
+        },
+        // 竖向配列时,是否显示下划线
+        borderBottom: {
+            type: Boolean,
+            default: uni.$u.props.checkboxGroup.borderBottom
+        }
+
+    }
+}

+ 69 - 89
uview-ui/components/u-checkbox-group/u-checkbox-group.vue

@@ -1,123 +1,103 @@
 <template>
-	<view class="u-checkbox-group u-clearfix">
+	<view
+	    class="u-checkbox-group"
+	    :class="bemClass"
+	>
 		<slot></slot>
 	</view>
 </template>
 
 <script>
-	import Emitter from '../../libs/util/emitter.js';
+	import props from './props.js';
 	/**
-	 * checkboxGroup 开关选择器父组件Group
+	 * checkboxGroup 复选框组
 	 * @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状态发生变化时触发,回调为一个对象
+	 * @property {String}			name			标识符 
+	 * @property {Array}			value			绑定的值
+	 * @property {String}			shape			形状,circle-圆形,square-方形 (默认 'square' )
+	 * @property {Boolean}			disabled		是否禁用全部checkbox (默认 false )
+	 * @property {String}			activeColor		选中状态下的颜色,如设置此值,将会覆盖parent的activeColor值 (默认 '#2979ff' )
+	 * @property {String}			inactiveColor	未选中的颜色 (默认 '#c8c9cc' )
+	 * @property {String | Number}	size			整个组件的尺寸 单位px (默认 18 )
+	 * @property {String}			placement		布局方式,row-横向,column-纵向 (默认 'row' )
+	 * @property {String | Number}	labelSize		label的字体大小,px单位  (默认 14 )
+	 * @property {String}			labelColor		label的字体颜色 (默认 '#303133' )
+	 * @property {Boolean}			labelDisabled	是否禁止点击文本操作 (默认 false )
+	 * @property {String}			iconColor		图标颜色 (默认 '#ffffff' )
+	 * @property {String | Number}	iconSize		图标的大小,单位px (默认 12 )
+	 * @property {String}			iconPlacement	勾选图标的对齐方式,left-左边,right-右边  (默认 'left' )
+	 * @property {Boolean}			borderBottom	placement为row时,是否显示下边框 (默认 false )
+	 * @event {Function}	change	任一个checkbox状态发生变化时触发,回调为一个对象
+	 * @event {Function}	input	修改通过v-model绑定的值时触发,回调为一个对象
 	 * @example <u-checkbox-group></u-checkbox-group>
 	 */
 	export default {
 		name: 'u-checkbox-group',
-		mixins: [Emitter],
-		props: {
-			// 最多能选中多少个checkbox
-			max: {
-				type: [Number, String],
-				default: 999
+		mixins: [uni.$u.mpMixin, uni.$u.mixin,props],
+		computed: {
+			// 这里computed的变量,都是子组件u-checkbox需要用到的,由于头条小程序的兼容性差异,子组件无法实时监听父组件参数的变化
+			// 所以需要手动通知子组件,这里返回一个parentData变量,供watch监听,在其中去通知每一个子组件重新从父组件(u-checkbox-group)
+			// 拉取父组件新的变化后的参数
+			parentData() {
+				return [this.value, this.disabled, this.inactiveColor, this.activeColor, this.size, this.labelDisabled, this.shape,
+					this.iconSize, this.borderBottom, this.placement
+				]
 			},
-			// 所有选中项的 name
-			// value: {
-			// 	default: Array,
-			// 	default() {
-			// 		return []
-			// 	}
-			// },
-			// 是否禁用所有复选框
-			disabled: {
-				type: Boolean,
-				default: false
+			bemClass() {
+				// this.bem为一个computed变量,在mixin中
+				return this.bem('checkbox-group', ['placement'])
 			},
-			// 在表单内提交时的标识符
-			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
+		},
+		watch: {
+			// 当父组件需要子组件需要共享的参数发生了变化,手动通知子组件
+			parentData() {
+				if (this.children.length) {
+					this.children.map(child => {
+						// 判断子组件(u-checkbox)如果有init方法的话,就就执行(执行的结果是子组件重新从父组件拉取了最新的值)
+						typeof(child.init) === 'function' && child.init()
+					})
+				}
 			},
 		},
 		data() {
 			return {
+
 			}
 		},
 		created() {
-			// 如果将children定义在data中,在微信小程序会造成循环引用而报错
-			this.children = [];
+			this.children = []
 		},
 		methods: {
-			emitEvent() {
-				let values = [];
-				this.children.map(val => {
-					if(val.value) values.push(val.name);
+			// 将其他的checkbox设置为未选中的状态
+			unCheckedOther(childInstance) {
+				const values = []
+				this.children.map(child => {
+					// 将被选中的checkbox,放到数组中返回
+					if (child.isChecked) {
+						values.push(child.name)
+					}
 				})
-				this.$emit('change', values);
-				// 发出事件,用于在表单组件中嵌入checkbox的情况,进行验证
-				// 由于头条小程序执行迟钝,故需要用几十毫秒的延时
-				setTimeout(() => {
-					// 将当前的值发送到 u-form-item 进行校验
-					this.dispatch('u-form-item', 'on-form-change', values);
-				}, 60)
-			}
+				// 发出事件
+				this.$emit('change', values)
+				// 修改通过v-model绑定的值
+				this.$emit('input', values)
+			},
 		}
 	}
 </script>
 
 <style lang="scss" scoped>
-	@import "../../libs/css/style.components.scss";
+	@import "../../libs/css/components.scss";
 
 	.u-checkbox-group {
-		/* #ifndef MP || APP-NVUE */
-		display: inline-flex;
-		flex-wrap: wrap;
-		/* #endif */
+
+		&--row {
+			@include flex;
+		}
+
+		&--column {
+			@include flex(column);
+		}
 	}
 </style>

+ 69 - 0
uview-ui/components/u-checkbox/props.js

@@ -0,0 +1,69 @@
+export default {
+    props: {
+        // checkbox的名称
+        name: {
+            type: [String, Number, Boolean],
+            default: uni.$u.props.checkbox.name
+        },
+        // 形状,square为方形,circle为圆型
+        shape: {
+            type: String,
+            default: uni.$u.props.checkbox.shape
+        },
+        // 整体的大小
+        size: {
+            type: [String, Number],
+            default: uni.$u.props.checkbox.size
+        },
+        // 是否默认选中
+        checked: {
+            type: Boolean,
+            default: uni.$u.props.checkbox.checked
+        },
+        // 是否禁用
+        disabled: {
+            type: [String, Boolean],
+            default: uni.$u.props.checkbox.disabled
+        },
+        // 选中状态下的颜色,如设置此值,将会覆盖parent的activeColor值
+        activeColor: {
+            type: String,
+            default: uni.$u.props.checkbox.activeColor
+        },
+        // 未选中的颜色
+        inactiveColor: {
+            type: String,
+            default: uni.$u.props.checkbox.inactiveColor
+        },
+        // 图标的大小,单位px
+        iconSize: {
+            type: [String, Number],
+            default: uni.$u.props.checkbox.iconSize
+        },
+        // 图标颜色
+        iconColor: {
+            type: String,
+            default: uni.$u.props.checkbox.iconColor
+        },
+        // label提示文字,因为nvue下,直接slot进来的文字,由于特殊的结构,无法修改样式
+        label: {
+            type: [String, Number],
+            default: uni.$u.props.checkbox.label
+        },
+        // label的字体大小,px单位
+        labelSize: {
+            type: [String, Number],
+            default: uni.$u.props.checkbox.labelSize
+        },
+        // label的颜色
+        labelColor: {
+            type: String,
+            default: uni.$u.props.checkbox.labelColor
+        },
+        // 是否禁止点击提示语选中复选框
+        labelDisabled: {
+            type: [String, Boolean],
+            default: uni.$u.props.checkbox.labelDisabled
+        }
+    }
+}

+ 262 - 202
uview-ui/components/u-checkbox/u-checkbox.vue

@@ -1,283 +1,343 @@
 <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
+	    class="u-checkbox"
+	    :style="[checkboxStyle]"
+	    @tap.stop="wrapperClickHandler"
+	    :class="[`u-checkbox-label--${parentData.iconPlacement}`, parentData.borderBottom && parentData.placement === 'column' && 'u-border-bottom']"
+	>
+		<view
+		    class="u-checkbox__icon-wrap"
+		    @tap.stop="iconClickHandler"
+		    :class="iconClasses"
+		    :style="[iconWrapStyle]"
+		>
+			<slot name="icon">
+				<u-icon
+				    class="u-checkbox__icon-wrap__icon"
+				    name="checkbox-mark"
+				    :size="elIconSize"
+				    :color="elIconColor"
+				/>
+			</slot>
 		</view>
+		<text
+		    @tap.stop="labelClickHandler"
+		    :style="{
+				color: elDisabled ? elInactiveColor : elLabelColor,
+				fontSize: elLabelSize,
+				lineHeight: elLabelSize
+			}"
+		>{{label}}</text>
 	</view>
 </template>
 
 <script>
+	import props from './props.js';
 	/**
-	 * 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状态发生变化时触发,回调为一个对象
+	 * checkbox  复选框
+	 * @description 复选框组件一般用于需要多个选择的场景,该组件功能完整,使用方便
+	 * @tutorial https://uviewui.com/components/checkbox.html
+	 * @property {String | Number | Boolean}	name			checkbox组件的标示符
+	 * @property {String}						shape			形状,square为方形,circle为圆型
+	 * @property {String | Number}				size			整体的大小
+	 * @property {Boolean}						checked			是否默认选中
+	 * @property {String | Boolean}				disabled		是否禁用
+	 * @property {String}						activeColor		选中状态下的颜色,如设置此值,将会覆盖parent的activeColor值
+	 * @property {String}						inactiveColor	未选中的颜色
+	 * @property {String | Number}				iconSize		图标的大小,单位px
+	 * @property {String}						iconColor		图标颜色
+	 * @property {String | Number}				label			label提示文字,因为nvue下,直接slot进来的文字,由于特殊的结构,无法修改样式
+	 * @property {String}						labelColor 		label的颜色
+	 * @property {String | Number}				labelSize		label的字体大小,px单位
+	 * @property {String | Boolean}				labelDisabled	是否禁止点击提示语选中复选框
+	 * @property {Object}						customStyle		定义需要用到的外部样式
+	 * 
+	 * @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: ''
-			},
-		},
+		mixins: [uni.$u.mpMixin, uni.$u.mixin,props],
 		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);
+				isChecked: false,
+				// 父组件的默认值,因为头条小程序不支持在computed中使用this.parent.shape的形式
+				// 故只能使用如此方法
+				parentData: {
+					iconSize: 12,
+					labelDisabled: null,
+					disabled: null,
+					shape: 'square',
+					activeColor: null,
+					inactiveColor: null,
+					size: 18,
+					value: null,
+					iconColor: null,
+					placement: 'row',
+					borderBottom: false,
+					iconPlacement: 'left'
+				}
+			}
 		},
 		computed: {
-			// 是否禁用,如果父组件u-checkbox-group禁用的话,将会忽略子组件的配置
-			isDisabled() {
-				return this.disabled !== '' ? this.disabled : this.parent ? this.parent.disabled : false;
+			// 是否禁用,如果父组件u-raios-group禁用的话,将会忽略子组件的配置
+			elDisabled() {
+				return this.disabled !== '' ? this.disabled : this.parentData.disabled !== null ? this.parentData.disabled : false;
 			},
 			// 是否禁用label点击
-			isLabelDisabled() {
-				return this.labelDisabled !== '' ? this.labelDisabled : this.parent ? this.parent.labelDisabled : false;
+			elLabelDisabled() {
+				return this.labelDisabled !== '' ? this.labelDisabled : this.parentData.labelDisabled !== null ? this.parentData.labelDisabled :
+					false;
 			},
-			// 组件尺寸,对应size的值,默认值为34rpx
-			checkboxSize() {
-				return this.size ? this.size : (this.parent ? this.parent.size : 34);
+			// 组件尺寸,对应size的值,默认值为21px
+			elSize() {
+				return this.size ? this.size : (this.parentData.size ? this.parentData.size : 21);
 			},
-			// 组件的勾选图标的尺寸,默认20
-			checkboxIconSize() {
-				return this.iconSize ? this.iconSize : (this.parent ? this.parent.iconSize : 20);
+			// 组件的勾选图标的尺寸,默认12px
+			elIconSize() {
+				return this.iconSize ? this.iconSize : (this.parentData.iconSize ? this.parentData.iconSize : 12);
 			},
 			// 组件选中激活时的颜色
 			elActiveColor() {
-				return this.activeColor ? this.activeColor : (this.parent ? this.parent.activeColor : 'primary');
+				return this.activeColor ? this.activeColor : (this.parentData.activeColor ? this.parentData.activeColor : '#2979ff');
+			},
+			// 组件选未中激活时的颜色
+			elInactiveColor() {
+				return this.inactiveColor ? this.inactiveColor : (this.parentData.inactiveColor ? this.parentData.inactiveColor :
+					'#c8c9cc');
+			},
+			// label的颜色
+			elLabelColor() {
+				return this.labelColor ? this.labelColor : (this.parentData.labelColor ? this.parentData.labelColor : '#606266')
 			},
 			// 组件的形状
 			elShape() {
-				return this.shape ? this.shape : (this.parent ? this.parent.shape : 'square');
+				return this.shape ? this.shape : (this.parentData.shape ? this.parentData.shape : 'circle');
+			},
+			// label大小
+			elLabelSize() {
+				return uni.$u.addUnit(this.labelSize ? this.labelSize : (this.parentData.labelSize ? this.parentData.labelSize :
+					'15'))
 			},
-			iconStyle() {
-				let style = {};
-				// 既要判断是否手动禁用,还要判断用户v-model绑定的值,如果绑定为false,那么也无法选中
-				if (this.elActiveColor && this.value && !this.isDisabled) {
-					style.borderColor = this.elActiveColor; 
-					style.backgroundColor = this.elActiveColor;
+			elIconColor() {
+				const iconColor = this.iconColor ? this.iconColor : (this.parentData.iconColor ? this.parentData.iconColor :
+					'#ffffff');
+				// 图标的颜色
+				if (this.elDisabled) {
+					// disabled状态下,已勾选的checkbox图标改为elInactiveColor
+					return this.isChecked ? this.elInactiveColor : 'transparent'
+				} else {
+					return this.isChecked ? iconColor : 'transparent'
 				}
-				style.width = this.$u.addUnit(this.checkboxSize);
-				style.height = this.$u.addUnit(this.checkboxSize);
-				return style;
 			},
-			// checkbox内部的勾选图标,如果选中状态,为白色,否则为透明色即可
-			iconColor() {
-				return this.value ? '#ffffff' : 'transparent';
+			iconClasses() {
+				let classes = []
+				// 组件的形状
+				classes.push('u-checkbox__icon-wrap--' + this.elShape)
+				if (this.elDisabled) {
+					classes.push('u-checkbox__icon-wrap--disabled')
+				}
+				if (this.isChecked && this.elDisabled) {
+					classes.push('u-checkbox__icon-wrap--disabled--checked')
+				}
+				// 支付宝,头条小程序无法动态绑定一个数组类名,否则解析出来的结果会带有",",而导致失效
+				// #ifdef MP-ALIPAY || MP-TOUTIAO
+				classes = classes.join(' ')
+				// #endif
+				return classes
 			},
-			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(' ');
+			iconWrapStyle() {
+				// checkbox的整体样式
+				const style = {}
+				style.backgroundColor = this.isChecked && !this.elDisabled ? this.elActiveColor : '#ffffff'
+				style.borderColor = this.isChecked && !this.elDisabled ? this.elActiveColor : this.elInactiveColor
+				style.width = uni.$u.addUnit(this.elSize)
+				style.height = uni.$u.addUnit(this.elSize)
+				// 如果是图标在右边的话,移除它的右边距
+				if (this.parentData.iconPlacement === 'right') {
+					style.marginRight = 0
+				}
+				return style
 			},
 			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
+				const style = {}
+				if (this.parentData.borderBottom && this.parentData.placement === 'row') {
+					uni.$u.error('检测到您将borderBottom设置为true,需要同时将u-checkbox-group的placement设置为column才有效')
 				}
-				if(this.parent && this.parent.wrap) {
-					style.width = '100%';
-					// #ifndef MP
-					// H5和APP使用flex布局,将宽度设置100%,即可自动换行
-					style.flex = '0 0 100%';
-					// #endif
+				// 当父组件设置了显示下边框并且排列形式为纵向时,给内容和边框之间加上一定间隔
+				if (this.parentData.borderBottom && this.parentData.placement === 'column') {
+					style.paddingBottom = '8px'
 				}
-				return style;
+				return uni.$u.deepMerge(style, uni.$u.addStyle(this.customStyle))
 			}
 		},
+		mounted() {
+			this.init()
+		},
 		methods: {
-			onClickLabel() {
-				if (!this.isLabelDisabled && !this.isDisabled) {
-					this.setValue();
+			init() {
+				// 支付宝小程序不支持provide/inject,所以使用这个方法获取整个父组件,在created定义,避免循环引用
+				this.updateParentData()
+				if (!this.parent) {
+					uni.$u.error('u-checkbox必须搭配u-checkbox-group组件使用')
 				}
+				// 设置初始化时,是否默认选中的状态,父组件u-checkbox-group的value可能是array,所以额外判断
+				if (this.checked) {
+					this.isChecked = true
+				} else if (uni.$u.test.array(this.parentData.value)) {
+					// 查找数组是是否存在this.name元素值
+					this.isChecked = this.parentData.value.some(item => {
+						return item === this.name
+					})
+				}
+			},
+			updateParentData() {
+				this.getParentData('u-checkbox-group')
 			},
-			toggle() {
-				if (!this.isDisabled) {
-					this.setValue();
+			// 横向两端排列时,点击组件即可触发选中事件
+			wrapperClickHandler(e) {
+				this.parentData.iconPlacement === 'right' && this.iconClickHandler(e)
+			},
+			// 点击图标
+			iconClickHandler(e) {
+				this.preventEvent(e)
+				// 如果整体被禁用,不允许被点击
+				if (!this.elDisabled) {
+					this.setRadioCheckedStatus()
+				}
+			},
+			// 点击label
+			labelClickHandler(e) {
+				this.preventEvent(e)
+				// 如果按钮整体被禁用或者label被禁用,则不允许点击文字修改状态
+				if (!this.elLabelDisabled && !this.elDisabled) {
+					this.setRadioCheckedStatus()
 				}
 			},
 			emitEvent() {
-				this.$emit('change', {
-					value: !this.value,
-					name: this.name
+				this.$emit('change', this.isChecked)
+				// 尝试调用u-form的验证方法,进行一定延迟,否则微信小程序更新可能会不及时
+				this.$nextTick(() => {
+					uni.$u.formValidate(this, 'change')
 				})
-				// 执行父组件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);
-				}
+			// 改变组件选中状态
+			// 这里的改变的依据是,更改本组件的checked值为true,同时通过父组件遍历所有u-checkbox实例
+			// 将本组件外的其他u-checkbox的checked都设置为false(都被取消选中状态),因而只剩下一个为选中状态
+			setRadioCheckedStatus() {
+				// 将本组件标记为与原来相反的状态
+				this.isChecked = !this.isChecked
+				this.emitEvent()
+				typeof this.parent.unCheckedOther === 'function' && this.parent.unCheckedOther(this)
+			}
+		},
+		watch:{
+			checked(){
+				this.isChecked = this.checked
 			}
 		}
-	};
+	}
 </script>
 
 <style lang="scss" scoped>
-	@import "../../libs/css/style.components.scss";
+	@import "../../libs/css/components.scss";
+	$u-checkbox-icon-wrap-margin-right:6px !default;
+	$u-checkbox-icon-wrap-font-size:6px !default;
+	$u-checkbox-icon-wrap-border-width:1px !default;
+	$u-checkbox-icon-wrap-border-color:#c8c9cc !default;
+	$u-checkbox-icon-wrap-icon-line-height:0 !default;
+	$u-checkbox-icon-wrap-circle-border-radius:100% !default;
+	$u-checkbox-icon-wrap-square-border-radius:3px !default;
+	$u-checkbox-icon-wrap-checked-color:#fff !default;
+	$u-checkbox-icon-wrap-checked-background-color:red !default;
+	$u-checkbox-icon-wrap-checked-border-color:#2979ff !default;
+	$u-checkbox-icon-wrap-disabled-background-color:#ebedf0 !default;
+	$u-checkbox-icon-wrap-disabled-checked-color:#c8c9cc !default;
+	$u-checkbox-label-margin-left:5px !default;
+	$u-checkbox-label-margin-right:12px !default;
+	$u-checkbox-label-color:$u-content-color !default;
+	$u-checkbox-label-font-size:15px !default;
+	$u-checkbox-label-disabled-color:#c8c9cc !default;
 
 	.u-checkbox {
 		/* #ifndef APP-NVUE */
-		display: inline-flex;
+		@include flex(row);
 		/* #endif */
-		align-items: center;
 		overflow: hidden;
-		user-select: none;
-		line-height: 1.8;
-		
+		flex-direction: row;
+		align-items: center;
+
+		&-label--left {
+			flex-direction: row
+		}
+
+		&-label--right {
+			flex-direction: row-reverse;
+			justify-content: space-between
+		}
+
 		&__icon-wrap {
+			/* #ifndef APP-NVUE */
+			box-sizing: border-box;
+			// nvue下,border-color过渡有问题
+			transition-property: border-color, background-color, color;
+			transition-duration: 0.2s;
+			/* #endif */
 			color: $u-content-color;
-			flex: none;
-			display: -webkit-flex;
-			@include vue-flex;
+			@include 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;
-			
+			margin-right: $u-checkbox-icon-wrap-margin-right;
+
+			font-size: $u-checkbox-icon-wrap-font-size;
+			border-width: $u-checkbox-icon-wrap-border-width;
+			border-color: $u-checkbox-icon-wrap-border-color;
+			border-style: solid;
+
 			/* #ifdef MP-TOUTIAO */
 			// 头条小程序兼容性问题,需要设置行高为0,否则图标偏下
 			&__icon {
-				line-height: 0;
+				line-height: $u-checkbox-icon-wrap-icon-line-height;
 			}
+
 			/* #endif */
-			
+
 			&--circle {
-				border-radius: 100%;
+				border-radius: $u-checkbox-icon-wrap-circle-border-radius;
 			}
-			
+
 			&--square {
-				border-radius: 6rpx;
+				border-radius: $u-checkbox-icon-wrap-square-border-radius;
 			}
-			
+
 			&--checked {
-				color: #fff;
-				background-color: $u-type-primary;
-				border-color: $u-type-primary;
+				color: $u-checkbox-icon-wrap-checked-color;
+				background-color: $u-checkbox-icon-wrap-checked-background-color;
+				border-color: $u-checkbox-icon-wrap-checked-border-color;
 			}
-			
+
 			&--disabled {
-				background-color: #ebedf0;
-				border-color: #c8c9cc;
+				background-color: $u-checkbox-icon-wrap-disabled-background-color !important;
 			}
-			
+
 			&--disabled--checked {
-				color: #c8c9cc !important;
+				color: $u-checkbox-icon-wrap-disabled-checked-color !important;
 			}
 		}
-	
+
 		&__label {
+			/* #ifndef APP-NVUE */
 			word-wrap: break-word;
-			margin-left: 10rpx;
-			margin-right: 24rpx;
-			color: $u-content-color;
-			font-size: 30rpx;
-			
+			/* #endif */
+			margin-left: $u-checkbox-label-margin-left;
+			margin-right: $u-checkbox-label-margin-right;
+			color: $u-checkbox-label-color;
+			font-size: $u-checkbox-label-font-size;
+
 			&--disabled {
-				color: #c8c9cc;
+				color: $u-checkbox-label-disabled-color;
 			}
 		}
 	}

+ 8 - 0
uview-ui/components/u-circle-progress/props.js

@@ -0,0 +1,8 @@
+export default {
+    props: {
+        percentage: {
+            type: [String, Number],
+            default: uni.$u.props.circleProgress.percentage
+        }
+    }
+}

+ 181 - 203
uview-ui/components/u-circle-progress/u-circle-progress.vue

@@ -1,220 +1,198 @@
 <template>
-	<view
-		class="u-circle-progress"
-		:style="{
-			width: widthPx + 'px',
-			height: widthPx + 'px',
-			backgroundColor: bgColor
-		}"
-	>
-		<!-- 支付宝小程序不支持canvas-id属性,必须用id属性 -->
-		<canvas
-			class="u-canvas-bg"
-			:canvas-id="elBgId"
-			:id="elBgId"
-			:style="{
-				width: widthPx + 'px',
-				height: widthPx + 'px'
-			}"
-		></canvas>
-		<canvas
-			class="u-canvas"
-			:canvas-id="elId"
-			:id="elId"
-			:style="{
-				width: widthPx + 'px',
-				height: widthPx + 'px'
-			}"
-		></canvas>
-		<slot></slot>
+	<view class="u-circle-progress">
+		<view class="u-circle-progress__left">
+			<view
+			    class="u-circle-progress__left__circle"
+			    :style="[leftSyle]"
+			    ref="left-circle"
+			>
+
+			</view>
+		</view>
+		<view
+		    class="u-circle-progress__right"
+		>
+			<view
+			    class="u-circle-progress__right__circle"
+			    ref="right-circle"
+				:style="[rightSyle]"
+			>
+
+			</view>
+		</view>
+		<view class="u-circle-progress__circle">
+
+		</view>
 	</view>
 </template>
 
 <script>
-/**
- * circleProgress 环形进度条
- * @description 展示操作或任务的当前进度,比如上传文件,是一个圆形的进度条。注意:此组件的percent值只能动态增加,不能动态减少。
- * @tutorial https://www.uviewui.com/components/circleProgress.html
- * @property {String Number} percent 圆环进度百分比值,为数值类型,0-100
- * @property {String} inactive-color 圆环的底色,默认为灰色(该值无法动态变更)(默认#ececec)
- * @property {String} active-color 圆环激活部分的颜色(该值无法动态变更)(默认#19be6b)
- * @property {String Number} width 整个圆环组件的宽度,高度默认等于宽度值,单位rpx(默认200)
- * @property {String Number} border-width 圆环的边框宽度,单位rpx(默认14)
- * @property {String Number} duration 整个圆环执行一圈的时间,单位ms(默认呢1500)
- * @property {String} type 如设置,active-color值将会失效
- * @property {String} bg-color 整个组件背景颜色,默认为白色
- * @example <u-circle-progress active-color="#2979ff" :percent="80"></u-circle-progress>
- */
-export default {
-	name: 'u-circle-progress',
-	props: {
-		// 圆环进度百分比值
-		percent: {
-			type: Number,
-			default: 0,
-			// 限制值在0到100之间
-			validator: val => {
-				return val >= 0 && val <= 100;
+	import props from './props.js';
+	// #ifdef APP-NVUE
+	const animation = uni.requireNativePlugin('animation')
+	// #endif
+	/**
+	 * CircleProgress 圆形进度条 TODO: 待完善 
+	 * @description 展示操作或任务的当前进度,比如上传文件,是一个圆形的进度环。
+	 * @tutorial https://www.uviewui.com/components/circleProgress.html
+	 * @property {String | Number}	percentage	圆环进度百分比值,为数值类型,0-100 (默认 30 )
+	 * @example
+	 */
+	export default {
+		name: 'u-circle-progress',
+		mixins: [uni.$u.mpMixin, uni.$u.mixin,props],
+		data() {
+			return {
+				leftBorderColor: 'rgb(200, 200, 200)',
+				rightBorderColor: 'rgb(200, 200, 200)',
 			}
 		},
-		// 底部圆环的颜色(灰色的圆环)
-		inactiveColor: {
-			type: String,
-			default: '#ececec'
-		},
-		// 圆环激活部分的颜色
-		activeColor: {
-			type: String,
-			default: '#19be6b'
-		},
-		// 圆环线条的宽度,单位rpx
-		borderWidth: {
-			type: [Number, String],
-			default: 14
-		},
-		// 整个圆形的宽度,单位rpx
-		width: {
-			type: [Number, String],
-			default: 200
-		},
-		// 整个圆环执行一圈的时间,单位ms
-		duration: {
-			type: [Number, String],
-			default: 1500
+		computed: {
+			leftSyle() {
+				const style = {}
+				style.borderTopColor = this.leftBorderColor
+				style.borderRightColor = this.leftBorderColor
+				return style
+			},
+			rightSyle() {
+				const style = {}
+				style.borderLeftColor = this.rightBorderColor
+				style.borderBottomColor = this.rightBorderColor
+				return style
+			}
 		},
-		// 主题类型
-		type: {
-			type: String,
-			default: ''
+		mounted() {
+			uni.$u.sleep().then(() => {
+				this.rightBorderColor = 'rgb(66, 185, 131)'
+				// this.init()
+			})
 		},
-		// 整个圆环进度区域的背景色
-		bgColor: {
-			type: String,
-			default: '#ffffff'
-		}
-	},
-	data() {
-		return {
-			// #ifdef MP-WEIXIN
-			elBgId: 'uCircleProgressBgId', // 微信小程序中不能使用this.$u.guid()形式动态生成id值,否则会报错
-			elId: 'uCircleProgressElId',
-			// #endif
-			// #ifndef MP-WEIXIN
-			elBgId: this.$u.guid(), // 非微信端的时候,需用动态的id,否则一个页面多个圆形进度条组件数据会混乱
-			elId: this.$u.guid(),
-			// #endif
-			widthPx: uni.upx2px(this.width), // 转成px后的整个组件的背景宽度
-			borderWidthPx: uni.upx2px(this.borderWidth), // 转成px后的圆环的宽度
-			startAngle: -Math.PI / 2, // canvas画圆的起始角度,默认为3点钟方向,定位到12点钟方向
-			progressContext: null, // 活动圆的canvas上下文
-			newPercent: 0, // 当动态修改进度值的时候,保存进度值的变化前后值,用于比较用
-			oldPercent: 0 // 当动态修改进度值的时候,保存进度值的变化前后值,用于比较用
-		};
-	},
-	watch: {
-		percent(nVal, oVal = 0) {
-			if (nVal > 100) nVal = 100;
-			if (nVal < 0) oVal = 0;
-			// 此值其实等于this.percent,命名一个新
-			this.newPercent = nVal;
-			this.oldPercent = oVal;
-			setTimeout(() => {
-				// 无论是百分比值增加还是减少,需要操作还是原来的旧的百分比值
-				// 将此值减少或者新增到新的百分比值
-				this.drawCircleByProgress(oVal);
-			}, 50);
-		}
-	},
-	created() {
-		// 赋值,用于加载后第一个画圆使用
-		this.newPercent = this.percent;
-		this.oldPercent = 0;
-	},
-	computed: {
-		// 有type主题时,优先起作用
-		circleColor() {
-			if (['success', 'error', 'info', 'primary', 'warning'].indexOf(this.type) >= 0) return this.$u.color[this.type];
-			else return this.activeColor;
-		}
-	},
-	mounted() {
-		// 在h5端,必须要做一点延时才起作用,this.$nextTick()无效(HX2.4.7)
-		setTimeout(() => {
-			this.drawProgressBg();
-			this.drawCircleByProgress(this.oldPercent);
-		}, 50);
-	},
-	methods: {
-		drawProgressBg() {
-			let ctx = uni.createCanvasContext(this.elBgId, this);
-			ctx.setLineWidth(this.borderWidthPx); // 设置圆环宽度
-			ctx.setStrokeStyle(this.inactiveColor); // 线条颜色
-			ctx.beginPath(); // 开始描绘路径
-			// 设置一个原点(110,110),半径为100的圆的路径到当前路径
-			let radius = this.widthPx / 2;
-			ctx.arc(radius, radius, radius - this.borderWidthPx, 0, 2 * Math.PI, false);
-			ctx.stroke(); // 对路径进行描绘
-			ctx.draw();
-		},
-		drawCircleByProgress(progress) {
-			// 第一次操作进度环时将上下文保存到了this.data中,直接使用即可
-			let ctx = this.progressContext;
-			if (!ctx) {
-				ctx = uni.createCanvasContext(this.elId, this);
-				this.progressContext = ctx;
-			}
-			// 表示进度的两端为圆形
-			ctx.setLineCap('round');
-			// 设置线条的宽度和颜色
-			ctx.setLineWidth(this.borderWidthPx);
-			ctx.setStrokeStyle(this.circleColor);
-			// 将总过渡时间除以100,得出每修改百分之一进度所需的时间
-			let time = Math.floor(this.duration / 100);
-			// 结束角的计算依据为:将2π分为100份,乘以当前的进度值,得出终止点的弧度值,加起始角,为整个圆从默认的
-			// 3点钟方向开始画图,转为更好理解的12点钟方向开始作图,这需要起始角和终止角同时加上this.startAngle值
-			let endAngle = ((2 * Math.PI) / 100) * progress + this.startAngle;
-			ctx.beginPath();
-			// 半径为整个canvas宽度的一半
-			let radius = this.widthPx / 2;
-			ctx.arc(radius, radius, radius - this.borderWidthPx, this.startAngle, endAngle, false);
-			ctx.stroke();
-			ctx.draw();
-			// 如果变更后新值大于旧值,意味着增大了百分比
-			if (this.newPercent > this.oldPercent) {
-				// 每次递增百分之一
-				progress++;
-				// 如果新增后的值,大于需要设置的值百分比值,停止继续增加
-				if (progress > this.newPercent) return;
-			} else {
-				// 同理于上面
-				progress--;
-				if (progress < this.newPercent) return;
+		methods: {
+			init() {
+				animation.transition(this.$refs['right-circle'].ref, {
+					styles: {
+						transform: 'rotate(45deg)',
+						transformOrigin: 'center center'
+					},
+				}, () => {
+					this.rightBorderColor = 'rgb(66, 185, 131)'
+					// animation.transition(this.$refs['right-circle'].ref, {
+					// 	styles: {
+					// 		transform: 'rotate(225deg)',
+					// 		transformOrigin: 'center center'
+					// 	},
+					// 	duration: 3000,
+					// }, () => {
+					// 	animation.transition(this.$refs['left-circle'].ref, {
+					// 		styles: {
+					// 			transform: 'rotate(45deg)',
+					// 			transformOrigin: 'center center'
+					// 		},
+					// 	}, () => {
+					// 		this.leftBorderColor = 'rgb(66, 185, 131)'
+					// 		animation.transition(this.$refs['left-circle'].ref, {
+					// 			styles: {
+					// 				transform: 'rotate(225deg)',
+					// 				transformOrigin: 'center center'
+					// 			},
+					// 			duration: 1500,
+					// 		}, () => {
+
+					// 		})
+					// 	})
+					// })
+				})
+
 			}
-			setTimeout(() => {
-				// 定时器,每次操作间隔为time值,为了让进度条有动画效果
-				this.drawCircleByProgress(progress);
-			}, time);
-		}
+		},
 	}
-};
 </script>
 
 <style lang="scss" scoped>
-@import "../../libs/css/style.components.scss";
-.u-circle-progress {
-	position: relative;
-	/* #ifndef APP-NVUE */
-	display: inline-flex;		
-	/* #endif */
-	align-items: center;
-	justify-content: center;
-}
+	@import "../../libs/css/components.scss";
 
-.u-canvas-bg {
-	position: absolute;
-}
+	.u-circle-progress {
+		@include flex(row);
+		position: relative;
+		border-radius: 100px;
+		height: 100px;
+		width: 100px;
+		// transform: rotate(0deg);
+		// background-color: rgb(66, 185, 131);
+		background-color: rgb(200, 200, 200);
+		overflow: hidden;
+		justify-content: space-between;
 
-.u-canvas {
-	position: absolute;
-}
+		&__circle {
+			border-radius: 100px;
+			height: 90px;
+			width: 90px;
+			transform: translate(-50%, -50%);
+			background-color: rgb(255, 255, 255);
+			left: 50px;
+			top: 50px;
+			position: absolute;
+		}
+
+		&__left {
+			position: absolute;
+			left: 0;
+			width: 50px;
+			height: 100px;
+			overflow: hidden;
+			box-sizing: border-box;
+			// background-color: rgb(66, 185, 131);
+			// background-color: rgb(200, 200, 200);
+			// transform-origin: left center;
+
+			&__circle {
+				box-sizing: border-box;
+				// background-color: red;
+				border-left-color: transparent;
+				border-bottom-color: transparent;
+				border-top-left-radius: 50px;
+				border-top-right-radius: 50px;
+				border-bottom-right-radius: 50px;
+				// border-left-color: rgb(66, 185, 131);
+				// border-bottom-color: rgb(66, 185, 131);
+				border-top-color: rgb(66, 185, 131);
+				border-right-color: rgb(66, 185, 131);
+				border-width: 5px;
+				width: 100px;
+				height: 100px;
+				transform: rotate(225deg);
+				// border-radius: 100px;
+			}
+		}
+
+		&__right {
+			position: absolute;
+			right: 0;
+			width: 50px;
+			height: 100px;
+			overflow: hidden;
+
+			&__circle {
+				position: absolute;
+				right: 0;
+				box-sizing: border-box;
+				// background-color: red;
+				border-top-color: transparent;
+				border-right-color: transparent;
+				border-top-left-radius: 50px;
+				border-bottom-left-radius: 50px;
+				border-bottom-right-radius: 50px;
+				// border-left-color: rgb(66, 185, 131);
+				// border-bottom-color: rgb(66, 185, 131);
+				border-left-color: rgb(200, 200, 200);
+				border-bottom-color: rgb(200, 200, 200);
+				border-width: 5px;
+				width: 100px;
+				height: 100px;
+				transform: rotate(45deg);
+				transform-origin: center center;
+				// border-radius: 100px;
+			}
+		}
+	}
 </style>

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

@@ -1,147 +0,0 @@
-<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>

+ 79 - 0
uview-ui/components/u-code-input/props.js

@@ -0,0 +1,79 @@
+export default {
+    props: {
+		// 键盘弹起时,是否自动上推页面
+		adjustPosition: {
+			type: Boolean,
+            default: uni.$u.props.codeInput.adjustPosition
+		},
+        // 最大输入长度
+        maxlength: {
+            type: [String, Number],
+            default: uni.$u.props.codeInput.maxlength
+        },
+        // 是否用圆点填充
+        dot: {
+            type: Boolean,
+            default: uni.$u.props.codeInput.dot
+        },
+        // 显示模式,box-盒子模式,line-底部横线模式
+        mode: {
+            type: String,
+            default: uni.$u.props.codeInput.mode
+        },
+        // 是否细边框
+        hairline: {
+            type: Boolean,
+            default: uni.$u.props.codeInput.hairline
+        },
+        // 字符间的距离
+        space: {
+            type: [String, Number],
+            default: uni.$u.props.codeInput.space
+        },
+        // 预置值
+        value: {
+            type: [String, Number],
+            default: uni.$u.props.codeInput.value
+        },
+        // 是否自动获取焦点
+        focus: {
+            type: Boolean,
+            default: uni.$u.props.codeInput.focus
+        },
+        // 字体是否加粗
+        bold: {
+            type: Boolean,
+            default: uni.$u.props.codeInput.bold
+        },
+        // 字体颜色
+        color: {
+            type: String,
+            default: uni.$u.props.codeInput.color
+        },
+        // 字体大小
+        fontSize: {
+            type: [String, Number],
+            default: uni.$u.props.codeInput.fontSize
+        },
+        // 输入框的大小,宽等于高
+        size: {
+            type: [String, Number],
+            default: uni.$u.props.codeInput.size
+        },
+        // 是否隐藏原生键盘,如果想用自定义键盘的话,需设置此参数为true
+        disabledKeyboard: {
+            type: Boolean,
+            default: uni.$u.props.codeInput.disabledKeyboard
+        },
+        // 边框和线条颜色
+        borderColor: {
+            type: String,
+            default: uni.$u.props.codeInput.borderColor
+        },
+		// 是否禁止输入"."符号
+		disabledDot: {
+			type: Boolean,
+			default: uni.$u.props.codeInput.disabledDot
+		}
+    }
+}

+ 252 - 0
uview-ui/components/u-code-input/u-code-input.vue

@@ -0,0 +1,252 @@
+<template>
+	<view class="u-code-input">
+		<view
+			class="u-code-input__item"
+			:style="[itemStyle(index)]"
+			v-for="(item, index) in codeLength"
+			:key="index"
+		>
+			<view
+				class="u-code-input__item__dot"
+				v-if="dot && codeArray.length > index"
+			></view>
+			<text
+				v-else
+				:style="{
+					fontSize: $u.addUnit(fontSize),
+					fontWeight: bold ? 'bold' : 'normal',
+					color: color
+				}"
+			>{{codeArray[index]}}</text>
+			<view
+				class="u-code-input__item__line"
+				v-if="mode === 'line'"
+				:style="[lineStyle]"
+			></view>
+			<!-- #ifndef APP-PLUS -->
+			<view v-if="isFocus && codeArray.length === index" :style="{backgroundColor: color}" class="u-code-input__item__cursor"></view>
+			<!-- #endif -->
+		</view>
+		<input
+			:disabled="disabledKeyboard"
+			type="number"
+			:focus="focus"
+			:value="inputValue"
+			:maxlength="maxlength"
+			:adjustPosition="adjustPosition"
+			class="u-code-input__input"
+			@input="inputHandler"
+			:style="{
+				height: $u.addUnit(size) 
+			}"
+			@focus="isFocus = true"
+			@blur="isFocus = false"
+		/>
+	</view>
+</template>
+
+<script>
+	import props from './props.js';
+	/**
+	 * CodeInput 验证码输入
+	 * @description 该组件一般用于验证用户短信验证码的场景,也可以结合uView的键盘组件使用
+	 * @tutorial https://www.uviewui.com/components/codeInput.html
+	 * @property {String | Number}	maxlength			最大输入长度 (默认 6 )
+	 * @property {Boolean}			dot					是否用圆点填充 (默认 false )
+	 * @property {String}			mode				显示模式,box-盒子模式,line-底部横线模式 (默认 'box' )
+	 * @property {Boolean}			hairline			是否细边框 (默认 false )
+	 * @property {String | Number}	space				字符间的距离 (默认 10 )
+	 * @property {String | Number}	value				预置值
+	 * @property {Boolean}			focus				是否自动获取焦点 (默认 false )
+	 * @property {Boolean}			bold				字体和输入横线是否加粗 (默认 false )
+	 * @property {String}			color				字体颜色 (默认 '#606266' )
+	 * @property {String | Number}	fontSize			字体大小,单位px (默认 18 )
+	 * @property {String | Number}	size				输入框的大小,宽等于高 (默认 35 )
+	 * @property {Boolean}			disabledKeyboard	是否隐藏原生键盘,如果想用自定义键盘的话,需设置此参数为true (默认 false )
+	 * @property {String}			borderColor			边框和线条颜色 (默认 '#c9cacc' )
+	 * @property {Boolean}			disabledDot			是否禁止输入"."符号 (默认 true )
+	 * 
+	 * @event {Function}	change	输入内容发生改变时触发,具体见上方说明			value:当前输入的值
+	 * @event {Function}	finish	输入字符个数达maxlength值时触发,见上方说明	value:当前输入的值
+	 * @example	<u-code-input v-model="value4" :focus="true"></u-code-input>
+	 */
+	export default {
+		name: 'u-code-input',
+		mixins: [uni.$u.mpMixin, uni.$u.mixin, props],
+		data() {
+			return {
+				inputValue: '',
+				isFocus: this.focus
+			}
+		},
+		watch: {
+			value: {
+				immediate: true,
+				handler(val) {
+					// 转为字符串,超出部分截掉
+					this.inputValue = String(val).substring(0, this.maxlength)
+				}
+			},
+		},
+		computed: {
+			// 根据长度,循环输入框的个数,因为头条小程序数值不能用于v-for
+			codeLength() {
+				return new Array(Number(this.maxlength))
+			},
+			// 循环item的样式
+			itemStyle() {
+				return index => {
+					const addUnit = uni.$u.addUnit
+					const style = {
+						width: addUnit(this.size),
+						height: addUnit(this.size)
+					}
+					// 盒子模式下,需要额外进行处理
+					if (this.mode === 'box') {
+						// 设置盒子的边框,如果是细边框,则设置为0.5px宽度
+						style.border = `${this.hairline ? 0.5 : 1}px solid ${this.borderColor}`
+						// 如果盒子间距为0的话
+						if (uni.$u.getPx(this.space) === 0) {
+							// 给第一和最后一个盒子设置圆角
+							if (index === 0) {
+								style.borderTopLeftRadius = '3px'
+								style.borderBottomLeftRadius = '3px'
+							}
+							if (index === this.codeLength.length - 1) {
+								style.borderTopRightRadius = '3px'
+								style.borderBottomRightRadius = '3px'
+							}
+							// 最后一个盒子的右边框需要保留
+							if (index !== this.codeLength.length - 1) {
+								style.borderRight = 'none'
+							}
+						}
+					}
+					if (index !== this.codeLength.length - 1) {
+						// 设置验证码字符之间的距离,通过margin-right设置,最后一个字符,无需右边框
+						style.marginRight = addUnit(this.space)
+					} else {
+						// 最后一个盒子的有边框需要保留
+						style.marginRight = 0
+					}
+
+					return style
+				}
+			},
+			// 将输入的值,转为数组,给item历遍时,根据当前的索引显示数组的元素
+			codeArray() {
+				return String(this.inputValue).split('')
+			},
+			// 下划线模式下,横线的样式
+			lineStyle() {
+				const style = {}
+				style.height = this.hairline ? '2px' : '4px'
+				style.width = uni.$u.addUnit(this.size)
+				// 线条模式下,背景色即为边框颜色
+				style.backgroundColor = this.borderColor
+				return style
+			}
+		},
+		methods: {
+			// 监听输入框的值发生变化
+			inputHandler(e) {
+				const value = e.detail.value
+				this.inputValue = value
+				// 是否允许输入“.”符号
+				if(this.disabledDot) {
+					this.$nextTick(() => {
+						this.inputValue = value.replace('.', '')
+					})
+				}
+				// 未达到maxlength之前,发送change事件,达到后发送finish事件
+				this.$emit('change', value)
+				// 修改通过v-model双向绑定的值
+				this.$emit('input', value)
+				// 达到用户指定输入长度时,发出完成事件
+				if (String(value).length >= Number(this.maxlength)) {
+					this.$emit('finish', value)
+				}
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	@import "../../libs/css/components.scss";
+	$u-code-input-cursor-width: 1px;
+	$u-code-input-cursor-height: 40%;
+	$u-code-input-cursor-animation-duration: 1s;
+	$u-code-input-cursor-animation-name: u-cursor-flicker;
+
+	.u-code-input {
+		@include flex;
+		position: relative;
+		overflow: hidden;
+
+		&__item {
+			@include flex;
+			justify-content: center;
+			align-items: center;
+			position: relative;
+
+			&__text {
+				font-size: 15px;
+				color: $u-content-color;
+			}
+
+			&__dot {
+				width: 7px;
+				height: 7px;
+				border-radius: 100px;
+				background-color: $u-content-color;
+			}
+
+			&__line {
+				position: absolute;
+				bottom: 0;
+				height: 4px;
+				border-radius: 100px;
+				width: 40px;
+				background-color: $u-content-color;
+			}
+			/* #ifndef APP-PLUS */
+			&__cursor {
+				position: absolute;
+				top: 50%;
+				left: 50%;
+				transform: translate(-50%,-50%);
+				width: $u-code-input-cursor-width;
+				height: $u-code-input-cursor-height;
+				animation: $u-code-input-cursor-animation-duration u-cursor-flicker infinite;
+			}
+			/* #endif */
+			
+		}
+
+		&__input {
+			// 之所以需要input输入框,是因为有它才能唤起键盘
+			// 这里将它设置为两倍的屏幕宽度,再将左边的一半移出屏幕,为了不让用户看到输入的内容
+			position: absolute;
+			left: -750rpx;
+			width: 1500rpx;
+			top: 0;
+			background-color: transparent;
+			text-align: left;
+		}
+	}
+	
+	/* #ifndef APP-PLUS */
+	@keyframes u-cursor-flicker {
+		0% {
+		    opacity: 0;
+		}
+		50% {
+		    opacity: 1;
+		}
+		100% {
+		    opacity: 0;
+		}
+	}
+	/* #endif */
+
+</style>

+ 34 - 0
uview-ui/components/u-code/props.js

@@ -0,0 +1,34 @@
+export default {
+    props: {
+        // 倒计时总秒数
+        seconds: {
+            type: [String, Number],
+            default: uni.$u.props.code.seconds
+        },
+        // 尚未开始时提示
+        startText: {
+            type: String,
+            default: uni.$u.props.code.startText
+        },
+        // 正在倒计时中的提示
+        changeText: {
+            type: String,
+            default: uni.$u.props.code.changeText
+        },
+        // 倒计时结束时的提示
+        endText: {
+            type: String,
+            default: uni.$u.props.code.endText
+        },
+        // 是否在H5刷新或各端返回再进入时继续倒计时
+        keepRunning: {
+            type: Boolean,
+            default: uni.$u.props.code.keepRunning
+        },
+        // 为了区分多个页面,或者一个页面多个倒计时组件本地存储的继续倒计时变了
+        uniqueKey: {
+            type: String,
+            default: uni.$u.props.code.uniqueKey
+        }
+    }
+}

+ 51 - 86
uview-ui/components/u-verification-code/u-verification-code.vue → uview-ui/components/u-code/u-code.vue

@@ -1,58 +1,30 @@
 <template>
-	<view class="u-code-wrap">
+	<view class="u-code">
 		<!-- 此组件功能由js完成,无需写html逻辑 -->
 	</view>
 </template>
 
 <script>
+	import props from './props.js';
 	/**
-	 * verificationCode 验证码输入框
+	 * Code 验证码输入框
 	 * @description 考虑到用户实际发送验证码的场景,可能是一个按钮,也可能是一段文字,提示语各有不同,所以本组件 不提供界面显示,只提供提示语,由用户将提示语嵌入到具体的场景
-	 * @tutorial https://www.uviewui.com/components/verificationCode.html
-	 * @property {Number String} seconds 倒计时所需的秒数(默认60)
-	 * @property {String} start-text 开始前的提示语,见官网说明(默认获取验证码)
-	 * @property {String} change-text 倒计时期间的提示语,必须带有字母"x",见官网说明(默认X秒重新获取)
-	 * @property {String} end-text 倒计结束的提示语,见官网说明(默认重新获取)
-	 * @property {Boolean} keep-running 是否在H5刷新或各端返回再进入时继续倒计时(默认false)
-	 * @event {Function} change 倒计时期间,每秒触发一次
-	 * @event {Function} start 开始倒计时触发
-	 * @event {Function} end 结束倒计时触发
-	 * @example <u-verification-code :seconds="seconds" @end="end" @start="start" ref="uCode" 
+	 * @tutorial https://www.uviewui.com/components/code.html
+	 * @property {String | Number}	seconds			倒计时所需的秒数(默认 60 )
+	 * @property {String}			startText		开始前的提示语,见官网说明(默认 '获取验证码' )
+	 * @property {String}			changeText		倒计时期间的提示语,必须带有字母"x",见官网说明(默认 'X秒重新获取' )
+	 * @property {String}			endText			倒计结束的提示语,见官网说明(默认 '重新获取' )
+	 * @property {Boolean}			keepRunning		是否在H5刷新或各端返回再进入时继续倒计时( 默认false )
+	 * @property {String}			uniqueKey		为了区分多个页面,或者一个页面多个倒计时组件本地存储的继续倒计时变了
+	 * 
+	 * @event {Function}	change	倒计时期间,每秒触发一次
+	 * @event {Function}	start	开始倒计时触发
+	 * @event {Function}	end		结束倒计时触发
+	 * @example <u-code ref="uCode" @change="codeChange" seconds="20"></u-code> 
 	 */
 	export default {
-		name: "u-verification-code",
-		props: {
-			// 倒计时总秒数
-			seconds: {
-				type: [String, Number],
-				default: 60
-			},
-			// 尚未开始时提示
-			startText: {
-				type: String,
-				default: '获取验证码'
-			},
-			// 正在倒计时中的提示
-			changeText: {
-				type: String,
-				default: 'X秒重新获取'
-			},
-			// 倒计时结束时的提示
-			endText: {
-				type: String,
-				default: '重新获取'
-			},
-			// 是否在H5刷新或各端返回再进入时继续倒计时
-			keepRunning: {
-				type: Boolean,
-				default: false
-			},
-			// 为了区分多个页面,或者一个页面多个倒计时组件本地存储的继续倒计时变了
-			uniqueKey: {
-				type: String,
-				default: ''
-			}
-		},
+		name: "u-code",
+		mixins: [uni.$u.mpMixin, uni.$u.mixin,props],
 		data() {
 			return {
 				secNum: this.seconds,
@@ -61,80 +33,80 @@
 			}
 		},
 		mounted() {
-			this.checkKeepRunning();
+			this.checkKeepRunning()
 		},
 		watch: {
 			seconds: {
 				immediate: true,
 				handler(n) {
-					this.secNum = n;
+					this.secNum = n
 				}
 			}
 		},
 		methods: {
 			checkKeepRunning() {
 				// 获取上一次退出页面(H5还包括刷新)时的时间戳,如果没有上次的保存,此值可能为空
-				let lastTimestamp = Number(uni.getStorageSync(this.uniqueKey + '_$uCountDownTimestamp'));
-				if(!lastTimestamp) return this.changeEvent(this.startText);
+				let lastTimestamp = Number(uni.getStorageSync(this.uniqueKey + '_$uCountDownTimestamp'))
+				if(!lastTimestamp) return this.changeEvent(this.startText)
 				// 当前秒的时间戳
-				let nowTimestamp = Math.floor((+ new Date()) / 1000);
+				let nowTimestamp = Math.floor((+ new Date()) / 1000)
 				// 判断当前的时间戳,是否小于上一次的本该按设定结束,却提前结束的时间戳
 				if(this.keepRunning && lastTimestamp && lastTimestamp > nowTimestamp) {
 					// 剩余尚未执行完的倒计秒数
-					this.secNum = lastTimestamp - nowTimestamp;
+					this.secNum = lastTimestamp - nowTimestamp
 					// 清除本地保存的变量
-					uni.removeStorageSync(this.uniqueKey + '_$uCountDownTimestamp');
+					uni.removeStorageSync(this.uniqueKey + '_$uCountDownTimestamp')
 					// 开始倒计时
-					this.start();
+					this.start()
 				} else {
 					// 如果不存在需要继续上一次的倒计时,执行正常的逻辑
-					this.changeEvent(this.startText);
+					this.changeEvent(this.startText)
 				}
 			},
 			// 开始倒计时
 			start() {
 				// 防止快速点击获取验证码的按钮而导致内部产生多个定时器导致混乱
 				if(this.timer) {
-					clearInterval(this.timer);
-					this.timer = null;
+					clearInterval(this.timer)
+					this.timer = null
 				}
-				this.$emit('start');
-				this.canGetCode = false;
+				this.$emit('start')
+				this.canGetCode = false
 				// 这里放这句,是为了一开始时就提示,否则要等setInterval的1秒后才会有提示
-				this.changeEvent(this.changeText.replace(/x|X/, this.secNum));
-				this.setTimeToStorage();
+				this.changeEvent(this.changeText.replace(/x|X/, this.secNum))
+				this.setTimeToStorage()
 				this.timer = setInterval(() => {
 					if (--this.secNum) {
 						// 用当前倒计时的秒数替换提示字符串中的"x"字母
-						this.changeEvent(this.changeText.replace(/x|X/, this.secNum));
+						this.changeEvent(this.changeText.replace(/x|X/, this.secNum))
 					} else {
-						clearInterval(this.timer);
-						this.timer = null;
-						this.changeEvent(this.endText);
-						this.secNum = this.seconds;
-						this.$emit('end');
-						this.canGetCode = true;
+						clearInterval(this.timer)
+						this.timer = null
+						this.changeEvent(this.endText)
+						this.secNum = this.seconds
+						this.$emit('end')
+						this.canGetCode = true
 					}
-				}, 1000);
+				}, 1000)
 			},
 			// 重置,可以让用户再次获取验证码
 			reset() {
-				this.canGetCode = true;
-				clearInterval(this.timer);
-				this.secNum = this.seconds;
-				this.changeEvent(this.endText);
+				this.canGetCode = true
+				clearInterval(this.timer)
+				this.secNum = this.seconds
+				this.changeEvent(this.endText)
 			},
 			changeEvent(text) {
-				this.$emit('change', text);
+				this.$emit('change', text)
 			},
 			// 保存时间戳,为了防止倒计时尚未结束,H5刷新或者各端的右上角返回上一页再进来
 			setTimeToStorage() {
-				if(!this.keepRunning || !this.timer) return;
+				if(!this.keepRunning || !this.timer) return
 				// 记录当前的时间戳,为了下次进入页面,如果还在倒计时内的话,继续倒计时
 				// 倒计时尚未结束,结果大于0;倒计时已经开始,就会小于初始值,如果等于初始值,说明没有开始倒计时,无需处理
 				if(this.secNum > 0 && this.secNum <= this.seconds) {
 					// 获取当前时间戳(+ new Date()为特殊写法),除以1000变成秒,再去除小数部分
-					let nowTimestamp = Math.floor((+ new Date()) / 1000);
+					let nowTimestamp = Math.floor((+ new Date()) / 1000)
 					// 将本该结束时候的时间戳保存起来 => 当前时间戳 + 剩余的秒数
 					uni.setStorage({
 						key: this.uniqueKey + '_$uCountDownTimestamp',
@@ -145,20 +117,13 @@
 		},
 		// 组件销毁的时候,清除定时器,否则定时器会继续存在,系统不会自动清除
 		beforeDestroy() {
-			this.setTimeToStorage();
-			clearTimeout(this.timer);
-			this.timer = null;
+			this.setTimeToStorage()
+			clearTimeout(this.timer)
+			this.timer = null
 		}
 	}
 </script>
 
 <style lang="scss" scoped>
-	@import "../../libs/css/style.components.scss";
-	
-	.u-code-wrap {
-		width: 0;
-		height: 0;
-		position: fixed;
-		z-index: -1;
-	}
+	@import "../../libs/css/components.scss";
 </style>

+ 29 - 0
uview-ui/components/u-col/props.js

@@ -0,0 +1,29 @@
+export default {
+    props: {
+        // 占父容器宽度的多少等分,总分为12份
+        span: {
+            type: [String, Number],
+            default: uni.$u.props.col.span
+        },
+        // 指定栅格左侧的间隔数(总12栏)
+        offset: {
+            type: [String, Number],
+            default: uni.$u.props.col.offset
+        },
+        // 水平排列方式,可选值为`start`(或`flex-start`)、`end`(或`flex-end`)、`center`、`around`(或`space-around`)、`between`(或`space-between`)
+        justify: {
+            type: String,
+            default: uni.$u.props.col.justify
+        },
+        // 垂直对齐方式,可选值为top、center、bottom、stretch
+        align: {
+            type: String,
+            default: uni.$u.props.col.align
+        },
+        // 文字对齐方式
+        textAlign: {
+            type: String,
+            default: uni.$u.props.col.textAlign
+        }
+    }
+}

+ 80 - 74
uview-ui/components/u-col/u-col.vue

@@ -1,107 +1,111 @@
 <template>
-	<view class="u-col" :class="[
-		'u-col-' + span
-	]" :style="{
-		padding: `0 ${Number(gutter)/2 + 'rpx'}`,
-		marginLeft: 100 / 12 * offset + '%',
-		flex: `0 0 ${100 / 12 * span}%`,
-		alignItems: uAlignItem,
-		justifyContent: uJustify,
-		textAlign: textAlign
-	}"
-	 @tap="click">
+	<view
+	    class="u-col"
+		ref="u-col"
+	    :class="[
+			'u-col-' + span
+		]"
+	    :style="[colStyle]"
+	    @tap="clickHandler"
+	>
 		<slot></slot>
 	</view>
 </template>
 
 <script>
+	import props from './props.js';
 	/**
-	 * col 布局单元格
-	 * @description 通过基础的 12 分栏,迅速简便地创建布局(搭配<u-row>使用)
-	 * @tutorial https://www.uviewui.com/components/layout.html
-	 * @property {String Number} span 栅格占据的列数,总12等分(默认0)
-	 * @property {String} text-align 文字水平对齐方式(默认left)
-	 * @property {String Number} offset 分栏左边偏移,计算方式与span相同(默认0)
-	 * @example <u-col span="3"><view class="demo-layout bg-purple"></view></u-col>
+	 * CodeInput 栅格系统的列 
+	 * @description 该组件一般用于Layout 布局 通过基础的 12 分栏,迅速简便地创建布局
+	 * @tutorial https://www.uviewui.com/components/Layout.html
+	 * @property {String | Number}	span		栅格占据的列数,总12等份 (默认 12 ) 
+	 * @property {String | Number}	offset		分栏左边偏移,计算方式与span相同 (默认 0 ) 
+	 * @property {String}			justify		水平排列方式,可选值为`start`(或`flex-start`)、`end`(或`flex-end`)、`center`、`around`(或`space-around`)、`between`(或`space-between`)  (默认 'start' ) 
+	 * @property {String}			align		垂直对齐方式,可选值为top、center、bottom、stretch (默认 'stretch' ) 
+	 * @property {String}			textAlign	文字水平对齐方式 (默认 'left' ) 
+	 * @property {Object}			customStyle	定义需要用到的外部样式
+	 * @event {Function}	click	col被点击,会阻止事件冒泡到row
+	 * @example	 <u-col  span="3" offset="3" > <view class="demo-layout bg-purple"></view> </u-col>
 	 */
 	export default {
-		name: "u-col",
-		props: {
-			// 占父容器宽度的多少等分,总分为12份
-			span: {
-				type: [Number, String],
-				default: 12
-			},
-			// 指定栅格左侧的间隔数(总12栏)
-			offset: {
-				type: [Number, String],
-				default: 0
-			},
-			// 水平排列方式,可选值为`start`(或`flex-start`)、`end`(或`flex-end`)、`center`、`around`(或`space-around`)、`between`(或`space-between`)
-			justify: {
-				type: String,
-				default: 'start'
-			},
-			// 垂直对齐方式,可选值为top、center、bottom
-			align: {
-				type: String,
-				default: 'center'
-			},
-			// 文字对齐方式
-			textAlign: {
-				type: String,
-				default: 'left'
-			},
-			// 是否阻止事件传播
-			stop: {
-				type: Boolean,
-				default: true
-			}
-		},
+		name: 'u-col',
+		mixins: [uni.$u.mpMixin, uni.$u.mixin, props],
 		data() {
 			return {
-				gutter: 20, // 给col添加间距,左右边距各占一半,从父组件u-row获取
-			}
-		},
-		created() {
-			this.parent = false;
-		},
-		mounted() {
-			// 获取父组件实例,并赋值给对应的参数
-			this.parent = this.$u.$parent.call(this, 'u-row');
-			if (this.parent) {
-				this.gutter = this.parent.gutter;
+				width: 0,
+				parentData: {
+					gutter: 0
+				},
+				gridNum: 12
 			}
 		},
 		computed: {
 			uJustify() {
-				if (this.justify == 'end' || this.justify == 'start') return 'flex-' + this.justify;
-				else if (this.justify == 'around' || this.justify == 'between') return 'space-' + this.justify;
-				else return this.justify;
+				if (this.justify == 'end' || this.justify == 'start') return 'flex-' + this.justify
+				else if (this.justify == 'around' || this.justify == 'between') return 'space-' + this.justify
+				else return this.justify
 			},
 			uAlignItem() {
-				if (this.align == 'top') return 'flex-start';
-				if (this.align == 'bottom') return 'flex-end';
-				else return this.align;
+				if (this.align == 'top') return 'flex-start'
+				if (this.align == 'bottom') return 'flex-end'
+				else return this.align
+			},
+			colStyle() {
+				const style = {
+					// 这里写成"padding: 0 10px"的形式是因为nvue的需要
+					paddingLeft: uni.$u.addUnit(uni.$u.getPx(this.parentData.gutter)/2),
+					paddingRight: uni.$u.addUnit(uni.$u.getPx(this.parentData.gutter)/2),
+					alignItems: this.uAlignItem,
+					justifyContent: this.uJustify,
+					textAlign: this.textAlign,
+					// #ifndef APP-NVUE
+					// 在非nvue上,使用百分比形式
+					flex: `0 0 ${100 / this.gridNum * this.span}%`,
+					marginLeft: 100 / 12 * this.offset + '%',
+					// #endif
+					// #ifdef APP-NVUE
+					// 在nvue上,由于无法使用百分比单位,这里需要获取父组件的宽度,再计算得出该有对应的百分比尺寸
+					width: uni.$u.addUnit(Math.floor(this.width / this.gridNum * Number(this.span))),
+					marginLeft: uni.$u.addUnit(Math.floor(this.width / this.gridNum * Number(this.offset))),
+					// #endif
+				}
+				return uni.$u.deepMerge(style, uni.$u.addStyle(this.customStyle))
 			}
 		},
+		mounted() {
+			this.init()
+		},
 		methods: {
-			click(e) {
+			async init() {
+				// 支付宝小程序不支持provide/inject,所以使用这个方法获取整个父组件,在created定义,避免循环引用
+				this.updateParentData()
+				this.width = await this.parent.getComponentWidth()
+			},
+			updateParentData() {
+				this.getParentData('u-row')
+			},
+			clickHandler(e) {
 				this.$emit('click');
 			}
-		}
+		},
 	}
 </script>
 
-<style lang="scss">
-	@import "../../libs/css/style.components.scss";
+<style lang="scss" scoped>
+	@import "../../libs/css/components.scss";
 
 	.u-col {
-		/* #ifdef MP-WEIXIN || MP-QQ || MP-TOUTIAO */
-		float: left;
+		padding: 0;
+		/* #ifndef APP-NVUE */
+		box-sizing:border-box;
+		/* #endif */
+		/* #ifdef MP */
+		display: block;
 		/* #endif */
 	}
 
+	// nvue下百分比无效
+	/* #ifndef APP-NVUE */
 	.u-col-0 {
 		width: 0;
 	}
@@ -153,4 +157,6 @@
 	.u-col-12 {
 		width: calc(100%/12 * 12);
 	}
+
+	/* #endif */
 </style>

+ 59 - 0
uview-ui/components/u-collapse-item/props.js

@@ -0,0 +1,59 @@
+export default {
+    props: {
+        // 标题
+        title: {
+            type: String,
+            default: uni.$u.props.collapseItem.title
+        },
+        // 标题右侧内容
+        value: {
+            type: String,
+            default: uni.$u.props.collapseItem.value
+        },
+        // 标题下方的描述信息
+        label: {
+            type: String,
+            default: uni.$u.props.collapseItem.label
+        },
+        // 是否禁用折叠面板
+        disabled: {
+            type: Boolean,
+            default: uni.$u.props.collapseItem.disabled
+        },
+        // 是否展示右侧箭头并开启点击反馈
+        isLink: {
+            type: Boolean,
+            default: uni.$u.props.collapseItem.isLink
+        },
+        // 是否开启点击反馈
+        clickable: {
+            type: Boolean,
+            default: uni.$u.props.collapseItem.clickable
+        },
+        // 是否显示内边框
+        border: {
+            type: Boolean,
+            default: uni.$u.props.collapseItem.border
+        },
+        // 标题的对齐方式
+        align: {
+            type: String,
+            default: uni.$u.props.collapseItem.align
+        },
+        // 唯一标识符
+        name: {
+            type: [String, Number],
+            default: uni.$u.props.collapseItem.name
+        },
+        // 标题左侧图片,可为绝对路径的图片或内置图标
+        icon: {
+            type: String,
+            default: uni.$u.props.collapseItem.icon
+        },
+        // 面板展开收起的过渡时间,单位ms
+        duration: {
+            type: Number,
+            default: uni.$u.props.collapseItem.duration
+        }
+    }
+}

+ 181 - 160
uview-ui/components/u-collapse-item/u-collapse-item.vue

@@ -1,204 +1,225 @@
 <template>
-	<view class="u-collapse-item" :style="[itemStyle]">
-		<view :hover-stay-time="200" class="u-collapse-head" @tap.stop="headClick" :hover-class="hoverClass" :style="[headStyle]">
-			<block v-if="!$slots['title-all']">
-				<view v-if="!$slots['title']" class="u-collapse-title u-line-1" :style="[{ textAlign: align ? align : 'left' },
-					isShow && activeStyle && !arrow ? activeStyle : '']">
-					{{ title }}
-				</view>
-				<slot v-else name="title" />
-				<view class="u-icon-wrap">
-					<u-icon v-if="arrow" :color="arrowColor" :class="{ 'u-arrow-down-icon-active': isShow }"
-					 class="u-arrow-down-icon" name="arrow-down"></u-icon>
-				</view>
-			</block>
-			<slot v-else name="title-all" />
-		</view>
-		<view class="u-collapse-body" :style="[{
-				height: isShow ? height + 'px' : '0'
-			}]">
-			<view class="u-collapse-content" :id="elId" :style="[bodyStyle]">
-				<slot></slot>
-			</view>
+	<view class="u-collapse-item">
+		<u-cell
+			:title="title"
+			:value="value"
+			:label="label"
+			:icon="icon"
+			:isLink="isLink"
+			:clickable="clickable"
+			:border="parentData.border && showBorder"
+			@click="clickHandler"
+			:arrowDirection="expanded ? 'up' : 'down'"
+			:disabled="disabled"
+		>
+			<!-- #ifndef MP-WEIXIN -->
+			<!-- 微信小程序不支持,因为微信中不支持 <slot name="title" slot="title" />的写法 -->
+			<template slot="title">
+				<slot name="title"></slot>
+			</template>
+			<template slot="icon">
+				<slot name="icon"></slot>
+			</template>
+			<template slot="value">
+				<slot name="value"></slot>
+			</template>
+			<template slot="right-icon">
+				<slot name="right-icon"></slot>
+			</template>
+			<!-- #endif -->
+		</u-cell>
+		<view
+			class="u-collapse-item__content"
+			:animation="animationData"
+			ref="animation"
+		>
+			<view
+				class="u-collapse-item__content__text content-class"
+				:id="elId"
+				:ref="elId"
+			><slot /></view>
 		</view>
+		<u-line v-if="parentData.border"></u-line>
 	</view>
 </template>
 
 <script>
+	import props from './props.js';
+	// #ifdef APP-NVUE
+	const animation = uni.requireNativePlugin('animation')
+	const dom = uni.requireNativePlugin('dom')
+	// #endif
 	/**
-	 * collapseItem 手风琴Item
+	 * collapseItem 折叠面板Item
 	 * @description 通过折叠面板收纳内容区域(搭配u-collapse使用)
 	 * @tutorial https://www.uviewui.com/components/collapse.html
-	 * @property {String} title 面板标题
-	 * @property {String Number} index 主要用于事件的回调,标识那个Item被点击
-	 * @property {Boolean} disabled 面板是否可以打开或收起(默认false)
-	 * @property {Boolean} open 设置某个面板的初始状态是否打开(默认false)
-	 * @property {String Number} name 唯一标识符,如不设置,默认用当前collapse-item的索引值
-	 * @property {String} align 标题的对齐方式(默认left)
-	 * @property {Object} active-style 不显示箭头时,可以添加当前选择的collapse-item活动样式,对象形式
-	 * @event {Function} change 某个item被打开或者收起时触发
+	 * @property {String}			title 		标题
+	 * @property {String}			value 		标题右侧内容
+	 * @property {String}			label 		标题下方的描述信息
+	 * @property {Boolean}			disbled 	是否禁用折叠面板 ( 默认 false )
+	 * @property {Boolean}			isLink 		是否展示右侧箭头并开启点击反馈 ( 默认 true )
+	 * @property {Boolean}			clickable	是否开启点击反馈 ( 默认 true )
+	 * @property {Boolean}			border		是否显示内边框 ( 默认 true )
+	 * @property {String}			align		标题的对齐方式 ( 默认 'left' )
+	 * @property {String | Number}	name		唯一标识符
+	 * @property {String}			icon		标题左侧图片,可为绝对路径的图片或内置图标
+	 * @event {Function}			change 			某个item被打开或者收起时触发
 	 * @example <u-collapse-item :title="item.head" v-for="(item, index) in itemList" :key="index">{{item.body}}</u-collapse-item>
 	 */
 	export default {
 		name: "u-collapse-item",
-		props: {
-			// 标题
-			title: {
-				type: String,
-				default: ''
-			},
-			// 标题的对齐方式
-			align: {
-				type: String,
-				default: 'left'
-			},
-			// 是否可以点击收起
-			disabled: {
-				type: Boolean,
-				default: false
-			},
-			// collapse显示与否
-			open: {
-				type: Boolean,
-				default: false
-			},
-			// 唯一标识符
-			name: {
-				type: [Number, String],
-				default: ''
-			},
-			//活动样式
-			activeStyle: {
-				type: Object,
-				default () {
-					return {}
-				}
-			},
-			// 标识当前为第几个
-			index: {
-				type: [String, Number],
-				default: ''
-			}
-		},
+		mixins: [uni.$u.mpMixin, uni.$u.mixin, props],
 		data() {
 			return {
-				isShow: false,
-				elId: this.$u.guid(),
-				height: 0, // body内容的高度
-				headStyle: {}, // 头部样式,对象形式
-				bodyStyle: {}, // 主体部分样式
-				itemStyle: {}, // 每个item的整体样式
-				arrowColor: '', // 箭头的颜色
-				hoverClass: '', // 头部按下时的效果样式类
-				arrow: true, // 是否显示右侧箭头
-				
+				elId: uni.$u.guid(),
+				// uni.createAnimation的导出数据
+				animationData: {},
+				// 是否展开状态
+				expanded: false,
+				// 根据expanded确定是否显示border,为了控制展开时,cell的下划线更好的显示效果,进行一定时间的延时
+				showBorder: false,
+				// 是否动画中,如果是则不允许继续触发点击
+				animating: false,
+				// 父组件u-collapse的参数
+				parentData: {
+					accordion: false,
+					border: false
+				}
 			};
 		},
 		watch: {
-			open(val) {
-				this.isShow = val;
+			expanded(n) {
+				clearTimeout(this.timer)
+				this.timer = null
+				// 这里根据expanded的值来进行一定的延时,是为了cell的下划线更好的显示效果
+				this.timer = setTimeout(() => {
+					this.showBorder = n
+				}, n ? 10 : 290)
 			}
 		},
-		created() {
-			this.parent = false;
-			// 获取u-collapse的信息,放在u-collapse是为了方便,不用每个u-collapse-item写一遍
-			this.isShow = this.open;
+		mounted() {
+			this.init()
 		},
 		methods: {
 			// 异步获取内容,或者动态修改了内容时,需要重新初始化
 			init() {
-				this.parent = this.$u.$parent.call(this, 'u-collapse');
-				if(this.parent) {
-					this.nameSync = this.name ? this.name : this.parent.childrens.length;
-					this.parent.childrens.push(this);
-					this.headStyle = this.parent.headStyle;
-					this.bodyStyle = this.parent.bodyStyle;
-					this.arrowColor = this.parent.arrowColor;
-					this.hoverClass = this.parent.hoverClass;
-					this.arrow = this.parent.arrow;
-					this.itemStyle = this.parent.itemStyle;
+				// 初始化数据
+				this.updateParentData()
+				if (!this.parent) {
+					return uni.$u.error('u-collapse-item必须要搭配u-collapse组件使用')
 				}
-				this.$nextTick(() => {
-					this.queryRect();
-				});
-			},
-			// 点击collapsehead头部
-			headClick() {
-				if (this.disabled) return;
-				if (this.parent && this.parent.accordion == true) {
-					this.parent.childrens.map(val => {
-						// 自身不设置为false,因为后面有this.isShow = !this.isShow;处理了
-						if (this != val) {
-							val.isShow = false;
-						}
-					});
+				const {
+					value,
+					accordion,
+					children = []
+				} = this.parent
+
+				if (accordion) {
+					if (uni.$u.test.array(value)) {
+						return uni.$u.error('手风琴模式下,u-collapse组件的value参数不能为数组')
+					}
+					this.expanded = this.name == value
+				} else {
+					if (!uni.$u.test.array(value) && value !== null) {
+						return uni.$u.error('非手风琴模式下,u-collapse组件的value参数必须为数组')
+					}
+					this.expanded = (value || []).some(item => item == this.name)
 				}
+				// 设置组件的展开或收起状态
+				this.$nextTick(function() {
+					this.setContentAnimate()
+				})
+			},
+			updateParentData() {
+				// 此方法在mixin中
+				this.getParentData('u-collapse')
+			},
+			async setContentAnimate() {
+				// 每次面板打开或者收起时,都查询元素尺寸
+				// 好处是,父组件从服务端获取内容后,变更折叠面板后可以获得最新的高度
+				const rect = await this.queryRect()
+				const height = this.expanded ? rect.height : 0
+				this.animating = true
+				// #ifdef APP-NVUE
+				const ref = this.$refs['animation'].ref
+				animation.transition(ref, {
+					styles: {
+						height: height + 'px'
+					},
+					duration: this.duration,
+					// 必须设置为true,否则会到面板收起或展开时,页面其他元素不会随之调整它们的布局
+					needLayout: true,
+					timingFunction: 'ease-in-out',
+				}, () => {
+					this.animating = false
+				})
+				// #endif
 
-				this.isShow = !this.isShow;
-				// 触发本组件的事件
-				this.$emit('change', {
-					index: this.index,
-					show: this.isShow
+				// #ifndef APP-NVUE
+				const animation = uni.createAnimation({
+					timingFunction: 'ease-in-out',
+				});
+				animation
+					.height(height)
+					.step({
+						duration: this.duration,
+					})
+					.step()
+				// 导出动画数据给面板的animationData值
+				this.animationData = animation.export()
+				// 标识动画结束
+				uni.$u.sleep(this.duration).then(() => {
+					this.animating = false
 				})
-				// 只有在打开时才发出事件
-				if (this.isShow) this.parent && this.parent.onChange();
-				this.$forceUpdate();
+				// #endif
+			},
+			// 点击collapsehead头部
+			clickHandler() {
+				if (this.disabled && this.animating) return
+				// 设置本组件为相反的状态
+				this.parent && this.parent.onChange(this)
 			},
 			// 查询内容高度
 			queryRect() {
+				// #ifndef APP-NVUE
 				// $uGetRect为uView自带的节点查询简化方法,详见文档介绍:https://www.uviewui.com/js/getRect.html
-				// 组件内部一般用this.$uGetRect,对外的为this.$u.getRect,二者功能一致,名称不同
-				this.$uGetRect('#' + this.elId).then(res => {
-					this.height = res.height;
+				// 组件内部一般用this.$uGetRect,对外的为uni.$u.getRect,二者功能一致,名称不同
+				return new Promise(resolve => {
+					this.$uGetRect(`#${this.elId}`).then(size => {
+						resolve(size)
+					})
 				})
+				// #endif
+
+				// #ifdef APP-NVUE
+				// nvue下,使用dom模块查询元素高度
+				// 返回一个promise,让调用此方法的主体能使用then回调
+				return new Promise(resolve => {
+					dom.getComponentRect(this.$refs[this.elId], res => {
+						resolve(res.size)
+					})
+				})
+				// #endif
 			}
 		},
-		mounted() {
-			this.init();
-		}
 	};
 </script>
 
 <style lang="scss" scoped>
-	@import "../../libs/css/style.components.scss";
-	
-	.u-collapse-head {
-		position: relative;
-		@include vue-flex;
-		justify-content: space-between;
-		align-items: center;
-		color: $u-main-color;
-		font-size: 30rpx;
-		line-height: 1;
-		padding: 24rpx 0;
-		text-align: left;
-	}
+	@import "../../libs/css/components.scss";
 
-	.u-collapse-title {
-		flex: 1;
-		overflow: hidden;
-	}
+	.u-collapse-item {
 
-	.u-arrow-down-icon {
-		transition: all 0.3s;
-		margin-right: 20rpx;
-		margin-left: 14rpx;
-	}
-
-	.u-arrow-down-icon-active {
-		transform: rotate(180deg);
-		transform-origin: center center;
-	}
+		&__content {
+			overflow: hidden;
+			height: 0;
 
-	.u-collapse-body {
-		overflow: hidden;
-		transition: all 0.3s;
-	}
-
-	.u-collapse-content {
-		overflow: hidden;
-		font-size: 28rpx;
-		color: $u-tips-color;
-		text-align: left;
+			&__text {
+				padding: 12px 15px;
+				color: $u-content-color;
+				font-size: 14px;
+				line-height: 18px;
+			}
+		}
 	}
 </style>

+ 19 - 0
uview-ui/components/u-collapse/props.js

@@ -0,0 +1,19 @@
+export default {
+    props: {
+        // 当前展开面板的name,非手风琴模式:[<string | number>],手风琴模式:string | number
+        value: {
+            type: [String, Number, Array, null],
+            default: uni.$u.props.collapse.value
+        },
+        // 是否手风琴模式
+        accordion: {
+            type: Boolean,
+            default: uni.$u.props.collapse.accordion
+        },
+        // 是否显示外边框
+        border: {
+            type: Boolean,
+            default: uni.$u.props.collapse.border
+        }
+    }
+}

+ 58 - 67
uview-ui/components/u-collapse/u-collapse.vue

@@ -1,99 +1,90 @@
 <template>
 	<view class="u-collapse">
+		<u-line v-if="border"></u-line>
 		<slot />
 	</view>
 </template>
 
 <script>
+	import props from './props.js';
 	/**
-	 * collapse 手风琴
+	 * collapse 折叠面板 
 	 * @description 通过折叠面板收纳内容区域
 	 * @tutorial https://www.uviewui.com/components/collapse.html
-	 * @property {Boolean} accordion 是否手风琴模式(默认true)
-	 * @property {Boolean} arrow 是否显示标题右侧的箭头(默认true)
-	 * @property {String} arrow-color 标题右侧箭头的颜色(默认#909399)
-	 * @property {Object} head-style 标题自定义样式,对象形式
-	 * @property {Object} body-style 主体自定义样式,对象形式
-	 * @property {String} hover-class 样式类名,按下时有效(默认u-hover-class)
-	 * @event {Function} change 当前激活面板展开时触发(如果是手风琴模式,参数activeNames类型为String,否则为Array)
+	 * @property {String | Number | Array}	value		当前展开面板的name,非手风琴模式:[<string | number>],手风琴模式:string | number
+	 * @property {Boolean}					accordion	是否手风琴模式( 默认 false )
+	 * @property {Boolean}					border		是否显示外边框 ( 默认 true )
+	 * @event {Function}	change 		当前激活面板展开时触发(如果是手风琴模式,参数activeNames类型为String,否则为Array)
 	 * @example <u-collapse></u-collapse>
 	 */
 	export default {
-		name:"u-collapse",
-		props: {
-			// 是否手风琴模式
-			accordion: {
-				type: Boolean,
-				default: true
-			},
-			// 头部的样式
-			headStyle: {
-				type: Object,
-				default () {
-					return {}
-				}
-			},
-			// 主体的样式
-			bodyStyle: {
-				type: Object,
-				default () {
-					return {}
-				}
-			},
-			// 每一个item的样式
-			itemStyle: {
-				type: Object,
-				default () {
-					return {}
-				}
-			},
-			// 是否显示右侧的箭头
-			arrow: {
-				type: Boolean,
-				default: true
-			},
-			// 箭头的颜色
-			arrowColor: {
-				type: String,
-				default: '#909399'
-			},
-			// 标题部分按压时的样式类,"none"为无效果
-			hoverClass: {
-				type: String,
-				default: 'u-hover-class'
+		name: "u-collapse",
+		mixins: [uni.$u.mpMixin, uni.$u.mixin,props],
+		watch: {
+			needInit() {
+				this.init()
 			}
 		},
 		created() {
-			this.childrens = []
+			this.children = []
 		},
-		data() {
-			return {
-
+		computed: {
+			needInit() {
+				// 通过computed,同时监听accordion和value值的变化
+				// 再通过watch去执行init()方法,进行再一次的初始化
+				return [this.accordion, this.value]
 			}
 		},
+		watch: {
+			// 当父组件需要子组件需要共享的参数发生了变化,手动通知子组件
+			parentData() {
+				if (this.children.length) {
+					this.children.map(child => {
+						// 判断子组件(u-checkbox)如果有updateParentData方法的话,就就执行(执行的结果是子组件重新从父组件拉取了最新的值)
+						typeof(child.updateParentData) === 'function' && child.updateParentData()
+					})
+				}
+			},
+		},
 		methods: {
-			// 重新初始化一次内部的所有子元素的高度计算,用于异步获取数据渲染的情况
+			// 重新初始化一次内部的所有子元素
 			init() {
-				this.childrens.forEach((vm, index) => {
-					vm.init();
+				this.children.map(child => {
+					child.init()
 				})
 			},
-			// collapse item被点击,由collapse item调用父组件方法
-			onChange() {
-				let activeItem = [];
-				this.childrens.forEach((vm, index) => {
-					if (vm.isShow) {
-						activeItem.push(vm.nameSync);
+			/**
+			 * collapse-item被点击时触发,由collapse统一处理各子组件的状态
+			 * @param {Object} target 被操作的面板的实例
+			 */
+			onChange(target) {
+				let changeArr = []
+				this.children.map((child, index) => {
+					// 如果是手风琴模式,将其他的折叠面板收起来
+					if (this.accordion) {
+						child.expanded = child === target ? !target.expanded : false
+						child.setContentAnimate()
+					} else {
+						if(child === target) {
+							child.expanded = !child.expanded
+							child.setContentAnimate()
+						}
 					}
+					// 拼接change事件中,数组元素的状态
+					changeArr.push({
+						// 如果没有定义name属性,则默认返回组件的index索引
+						name: child.name || index,
+						status: child.expanded ? 'open' : 'close'
+					})
 				})
-				// 如果是手风琴模式,只有一个匹配结果,也即activeItem长度为1,将其转为字符串
-				if (this.accordion) activeItem = activeItem.join('');
-				this.$emit('change', activeItem);
+
+				this.$emit('change', changeArr)
+				this.$emit(target.expanded ? 'open' : 'close', target.name)
 			}
 		}
 	}
 </script>
 
 <style lang="scss" scoped>
-	@import "../../libs/css/style.components.scss";
+	@import "../../libs/css/components.scss";
 </style>

+ 55 - 0
uview-ui/components/u-column-notice/props.js

@@ -0,0 +1,55 @@
+export default {
+    props: {
+        // 显示的内容,字符串
+        text: {
+            type: [Array],
+            default: uni.$u.props.columnNotice.text
+        },
+        // 是否显示左侧的音量图标
+        icon: {
+            type: String,
+            default: uni.$u.props.columnNotice.icon
+        },
+        // 通告模式,link-显示右箭头,closable-显示右侧关闭图标
+        mode: {
+            type: String,
+            default: uni.$u.props.columnNotice.mode
+        },
+        // 文字颜色,各图标也会使用文字颜色
+        color: {
+            type: String,
+            default: uni.$u.props.columnNotice.color
+        },
+        // 背景颜色
+        bgColor: {
+            type: String,
+            default: uni.$u.props.columnNotice.bgColor
+        },
+        // 字体大小,单位px
+        fontSize: {
+            type: [String, Number],
+            default: uni.$u.props.columnNotice.fontSize
+        },
+        // 水平滚动时的滚动速度,即每秒滚动多少px(px),这有利于控制文字无论多少时,都能有一个恒定的速度
+        speed: {
+            type: [String, Number],
+            default: uni.$u.props.columnNotice.speed
+        },
+        // direction = row时,是否使用步进形式滚动
+        step: {
+            type: Boolean,
+            default: uni.$u.props.columnNotice.step
+        },
+        // 滚动一个周期的时间长,单位ms
+        duration: {
+            type: [String, Number],
+            default: uni.$u.props.columnNotice.duration
+        },
+        // 是否禁止用手滑动切换
+        // 目前HX2.6.11,只支持App 2.5.5+、H5 2.5.5+、支付宝小程序、字节跳动小程序
+        disableTouch: {
+            type: Boolean,
+            default: uni.$u.props.columnNotice.disableTouch
+        }
+    }
+}

+ 134 - 211
uview-ui/components/u-column-notice/u-column-notice.vue

@@ -1,237 +1,160 @@
 <template>
 	<view
-		class="u-notice-bar"
-		:style="{
-			background: computeBgColor,
-			padding: padding
-		}"
-		:class="[
-			type ? `u-type-${type}-light-bg` : ''
-		]"
+		class="u-notice"
+		@tap="clickHandler"
 	>
-		<view class="u-icon-wrap">
-			<u-icon class="u-left-icon" v-if="volumeIcon" name="volume-fill" :size="volumeSize" :color="computeColor"></u-icon>
-		</view>
-		<swiper :disable-touch="disableTouch" @change="change" :autoplay="autoplay && playState == 'play'" :vertical="vertical" circular :interval="duration" class="u-swiper">
-			<swiper-item v-for="(item, index) in list" :key="index" class="u-swiper-item">
-				<view
-					class="u-news-item u-line-1"
+		<slot name="icon">
+			<view
+				class="u-notice__left-icon"
+				v-if="icon"
+			>
+				<u-icon
+					:name="icon"
+					:color="color"
+					size="19"
+				></u-icon>
+			</view>
+		</slot>
+		<swiper
+			:disable-touch="disableTouch"
+			:vertical="step ? false : true"
+			circular
+			:interval="duration"
+			:autoplay="true"
+			class="u-notice__swiper"
+			@change="noticeChange"
+		>
+			<swiper-item
+				v-for="(item, index) in text"
+				:key="index"
+				class="u-notice__swiper__item"
+			>
+				<text
+					class="u-notice__swiper__item__text u-line-1"
 					:style="[textStyle]"
-					@tap="click(index)"
-					:class="['u-type-' + type]"
-				>
-					{{ item }}
-				</view>
+				>{{ item }}</text>
 			</swiper-item>
 		</swiper>
-		<view class="u-icon-wrap">
-			<u-icon @click="getMore" class="u-right-icon" v-if="moreIcon" name="arrow-right" :size="26" :color="computeColor"></u-icon>
-			<u-icon @click="close" class="u-right-icon" v-if="closeIcon" name="close" :size="24" :color="computeColor"></u-icon>
+		<view
+			class="u-notice__right-icon"
+			v-if="['link', 'closable'].includes(mode)"
+		>
+			<u-icon
+				v-if="mode === 'link'"
+				name="arrow-right"
+				:size="17"
+				:color="color"
+			></u-icon>
+			<u-icon
+				v-if="mode === 'closable'"
+				name="close"
+				:size="16"
+				:color="color"
+				@click="close"
+			></u-icon>
 		</view>
 	</view>
 </template>
 
 <script>
-export default {
-	props: {
-		// 显示的内容,数组
-		list: {
-			type: Array,
-			default() {
-				return [];
+	import props from './props.js';
+	/**
+	 * ColumnNotice 滚动通知中的垂直滚动 内部组件
+	 * @description 该组件用于滚动通告场景,是其中的垂直滚动方式
+	 * @tutorial https://www.uviewui.com/components/noticeBar.html
+	 * @property {Array}			text 			显示的内容,字符串
+	 * @property {String}			icon 			是否显示左侧的音量图标 ( 默认 'volume' )
+	 * @property {String}			mode 			通告模式,link-显示右箭头,closable-显示右侧关闭图标
+	 * @property {String}			color 			文字颜色,各图标也会使用文字颜色 ( 默认 '#f9ae3d' )
+	 * @property {String}			bgColor 		背景颜色 ( 默认 '#fdf6ec' )
+	 * @property {String | Number}	fontSize		字体大小,单位px  ( 默认 14 )
+	 * @property {String | Number}	speed			水平滚动时的滚动速度,即每秒滚动多少px(rpx),这有利于控制文字无论多少时,都能有一个恒定的速度 ( 默认 80 )
+	 * @property {Boolean}			step			direction = row时,是否使用步进形式滚动 ( 默认 false )
+	 * @property {String | Number}	duration		滚动一个周期的时间长,单位ms ( 默认 1500 )
+	 * @property {Boolean}			disableTouch	是否禁止用手滑动切换   目前HX2.6.11,只支持App 2.5.5+、H5 2.5.5+、支付宝小程序、字节跳动小程序 ( 默认 true )
+	 * @example 
+	 */
+	export default {
+		mixins: [uni.$u.mpMixin, uni.$u.mixin,props],
+		watch: {
+			text: {
+				immediate: true,
+				handler(newValue, oldValue) {
+					if(!uni.$u.test.array(newValue)) {
+						uni.$u.error('noticebar组件direction为column时,要求text参数为数组形式')
+					}
+				}
 			}
 		},
-		// 显示的主题,success|error|primary|info|warning
-		type: {
-			type: String,
-			default: 'warning'
-		},
-		// 是否显示左侧的音量图标
-		volumeIcon: {
-			type: Boolean,
-			default: true
-		},
-		// 是否显示右侧的右箭头图标
-		moreIcon: {
-			type: Boolean,
-			default: false
-		},
-		// 是否显示右侧的关闭图标
-		closeIcon: {
-			type: Boolean,
-			default: false
-		},
-		// 是否自动播放
-		autoplay: {
-			type: Boolean,
-			default: true
-		},
-		// 文字颜色,各图标也会使用文字颜色
-		color: {
-			type: String,
-			default: ''
-		},
-		// 背景颜色
-		bgColor: {
-			type: String,
-			default: ''
-		},
-		// 滚动方向,row-水平滚动,column-垂直滚动
-		direction: {
-			type: String,
-			default: 'row'
-		},
-		// 是否显示
-		show: {
-			type: Boolean,
-			default: true
-		},
-		// 字体大小,单位rpx
-		fontSize: {
-			type: [Number, String],
-			default: 26
-		},
-		// 滚动一个周期的时间长,单位ms
-		duration: {
-			type: [Number, String],
-			default: 2000
-		},
-		// 音量喇叭的大小
-		volumeSize: {
-			type: [Number, String],
-			default: 34
-		},
-		// 水平滚动时的滚动速度,即每秒滚动多少rpx,这有利于控制文字无论多少时,都能有一个恒定的速度
-		speed: {
-			type: Number,
-			default: 160
-		},
-		// 水平滚动时,是否采用衔接形式滚动
-		isCircular: {
-			type: Boolean,
-			default: true
-		},
-		// 滚动方向,horizontal-水平滚动,vertical-垂直滚动
-		mode: {
-			type: String,
-			default: 'horizontal'
-		},
-		// 播放状态,play-播放,paused-暂停
-		playState: {
-			type: String,
-			default: 'play'
-		},
-		// 是否禁止用手滑动切换
-		// 目前HX2.6.11,只支持App 2.5.5+、H5 2.5.5+、支付宝小程序、字节跳动小程序
-		disableTouch: {
-			type: Boolean,
-			default: true
-		},
-		// 通知的边距
-		padding: {
-			type: [Number, String],
-			default: '18rpx 24rpx'
-		}
-	},
-	computed: {
-		// 计算字体颜色,如果没有自定义的,就用uview主题颜色
-		computeColor() {
-			if (this.color) return this.color;
-			// 如果是无主题,就默认使用content-color
-			else if(this.type == 'none') return '#606266';
-			else return this.type;
-		},
-		// 文字内容的样式
-		textStyle() {
-			let style = {};
-			if (this.color) style.color = this.color;
-			else if(this.type == 'none') style.color = '#606266';
-			style.fontSize = this.fontSize + 'rpx';
-			return style;
-		},
-		// 垂直或者水平滚动
-		vertical() {
-			if(this.mode == 'horizontal') return false;
-			else return true;
-		},
-		// 计算背景颜色
-		computeBgColor() {
-			if (this.bgColor) return this.bgColor;
-			else if(this.type == 'none') return 'transparent';
-		}
-	},
-	data() {
-		return {
-			// animation: false
-		};
-	},
-	methods: {
-		// 点击通告栏
-		click(index) {
-			this.$emit('click', index);
-		},
-		// 点击关闭按钮
-		close() {
-			this.$emit('close');
-		},
-		// 点击更多箭头按钮
-		getMore() {
-			this.$emit('getMore');
+		computed: {
+			// 文字内容的样式
+			textStyle() {
+				let style = {}
+				style.color = this.color
+				style.fontSize = uni.$u.addUnit(this.fontSize)
+				return style
+			},
+			// 垂直或者水平滚动
+			vertical() {
+				if (this.mode == 'horizontal') return false
+				else return true
+			},
+		},
+		data() {
+			return {
+				index:0
+			}
 		},
-		change(e) {
-			let index = e.detail.current;
-			if(index == this.list.length - 1) {
-				this.$emit('end');
+		methods: {
+			noticeChange(e){
+				this.index = e.detail.current
+			},
+			// 点击通告栏
+			clickHandler() {
+				this.$emit('click', this.index)
+			},
+			// 点击关闭按钮
+			close() {
+				this.$emit('close')
 			}
 		}
-	}
-};
+	};
 </script>
 
 <style lang="scss" scoped>
-@import "../../libs/css/style.components.scss";
+	@import "../../libs/css/components.scss";
 
-.u-notice-bar {
-	width: 100%;
-	@include vue-flex;
-	align-items: center;
-	justify-content: center;
-	flex-wrap: nowrap;
-	padding: 18rpx 24rpx;
-	overflow: hidden;
-}
+	.u-notice {
+		@include flex;
+		align-items: center;
+		justify-content: space-between;
 
-.u-swiper {
-	font-size: 26rpx;
-	height: 32rpx;
-	@include vue-flex;
-	align-items: center;
-	flex: 1;
-	margin-left: 12rpx;
-}
+		&__left-icon {
+			align-items: center;
+			margin-right: 5px;
+		}
 
-.u-swiper-item {
-	@include vue-flex;
-	align-items: center;
-	overflow: hidden;
-}
+		&__right-icon {
+			margin-left: 5px;
+			align-items: center;
+		}
 
-.u-news-item {
-	overflow: hidden;
-}
+		&__swiper {
+			height: 16px;
+			@include flex;
+			align-items: center;
+			flex: 1;
 
-.u-right-icon {
-	margin-left: 12rpx;
-	/* #ifndef APP-NVUE */
-	display: inline-flex;		
-	/* #endif */
-	align-items: center;
-}
+			&__item {
+				@include flex;
+				align-items: center;
+				overflow: hidden;
 
-.u-left-icon {
-	/* #ifndef APP-NVUE */
-	display: inline-flex;		
-	/* #endif */
-	align-items: center;
-}
+				&__text {
+					font-size: 14px;
+					color: $u-warning;
+				}
+			}
+		}
+	}
 </style>

+ 24 - 0
uview-ui/components/u-count-down/props.js

@@ -0,0 +1,24 @@
+export default {
+    props: {
+        // 倒计时时长,单位ms
+        time: {
+            type: [String, Number],
+            default: uni.$u.props.countDown.time
+        },
+        // 时间格式,DD-日,HH-时,mm-分,ss-秒,SSS-毫秒
+        format: {
+            type: String,
+            default: uni.$u.props.countDown.format
+        },
+        // 是否自动开始倒计时
+        autoStart: {
+            type: Boolean,
+            default: uni.$u.props.countDown.autoStart
+        },
+        // 是否展示毫秒倒计时
+        millisecond: {
+            type: Boolean,
+            default: uni.$u.props.countDown.millisecond
+        }
+    }
+}

+ 145 - 300
uview-ui/components/u-count-down/u-count-down.vue

@@ -1,318 +1,163 @@
 <template>
-	<view class="u-countdown">
-		<view class="u-countdown-item" :style="[itemStyle]" v-if="showDays && (hideZeroDay || (!hideZeroDay && d != '00'))">
-			<view class="u-countdown-time" :style="[letterStyle]">
-				{{ d }}
-			</view>
-		</view>
-		<view
-			class="u-countdown-colon"
-			:style="{fontSize: separatorSize + 'rpx', color: separatorColor, paddingBottom: separator == 'colon' ? '4rpx' : 0}"
-			v-if="showDays && (hideZeroDay || (!hideZeroDay && d != '00'))"
-		>
-			{{ separator == 'colon' ? ':' : '天' }}
-		</view>
-		<view class="u-countdown-item" :style="[itemStyle]" v-if="showHours">
-			<view class="u-countdown-time" :style="{ fontSize: fontSize + 'rpx', color: color}">
-				{{ h }}
-			</view>
-		</view>
-		<view
-			class="u-countdown-colon"
-			:style="{fontSize: separatorSize + 'rpx', color: separatorColor, paddingBottom: separator == 'colon' ? '4rpx' : 0}"
-			v-if="showHours"
-		>
-			{{ separator == 'colon' ? ':' : '时' }}
-		</view>
-		<view class="u-countdown-item" :style="[itemStyle]" v-if="showMinutes">
-			<view class="u-countdown-time" :style="{ fontSize: fontSize + 'rpx', color: color}">
-				{{ i }}
-			</view>
-		</view>
-		<view
-			class="u-countdown-colon"
-			:style="{fontSize: separatorSize + 'rpx', color: separatorColor, paddingBottom: separator == 'colon' ? '4rpx' : 0}"
-			v-if="showMinutes"
-		>
-			{{ separator == 'colon' ? ':' : '分' }}
-		</view>
-		<view class="u-countdown-item" :style="[itemStyle]" v-if="showSeconds">
-			<view class="u-countdown-time" :style="{ fontSize: fontSize + 'rpx', color: color}">
-				{{ s }}
-			</view>
-		</view>
-		<view
-			class="u-countdown-colon"
-			:style="{fontSize: separatorSize + 'rpx', color: separatorColor, paddingBottom: separator == 'colon' ? '4rpx' : 0}"
-			v-if="showSeconds && separator == 'zh'"
-		>
-			秒
-		</view>
+	<view class="u-count-down">
+		<slot>
+			<text class="u-count-down__text">{{ formattedTime }}</text>
+		</slot>
 	</view>
 </template>
 
 <script>
-/**
- * countDown 倒计时
- * @description 该组件一般使用于某个活动的截止时间上,通过数字的变化,给用户明确的时间感受,提示用户进行某一个行为操作。
- * @tutorial https://www.uviewui.com/components/countDown.html
- * @property {String Number} timestamp 倒计时,单位为秒
- * @property {Boolean} autoplay 是否自动开始倒计时,如果为false,需手动调用开始方法。见官网说明(默认true)
- * @property {String} separator 分隔符,colon为英文冒号,zh为中文(默认colon)
- * @property {String Number} separator-size 分隔符的字体大小,单位rpx(默认30)
- * @property {String} separator-color 分隔符的颜色(默认#303133)
- * @property {String Number} font-size 倒计时字体大小,单位rpx(默认30)
- * @property {Boolean} show-border 是否显示倒计时数字的边框(默认false)
- * @property {Boolean} hide-zero-day 当"天"的部分为0时,隐藏该字段 (默认true)
- * @property {String} border-color 数字边框的颜色(默认#303133)
- * @property {String} bg-color 倒计时数字的背景颜色(默认#ffffff)
- * @property {String} color 倒计时数字的颜色(默认#303133)
- * @property {String} height 数字高度值(宽度等同此值),设置边框时看情况是否需要设置此值,单位rpx(默认auto)
- * @property {Boolean} show-days 是否显示倒计时的"天"部分(默认true)
- * @property {Boolean} show-hours 是否显示倒计时的"时"部分(默认true)
- * @property {Boolean} show-minutes 是否显示倒计时的"分"部分(默认true)
- * @property {Boolean} show-seconds 是否显示倒计时的"秒"部分(默认true)
- * @event {Function} end 倒计时结束
- * @event {Function} change 每秒触发一次,回调为当前剩余的倒计秒数
- * @example <u-count-down ref="uCountDown" :timestamp="86400" :autoplay="false"></u-count-down>
- */
-export default {
-	name: 'u-count-down',
-	props: {
-		// 倒计时的时间,秒为单位
-		timestamp: {
-			type: [Number, String],
-			default: 0
-		},
-		// 是否自动开始倒计时
-		autoplay: {
-			type: Boolean,
-			default: true
-		},
-		// 用英文冒号(colon)或者中文(zh)当做分隔符,false的时候为中文,如:"11:22"或"11时22秒"
-		separator: {
-			type: String,
-			default: 'colon'
-		},
-		// 分隔符的大小,单位rpx
-		separatorSize: {
-			type: [Number, String],
-			default: 30
-		},
-		// 分隔符颜色
-		separatorColor: {
-			type: String,
-			default: "#303133"
-		},
-		// 字体颜色
-		color: {
-			type: String,
-			default: '#303133'
-		},
-		// 字体大小,单位rpx
-		fontSize: {
-			type: [Number, String],
-			default: 30
-		},
-		// 背景颜色
-		bgColor: {
-			type: String,
-			default: '#fff'
-		},
-		// 数字框高度,单位rpx
-		height: {
-			type: [Number, String],
-			default: 'auto'
-		},
-		// 是否显示数字框
-		showBorder: {
-			type: Boolean,
-			default: false
-		},
-		// 边框颜色
-		borderColor: {
-			type: String,
-			default: '#303133'
-		},
-		// 是否显示秒
-		showSeconds: {
-			type: Boolean,
-			default: true
-		},
-		// 是否显示分钟
-		showMinutes: {
-			type: Boolean,
-			default: true
-		},
-		// 是否显示小时
-		showHours: {
-			type: Boolean,
-			default: true
-		},
-		// 是否显示“天”
-		showDays: {
-			type: Boolean,
-			default: true
-		},
-		// 当"天"的部分为0时,不显示
-		hideZeroDay: {
-			type: Boolean,
-			default: false
-		}
-	},
-	watch: {
-		// 监听时间戳的变化
-		timestamp(newVal, oldVal) {
-			// 如果倒计时间发生变化,清除定时器,重新开始倒计时
-			this.clearTimer();
-			this.start();
-		}
-	},
-	data() {
-		return {
-			d: '00', // 天的默认值
-			h: '00', // 小时的默认值
-			i: '00', // 分钟的默认值
-			s: '00', // 秒的默认值
-			timer: null ,// 定时器
-			seconds: 0, // 记录不停倒计过程中变化的秒数
-		};
-	},
-	computed: {
-		// 倒计时item的样式,item为分别的时分秒部分的数字
-		itemStyle() {
-			let style = {};
-			if(this.height) {
-				style.height = this.height + 'rpx';
-				style.width = this.height + 'rpx';
+	import props from './props.js';
+	import {
+		isSameSecond,
+		parseFormat,
+		parseTimeData
+	} from './utils';
+	/**
+	 * u-count-down 倒计时
+	 * @description 该组件一般使用于某个活动的截止时间上,通过数字的变化,给用户明确的时间感受,提示用户进行某一个行为操作。
+	 * @tutorial https://uviewui.com/components/countDown.html
+	 * @property {String | Number}	time		倒计时时长,单位ms (默认 0 )
+	 * @property {String}			format		时间格式,DD-日,HH-时,mm-分,ss-秒,SSS-毫秒  (默认 'HH:mm:ss' )
+	 * @property {Boolean}			autoStart	是否自动开始倒计时 (默认 true )
+	 * @property {Boolean}			millisecond	是否展示毫秒倒计时 (默认 false )
+	 * @event {Function} finish 倒计时结束时触发 
+	 * @event {Function} change 倒计时变化时触发 
+	 * @event {Function} start	开始倒计时
+	 * @event {Function} pause	暂停倒计时 
+	 * @event {Function} reset	重设倒计时,若 auto-start 为 true,重设后会自动开始倒计时 
+	 * @example <u-count-down :time="time"></u-count-down>
+	 */
+	export default {
+		name: 'u-count-down',
+		mixins: [uni.$u.mpMixin, uni.$u.mixin, props],
+		data() {
+			return {
+				timer: null,
+				// 各单位(天,时,分等)剩余时间
+				timeData: parseTimeData(0),
+				// 格式化后的时间,如"03:23:21"
+				formattedTime: '0',
+				// 倒计时是否正在进行中
+				runing: false,
+				endTime: 0, // 结束的毫秒时间戳
+				remainTime: 0, // 剩余的毫秒时间
 			}
-			if(this.showBorder) {
-				style.borderStyle = 'solid';
-				style.borderColor = this.borderColor;
-				style.borderWidth = '1px';
-			}
-			if(this.bgColor) {
-				style.backgroundColor = this.bgColor;
+		},
+		watch: {
+			time(n) {
+				this.reset()
 			}
-			return style;
 		},
-		// 倒计时数字的样式
-		letterStyle() {
-			let style = {};
-			if(this.fontSize) style.fontSize = this.fontSize +  'rpx';
-			if(this.color) style.color = this.color;
-			return style;
-		}
-	},
-	mounted() {
-		// 如果自动倒计时
-		this.autoplay && this.timestamp && this.start();
-	},
-	methods: {
-		// 倒计时
-		start() {
-			// 避免可能出现的倒计时重叠情况
-			this.clearTimer();
-			if (this.timestamp <= 0) return;
-			this.seconds = Number(this.timestamp);
-			this.formatTime(this.seconds);
-			this.timer = setInterval(() => {
-				this.seconds--;
-				// 发出change事件
-				this.$emit('change', this.seconds);
-				if (this.seconds < 0) {
-					return this.end();
+		mounted() {
+			this.init()
+		},
+		methods: {
+			init() {
+				this.reset()
+			},
+			// 开始倒计时
+			start() {
+				if (this.runing) return
+				// 标识为进行中
+				this.runing = true
+				// 结束时间戳 = 此刻时间戳 + 剩余的时间
+				this.endTime = Date.now() + this.remainTime
+				this.toTick()
+			},
+			// 根据是否展示毫秒,执行不同操作函数
+			toTick() {
+				if (this.millisecond) {
+					this.microTick()
+				} else {
+					this.macroTick()
 				}
-				this.formatTime(this.seconds);
-			}, 1000);
-		},
-		// 格式化时间
-		formatTime(seconds) {
-			// 小于等于0的话,结束倒计时
-			seconds <= 0 && this.end();
-			let [day, hour, minute, second] = [0, 0, 0, 0];
-			day = Math.floor(seconds / (60 * 60 * 24));
-			// 判断是否显示“天”参数,如果不显示,将天部分的值,加入到小时中
-			// hour为给后面计算秒和分等用的(基于显示天的前提下计算)
-			hour = Math.floor(seconds / (60 * 60)) - day * 24;
-			// showHour为需要显示的小时
-			let showHour = null;
-			if(this.showDays) {
-				showHour = hour;
-			} else {
-				// 如果不显示天数,将“天”部分的时间折算到小时中去
-				showHour = Math.floor(seconds / (60 * 60));
+			},
+			macroTick() {
+				this.clearTimeout()
+				// 每隔一定时间,更新一遍定时器的值
+				// 同时此定时器的作用也能带来毫秒级的更新
+				this.timer = setTimeout(() => {
+					// 获取剩余时间
+					const remain = this.getRemainTime()
+					// 重设剩余时间
+					if (!isSameSecond(remain, this.remainTime) || remain === 0) {
+						this.setRemainTime(remain)
+					}
+					// 如果剩余时间不为0,则继续检查更新倒计时
+					if (this.remainTime !== 0) {
+						this.macroTick()
+					}
+				}, 30)
+			},
+			microTick() {
+				this.clearTimeout()
+				this.timer = setTimeout(() => {
+					this.setRemainTime(this.getRemainTime())
+					if (this.remainTime !== 0) {
+						this.microTick()
+					}
+				}, 50)
+			},
+			// 获取剩余的时间
+			getRemainTime() {
+				// 取最大值,防止出现小于0的剩余时间值
+				return Math.max(this.endTime - Date.now(), 0)
+			},
+			// 设置剩余的时间
+			setRemainTime(remain) {
+				this.remainTime = remain
+				// 根据剩余的毫秒时间,得出该有天,小时,分钟等的值,返回一个对象
+				const timeData = parseTimeData(remain)
+				this.$emit('change', timeData)
+				// 得出格式化后的时间
+				this.formattedTime = parseFormat(this.format, timeData)
+				// 如果时间已到,停止倒计时
+				if (remain <= 0) {
+					this.pause()
+					this.$emit('finish')
+				}
+			},
+			// 重置倒计时
+			reset() {
+				this.pause()
+				this.remainTime = this.time
+				this.setRemainTime(this.remainTime)
+				if (this.autoStart) {
+					this.start()
+				}
+			},
+			// 暂停倒计时
+			pause() {
+				this.runing = false;
+				this.clearTimeout()
+			},
+			// 清空定时器
+			clearTimeout() {
+				clearTimeout(this.timer)
+				this.timer = null
 			}
-			minute = Math.floor(seconds / 60) - hour * 60 - day * 24 * 60;
-			second = Math.floor(seconds) - day * 24 * 60 * 60 - hour * 60 * 60 - minute * 60;
-			// 如果小于10,在前面补上一个"0"
-			showHour = showHour < 10 ? '0' + showHour : showHour;
-			minute = minute < 10 ? '0' + minute : minute;
-			second = second < 10 ? '0' + second : second;
-			day = day < 10 ? '0' + day : day;
-			this.d = day;
-			this.h = showHour;
-			this.i = minute;
-			this.s = second;
-		},
-		// 停止倒计时
-		end() {
-			this.clearTimer();
-			this.$emit('end', {});
 		},
-		// 清除定时器
-		clearTimer() {
-			if(this.timer) {
-				// 清除定时器
-				clearInterval(this.timer);
-				this.timer = null;
-			}
+		beforeDestroy() {
+			this.clearTimeout()
 		}
-	},
-	beforeDestroy() {
-		clearInterval(this.timer);
-		this.timer = null;
 	}
-};
 </script>
 
-<style scoped lang="scss">
-	@import "../../libs/css/style.components.scss";
+<style
+	lang="scss"
+	scoped
+>
+	@import "../../libs/css/components.scss";
+	$u-count-down-text-color:$u-content-color !default;
+	$u-count-down-text-font-size:15px !default;
+	$u-count-down-text-line-height:22px !default;
 
-	.u-countdown {
-		/* #ifndef APP-NVUE */
-		display: inline-flex;		
-		/* #endif */
-		align-items: center;
-	}
-
-	.u-countdown-item {
-		@include vue-flex;
-		align-items: center;
-		justify-content: center;
-		padding: 2rpx;
-		border-radius: 6rpx;
-		white-space: nowrap;
-		transform: translateZ(0);
-	}
-
-	.u-countdown-time {
-		margin: 0;
-		padding: 0;
-		line-height: 1;
-	}
-
-	.u-countdown-colon {
-		@include vue-flex;
-		justify-content: center;
-		padding: 0 5rpx;
-		line-height: 1;
-		align-items: center;
-		padding-bottom: 4rpx;
-	}
-
-	.u-countdown-scale {
-		transform: scale(0.9);
-		transform-origin: center center;
+	.u-count-down {
+		&__text {
+			color: $u-count-down-text-color;
+			font-size: $u-count-down-text-font-size;
+			line-height: $u-count-down-text-line-height;
+		}
 	}
 </style>

+ 62 - 0
uview-ui/components/u-count-down/utils.js

@@ -0,0 +1,62 @@
+// 补0,如1 -> 01
+function padZero(num, targetLength = 2) {
+    let str = `${num}`
+    while (str.length < targetLength) {
+        str = `0${str}`
+    }
+    return str
+}
+const SECOND = 1000
+const MINUTE = 60 * SECOND
+const HOUR = 60 * MINUTE
+const DAY = 24 * HOUR
+export function parseTimeData(time) {
+    const days = Math.floor(time / DAY)
+    const hours = Math.floor((time % DAY) / HOUR)
+    const minutes = Math.floor((time % HOUR) / MINUTE)
+    const seconds = Math.floor((time % MINUTE) / SECOND)
+    const milliseconds = Math.floor(time % SECOND)
+    return {
+        days,
+        hours,
+        minutes,
+        seconds,
+        milliseconds
+    }
+}
+export function parseFormat(format, timeData) {
+    let {
+        days,
+        hours,
+        minutes,
+        seconds,
+        milliseconds
+    } = timeData
+    // 如果格式化字符串中不存在DD(天),则将天的时间转为小时中去
+    if (format.indexOf('DD') === -1) {
+        hours += days * 24
+    } else {
+        // 对天补0
+        format = format.replace('DD', padZero(days))
+    }
+    // 其他同理于DD的格式化处理方式
+    if (format.indexOf('HH') === -1) {
+        minutes += hours * 60
+    } else {
+        format = format.replace('HH', padZero(hours))
+    }
+    if (format.indexOf('mm') === -1) {
+        seconds += minutes * 60
+    } else {
+        format = format.replace('mm', padZero(minutes))
+    }
+    if (format.indexOf('ss') === -1) {
+        milliseconds += seconds * 1000
+    } else {
+        format = format.replace('ss', padZero(seconds))
+    }
+    return format.replace('SSS', padZero(milliseconds, 3))
+}
+export function isSameSecond(time1, time2) {
+    return Math.floor(time1 / 1000) === Math.floor(time2 / 1000)
+}

+ 59 - 0
uview-ui/components/u-count-to/props.js

@@ -0,0 +1,59 @@
+export default {
+    props: {
+        // 开始的数值,默认从0增长到某一个数
+        startVal: {
+            type: [String, Number],
+            default: uni.$u.props.countTo.startVal
+        },
+        // 要滚动的目标数值,必须
+        endVal: {
+            type: [String, Number],
+            default: uni.$u.props.countTo.endVal
+        },
+        // 滚动到目标数值的动画持续时间,单位为毫秒(ms)
+        duration: {
+            type: [String, Number],
+            default: uni.$u.props.countTo.duration
+        },
+        // 设置数值后是否自动开始滚动
+        autoplay: {
+            type: Boolean,
+            default: uni.$u.props.countTo.autoplay
+        },
+        // 要显示的小数位数
+        decimals: {
+            type: [String, Number],
+            default: uni.$u.props.countTo.decimals
+        },
+        // 是否在即将到达目标数值的时候,使用缓慢滚动的效果
+        useEasing: {
+            type: Boolean,
+            default: uni.$u.props.countTo.useEasing
+        },
+        // 十进制分割
+        decimal: {
+            type: [String, Number],
+            default: uni.$u.props.countTo.decimal
+        },
+        // 字体颜色
+        color: {
+            type: String,
+            default: uni.$u.props.countTo.color
+        },
+        // 字体大小
+        fontSize: {
+            type: [String, Number],
+            default: uni.$u.props.countTo.fontSize
+        },
+        // 是否加粗字体
+        bold: {
+            type: Boolean,
+            default: uni.$u.props.countTo.bold
+        },
+        // 千位分隔符,类似金额的分割(¥23,321.05中的",")
+        separator: {
+            type: String,
+            default: uni.$u.props.countTo.separator
+        }
+    }
+}

+ 21 - 78
uview-ui/components/u-count-to/u-count-to.vue

@@ -1,94 +1,36 @@
 <template>
-	<view
+	<text
 		class="u-count-num"
 		:style="{
-			fontSize: fontSize + 'rpx',
+			fontSize: $u.addUnit(fontSize),
 			fontWeight: bold ? 'bold' : 'normal',
 			color: color
 		}"
-	>
-		{{ displayValue }}
-	</view>
+	>{{ displayValue }}</text>
 </template>
 
 <script>
+	import props from './props.js';
 /**
  * countTo 数字滚动
  * @description 该组件一般用于需要滚动数字到某一个值的场景,目标要求是一个递增的值。
  * @tutorial https://www.uviewui.com/components/countTo.html
- * @property {String Number} start-val 开始值
- * @property {String Number} end-val 结束值
- * @property {String Number} duration 滚动过程所需的时间,单位ms(默认2000)
- * @property {Boolean} autoplay 是否自动开始滚动(默认true)
- * @property {String Number} decimals 要显示的小数位数,见官网说明(默认0)
- * @property {Boolean} use-easing 滚动结束时,是否缓动结尾,见官网说明(默认true)
- * @property {String} separator 千位分隔符,见官网说明
- * @property {String} color 字体颜色(默认#303133)
- * @property {String Number} font-size 字体大小,单位rpx(默认50)
- * @property {Boolean} bold 字体是否加粗(默认false)
+ * @property {String | Number}	startVal	开始的数值,默认从0增长到某一个数(默认 0 )
+ * @property {String | Number}	endVal		要滚动的目标数值,必须 (默认 0 )
+ * @property {String | Number}	duration	滚动到目标数值的动画持续时间,单位为毫秒(ms) (默认 2000 )
+ * @property {Boolean}			autoplay	设置数值后是否自动开始滚动 (默认 true )
+ * @property {String | Number}	decimals	要显示的小数位数,见官网说明(默认 0 )
+ * @property {Boolean}			useEasing	滚动结束时,是否缓动结尾,见官网说明(默认 true )
+ * @property {String}			decimal		十进制分割 ( 默认 "." )
+ * @property {String}			color		字体颜色( 默认 '#606266' )
+ * @property {String | Number}	fontSize	字体大小,单位px( 默认 22 )
+ * @property {Boolean}			bold		字体是否加粗(默认 false )
+ * @property {String}			separator	千位分隔符,见官网说明
  * @event {Function} end 数值滚动到目标值时触发
  * @example <u-count-to ref="uCountTo" :end-val="endVal" :autoplay="autoplay"></u-count-to>
  */
 export default {
 	name: 'u-count-to',
-	props: {
-		// 开始的数值,默认从0增长到某一个数
-		startVal: {
-			type: [Number, String],
-			default: 0
-		},
-		// 要滚动的目标数值,必须
-		endVal: {
-			type: [Number, String],
-			default: 0,
-			required: true
-		},
-		// 滚动到目标数值的动画持续时间,单位为毫秒(ms)
-		duration: {
-			type: [Number, String],
-			default: 2000
-		},
-		// 设置数值后是否自动开始滚动
-		autoplay: {
-			type: Boolean,
-			default: true
-		},
-		// 要显示的小数位数
-		decimals: {
-			type: [Number, String],
-			default: 0
-		},
-		// 是否在即将到达目标数值的时候,使用缓慢滚动的效果
-		useEasing: {
-			type: Boolean,
-			default: true
-		},
-		// 十进制分割
-		decimal: {
-			type: [Number, String],
-			default: '.'
-		},
-		// 字体颜色
-		color: {
-			type: String,
-			default: '#303133'
-		},
-		// 字体大小
-		fontSize: {
-			type: [Number, String],
-			default: 50
-		},
-		// 是否加粗字体
-		bold: {
-			type: Boolean,
-			default: false
-		},
-		// 千位分隔符,类似金额的分割(¥23,321.05中的",")
-		separator: {
-			type: String,
-			default: ''
-		}
-	},
 	data() {
 		return {
 			localStartVal: this.startVal,
@@ -103,6 +45,7 @@ export default {
 			lastTime: 0 // 上一次的时间
 		};
 	},
+	mixins: [uni.$u.mpMixin, uni.$u.mixin,props],
 	computed: {
 		countDown() {
 			return this.startVal > this.endVal;
@@ -133,7 +76,6 @@ export default {
 			this.lastTime = currTime + timeToCall;
 			return id;
 		},
-
 		cancelAnimationFrame(id) {
 			clearTimeout(id);
 		},
@@ -161,7 +103,8 @@ export default {
 		},
 		// 重新开始(暂停的情况下)
 		resume() {
-			this.startTime = null;
+			if (!this.remaining) return
+			this.startTime = 0;
 			this.localDuration = this.remaining;
 			this.localStartVal = this.printVal;
 			this.requestAnimationFrame(this.count);
@@ -195,7 +138,7 @@ export default {
 			} else {
 				this.printVal = this.printVal > this.endVal ? this.endVal : this.printVal;
 			}
-			this.displayValue = this.formatNumber(this.printVal);
+			this.displayValue = this.formatNumber(this.printVal) || 0;
 			if (progress < this.localDuration) {
 				this.rAF = this.requestAnimationFrame(this.count);
 			} else {
@@ -230,11 +173,11 @@ export default {
 </script>
 
 <style lang="scss" scoped>
-@import "../../libs/css/style.components.scss";
+@import "../../libs/css/components.scss";
 
 .u-count-num {
 	/* #ifndef APP-NVUE */
-	display: inline-flex;		
+	display: inline-flex;
 	/* #endif */
 	text-align: center;
 }

+ 116 - 0
uview-ui/components/u-datetime-picker/props.js

@@ -0,0 +1,116 @@
+export default {
+    props: {
+        // 是否打开组件
+        show: {
+            type: Boolean,
+            default: uni.$u.props.datetimePicker.show
+        },
+        // 是否展示顶部的操作栏
+        showToolbar: {
+            type: Boolean,
+            default: uni.$u.props.datetimePicker.showToolbar
+        },
+        // 绑定值
+        value: {
+            type: [String, Number],
+            default: uni.$u.props.datetimePicker.value
+        },
+        // 顶部标题
+        title: {
+            type: String,
+            default: uni.$u.props.datetimePicker.title
+        },
+        // 展示格式,mode=date为日期选择,mode=time为时间选择,mode=year-month为年月选择,mode=datetime为日期时间选择
+        mode: {
+            type: String,
+            default: uni.$u.props.datetimePicker.mode
+        },
+        // 可选的最大时间
+        maxDate: {
+            type: Number,
+            // 最大默认值为后10年
+            default: uni.$u.props.datetimePicker.maxDate
+        },
+        // 可选的最小时间
+        minDate: {
+            type: Number,
+            // 最小默认值为前10年
+            default: uni.$u.props.datetimePicker.minDate
+        },
+        // 可选的最小小时,仅mode=time有效
+        minHour: {
+            type: Number,
+            default: uni.$u.props.datetimePicker.minHour
+        },
+        // 可选的最大小时,仅mode=time有效
+        maxHour: {
+            type: Number,
+            default: uni.$u.props.datetimePicker.maxHour
+        },
+        // 可选的最小分钟,仅mode=time有效
+        minMinute: {
+            type: Number,
+            default: uni.$u.props.datetimePicker.minMinute
+        },
+        // 可选的最大分钟,仅mode=time有效
+        maxMinute: {
+            type: Number,
+            default: uni.$u.props.datetimePicker.maxMinute
+        },
+        // 选项过滤函数
+        filter: {
+            type: [Function, null],
+            default: uni.$u.props.datetimePicker.filter
+        },
+        // 选项格式化函数
+        formatter: {
+            type: [Function, null],
+            default: uni.$u.props.datetimePicker.formatter
+        },
+        // 是否显示加载中状态
+        loading: {
+            type: Boolean,
+            default: uni.$u.props.datetimePicker.loading
+        },
+        // 各列中,单个选项的高度
+        itemHeight: {
+            type: [String, Number],
+            default: uni.$u.props.datetimePicker.itemHeight
+        },
+        // 取消按钮的文字
+        cancelText: {
+            type: String,
+            default: uni.$u.props.datetimePicker.cancelText
+        },
+        // 确认按钮的文字
+        confirmText: {
+            type: String,
+            default: uni.$u.props.datetimePicker.confirmText
+        },
+        // 取消按钮的颜色
+        cancelColor: {
+            type: String,
+            default: uni.$u.props.datetimePicker.cancelColor
+        },
+        // 确认按钮的颜色
+        confirmColor: {
+            type: String,
+            default: uni.$u.props.datetimePicker.confirmColor
+        },
+        // 每列中可见选项的数量
+        visibleItemCount: {
+            type: [String, Number],
+            default: uni.$u.props.datetimePicker.visibleItemCount
+        },
+        // 是否允许点击遮罩关闭选择器
+        closeOnClickOverlay: {
+            type: Boolean,
+            default: uni.$u.props.datetimePicker.closeOnClickOverlay
+        },
+        // 各列的默认索引
+        defaultIndex: {
+            type: Array,
+            default: uni.$u.props.datetimePicker.defaultIndex
+        }
+    }
+}

+ 360 - 0
uview-ui/components/u-datetime-picker/u-datetime-picker.vue

@@ -0,0 +1,360 @@
+<template>
+	<u-picker
+		ref="picker"
+		:show="show"
+		:closeOnClickOverlay="closeOnClickOverlay"
+		:columns="columns"
+		:title="title"
+		:itemHeight="itemHeight"
+		:showToolbar="showToolbar"
+		:visibleItemCount="visibleItemCount"
+		:defaultIndex="innerDefaultIndex"
+		:cancelText="cancelText"
+		:confirmText="confirmText"
+		:cancelColor="cancelColor"
+		:confirmColor="confirmColor"
+		@close="close"
+		@cancel="cancel"
+		@confirm="confirm"
+		@change="change"
+	>
+	</u-picker>
+</template>
+
+<script>
+	function times(n, iteratee) {
+	    let index = -1
+	    const result = Array(n < 0 ? 0 : n)
+	    while (++index < n) {
+	        result[index] = iteratee(index)
+	    }
+	    return result
+	}
+	import props from './props.js';
+	import dayjs from '../../libs/util/dayjs.js';
+	/**
+	 * DatetimePicker 时间日期选择器
+	 * @description 此选择器用于时间日期
+	 * @tutorial https://www.uviewui.com/components/datetimePicker.html
+	 * @property {Boolean}			show				用于控制选择器的弹出与收起 ( 默认 false )
+	 * @property {Boolean}			showToolbar			是否显示顶部的操作栏  ( 默认 true )
+	 * @property {String | Number}	value				绑定值
+	 * @property {String}			title				顶部标题
+	 * @property {String}			mode				展示格式 mode=date为日期选择,mode=time为时间选择,mode=year-month为年月选择,mode=datetime为日期时间选择  ( 默认 ‘datetime )
+	 * @property {Number}			maxDate				可选的最大时间  默认值为后10年
+	 * @property {Number}			minDate				可选的最小时间  默认值为前10年
+	 * @property {Number}			minHour				可选的最小小时,仅mode=time有效   ( 默认 0 )
+	 * @property {Number}			maxHour				可选的最大小时,仅mode=time有效	  ( 默认 23 )
+	 * @property {Number}			minMinute			可选的最小分钟,仅mode=time有效	  ( 默认 0 )
+	 * @property {Number}			maxMinute			可选的最大分钟,仅mode=time有效   ( 默认 59 )
+	 * @property {Function}			filter				选项过滤函数
+	 * @property {Function}			formatter			选项格式化函数
+	 * @property {Boolean}			loading				是否显示加载中状态   ( 默认 false )
+	 * @property {String | Number}	itemHeight			各列中,单个选项的高度   ( 默认 44 )
+	 * @property {String}			cancelText			取消按钮的文字  ( 默认 '取消' )
+	 * @property {String}			confirmText			确认按钮的文字  ( 默认 '确认' )
+	 * @property {String}			cancelColor			取消按钮的颜色  ( 默认 '#909193' )
+	 * @property {String}			confirmColor		确认按钮的颜色  ( 默认 '#3c9cff' )
+	 * @property {String | Number}	visibleItemCount	每列中可见选项的数量  ( 默认 5 )
+	 * @property {Boolean}			closeOnClickOverlay	是否允许点击遮罩关闭选择器  ( 默认 false )
+	 * @property {Array}			defaultIndex		各列的默认索引
+	 * @event {Function} close 关闭选择器时触发
+	 * @event {Function} confirm 点击确定按钮,返回当前选择的值
+	 * @event {Function} change 当选择值变化时触发
+	 * @event {Function} cancel 点击取消按钮
+	 * @example  <u-datetime-picker :show="show" :value="value1"  mode="datetime" ></u-datetime-picker>
+	 */
+	export default {
+		name: 'datetime-picker',
+		mixins: [uni.$u.mpMixin, uni.$u.mixin, props],
+		data() {
+			return {
+				columns: [],
+				innerDefaultIndex: [],
+				innerFormatter: (type, value) => value
+			}
+		},
+		watch: {
+			show(newValue, oldValue) {
+				if (newValue) {
+					this.updateColumnValue(this.innerValue)
+				}
+			},
+			propsChange() {
+				this.init()
+			}
+		},
+		computed: {
+			// 如果以下这些变量发生了变化,意味着需要重新初始化各列的值
+			propsChange() {
+				return [this.mode, this.maxDate, this.minDate, this.minHour, this.maxHour, this.minMinute, this.maxMinute, this.filter, ]
+			}
+		},
+		mounted() {
+			this.init()
+		},
+		methods: {
+			init() {
+				this.innerValue = this.correctValue(this.value)
+				this.updateColumnValue(this.innerValue)
+			},
+			// 在微信小程序中,不支持将函数当做props参数,故只能通过ref形式调用
+			setFormatter(e) {
+				this.innerFormatter = e
+			},
+			// 关闭选择器
+			close() {
+				if (this.closeOnClickOverlay) {
+					this.$emit('close')
+				}
+			},
+			// 点击工具栏的取消按钮
+			cancel() {
+				this.$emit('cancel')
+			},
+			// 点击工具栏的确定按钮
+			confirm() {
+				this.$emit('confirm', {
+					value: this.innerValue,
+					mode: this.mode
+				})
+				this.$emit('input', this.innerValue)
+			},
+			//用正则截取输出值,当出现多组数字时,抛出错误
+			intercept(e,type){
+				let judge = e.match(/\d+/g)
+				//判断是否掺杂数字
+				if(judge.length>1){
+					uni.$u.error("请勿在过滤或格式化函数时添加数字")
+					return 0
+				}else if(type&&judge[0].length==4){//判断是否是年份
+					return judge[0]
+				}else if(judge[0].length>2){
+					uni.$u.error("请勿在过滤或格式化函数时添加数字")
+					return 0
+				}else{
+					return judge[0]
+				}
+			},
+			// 列发生变化时触发
+			change(e) {
+				const { indexs, values } = e
+				let selectValue = ''
+				if(this.mode === 'time') {
+					// 根据value各列索引,从各列数组中,取出当前时间的选中值
+					selectValue = `${this.intercept(values[0][indexs[0]])}:${this.intercept(values[1][indexs[1]])}`
+				} else {
+					// 将选择的值转为数值,比如'03'转为数值的3,'2019'转为数值的2019
+					const year = parseInt(this.intercept(values[0][indexs[0]],'year'))
+					const month = parseInt(this.intercept(values[1][indexs[1]]))
+					let date = parseInt(values[2] ? this.intercept(values[2][indexs[2]]) : 1)
+					let hour = 0, minute = 0
+					// 此月份的最大天数
+					const maxDate = dayjs(`${year}-${month}`).daysInMonth()
+					// year-month模式下,date不会出现在列中,设置为1,为了符合后边需要减1的需求
+					if (this.mode === 'year-month') {
+					    date = 1
+					}
+					// 不允许超过maxDate值
+					date = Math.min(maxDate, date)
+					if (this.mode === 'datetime') {
+					    hour = parseInt(this.intercept(values[3][indexs[3]]))
+					    minute = parseInt(this.intercept(values[4][indexs[4]]))
+					}
+					// 转为时间模式
+					selectValue = Number(new Date(year, month - 1, date, hour, minute))
+				}
+				// 取出准确的合法值,防止超越边界的情况
+				selectValue = this.correctValue(selectValue)
+				this.innerValue = selectValue
+				this.updateColumnValue(selectValue)
+				// 发出change时间,value为当前选中的时间戳
+				this.$emit('change', {
+					value: selectValue,
+					// #ifndef MP-WEIXIN
+					// 微信小程序不能传递this实例,会因为循环引用而报错
+					picker: this.$refs.picker,
+					// #endif
+					mode: this.mode
+				})
+			},
+			// 更新各列的值,进行补0、格式化等操作
+			updateColumnValue(value) {
+				this.innerValue = value
+				this.updateColumns()
+				this.updateIndexs(value)
+			},
+			// 更新索引
+			updateIndexs(value) {
+				let values = []
+				const formatter = this.formatter || this.innerFormatter
+				const padZero = uni.$u.padZero
+				if (this.mode === 'time') {
+					// 将time模式的时间用:分隔成数组
+				    const timeArr = value.split(':')
+					// 使用formatter格式化方法进行管道处理
+				    values = [formatter('hour', timeArr[0]), formatter('minute', timeArr[1])]
+				} else {
+				    const date = new Date(value)
+				    values = [
+				        formatter('year', `${dayjs(value).year()}`),
+						// 月份补0
+				        formatter('month', padZero(dayjs(value).month() + 1))
+				    ]
+				    if (this.mode === 'date') {
+						// date模式,需要添加天列
+				        values.push(formatter('day', padZero(dayjs(value).date())))
+				    }
+				    if (this.mode === 'datetime') {
+						// 数组的push方法,可以写入多个参数
+				        values.push(formatter('day', padZero(dayjs(value).date())), formatter('hour', padZero(dayjs(value).hour())), formatter('minute', padZero(dayjs(value).minute())))
+				    }
+				}
+
+				// 根据当前各列的所有值,从各列默认值中找到默认值在各列中的索引
+				const indexs = this.columns.map((column, index) => {
+					// 通过取大值,可以保证不会出现找不到索引的-1情况
+					return Math.max(0, column.findIndex(item => item === values[index]))
+				})
+				this.innerDefaultIndex = indexs
+			},
+			// 更新各列的值
+			updateColumns() {
+			    const formatter = this.formatter || this.innerFormatter
+				// 获取各列的值,并且map后,对各列的具体值进行补0操作
+			    const results = this.getOriginColumns().map((column) => column.values.map((value) => formatter(column.type, value)))
+				this.columns = results
+			},
+			getOriginColumns() {
+			    // 生成各列的值
+			    const results = this.getRanges().map(({ type, range }) => {
+			        let values = times(range[1] - range[0] + 1, (index) => {
+			            let value = range[0] + index
+			            value = type === 'year' ? `${value}` : uni.$u.padZero(value)
+			            return value
+			        })
+					// 进行过滤
+			        if (this.filter) {
+			            values = this.filter(type, values)
+			        }
+			        return { type, values }
+			    })
+			    return results
+			},
+			// 通过最大值和最小值生成数组
+			generateArray(start, end) {
+				return Array.from(new Array(end + 1).keys()).slice(start)
+			},
+			// 得出合法的时间
+			correctValue(value) {
+				const isDateMode = this.mode !== 'time'
+				if (isDateMode && !uni.$u.test.date(value)) {
+					// 如果是日期类型,但是又没有设置合法的当前时间的话,使用最小时间为当前时间
+					value = this.minDate
+				} else if (!isDateMode && !value) {
+					// 如果是时间类型,而又没有默认值的话,就用最小时间
+					value = `${uni.$u.padZero(this.minHour)}:${uni.$u.padZero(this.minMinute)}`
+				}
+				// 时间类型
+				if (!isDateMode) {
+					if (String(value).indexOf(':') === -1) return uni.$u.error('时间错误,请传递如12:24的格式')
+					let [hour, minute] = value.split(':')
+					// 对时间补零,同时控制在最小值和最大值之间
+					hour = uni.$u.padZero(uni.$u.range(this.minHour, this.maxHour, Number(hour)))
+					minute = uni.$u.padZero(uni.$u.range(this.minMinute, this.maxMinute, Number(minute)))
+					return `${ hour }:${ minute }`
+				} else {
+					// 如果是日期格式,控制在最小日期和最大日期之间
+					value = dayjs(value).isBefore(dayjs(this.minDate)) ? this.minDate : value
+					value = dayjs(value).isAfter(dayjs(this.maxDate)) ? this.maxDate : value
+					return value
+				}
+			},
+			// 获取每列的最大和最小值
+			getRanges() {
+			    if (this.mode === 'time') {
+			        return [
+			            {
+			                type: 'hour',
+			                range: [this.minHour, this.maxHour],
+			            },
+			            {
+			                type: 'minute',
+			                range: [this.minMinute, this.maxMinute],
+			            },
+			        ];
+			    }
+			    const { maxYear, maxDate, maxMonth, maxHour, maxMinute, } = this.getBoundary('max', this.innerValue);
+			    const { minYear, minDate, minMonth, minHour, minMinute, } = this.getBoundary('min', this.innerValue);
+			    const result = [
+			        {
+			            type: 'year',
+			            range: [minYear, maxYear],
+			        },
+			        {
+			            type: 'month',
+			            range: [minMonth, maxMonth],
+			        },
+			        {
+			            type: 'day',
+			            range: [minDate, maxDate],
+			        },
+			        {
+			            type: 'hour',
+			            range: [minHour, maxHour],
+			        },
+			        {
+			            type: 'minute',
+			            range: [minMinute, maxMinute],
+			        },
+			    ];
+			    if (this.mode === 'date')
+			        result.splice(3, 2);
+			    if (this.mode === 'year-month')
+			        result.splice(2, 3);
+			    return result;
+			},
+			// 根据minDate、maxDate、minHour、maxHour等边界值,判断各列的开始和结束边界值
+			getBoundary(type, innerValue) {
+			    const value = new Date(innerValue)
+			    const boundary = new Date(this[`${type}Date`])
+			    const year = dayjs(boundary).year()
+			    let month = 1
+			    let date = 1
+			    let hour = 0
+			    let minute = 0
+			    if (type === 'max') {
+			        month = 12
+					// 月份的天数
+			        date = dayjs(value).daysInMonth()
+			        hour = 23
+			        minute = 59
+			    }
+				// 获取边界值,逻辑是:当年达到了边界值(最大或最小年),就检查月允许的最大和最小值,以此类推
+			    if (dayjs(value).year() === year) {
+			        month = dayjs(boundary).month() + 1
+			        if (dayjs(value).month() + 1 === month) {
+			            date = dayjs(boundary).date()
+			            if (dayjs(value).date() === date) {
+			                hour = dayjs(boundary).hour()
+			                if (dayjs(value).hour() === hour) {
+			                    minute = dayjs(boundary).minute()
+			                }
+			            }
+			        }
+			    }
+			    return {
+			        [`${type}Year`]: year,
+			        [`${type}Month`]: month,
+			        [`${type}Date`]: date,
+			        [`${type}Hour`]: hour,
+			        [`${type}Minute`]: minute
+			    }
+			},
+		},
+	}
+</script>
+
+<style lang="scss" scoped>
+	@import '../../libs/css/components.scss';
+</style>

+ 44 - 0
uview-ui/components/u-divider/props.js

@@ -0,0 +1,44 @@
+export default {
+    props: {
+        // 是否虚线
+        dashed: {
+            type: Boolean,
+            default: uni.$u.props.divider.dashed
+        },
+        // 是否细线
+        hairline: {
+            type: Boolean,
+            default: uni.$u.props.divider.hairline
+        },
+        // 是否以点替代文字,优先于text字段起作用
+        dot: {
+            type: Boolean,
+            default: uni.$u.props.divider.dot
+        },
+        // 内容文本的位置,left-左边,center-中间,right-右边
+        textPosition: {
+            type: String,
+            default: uni.$u.props.divider.textPosition
+        },
+        // 文本内容
+        text: {
+            type: [String, Number],
+            default: uni.$u.props.divider.text
+        },
+        // 文本大小
+        textSize: {
+            type: [String, Number],
+            default: uni.$u.props.divider.textSize
+        },
+        // 文本颜色
+        textColor: {
+            type: String,
+            default: uni.$u.props.divider.textColor
+        },
+        // 线条颜色
+        lineColor: {
+            type: String,
+            default: uni.$u.props.divider.lineColor
+        }
+    }
+}

+ 102 - 139
uview-ui/components/u-divider/u-divider.vue

@@ -1,153 +1,116 @@
 <template>
-	<view class="u-divider" :style="{
-		height: height == 'auto' ? 'auto' : height + 'rpx',
-		backgroundColor: bgColor,
-		marginBottom: marginBottom + 'rpx',
-		marginTop: marginTop + 'rpx'
-	}" @tap="click">
-		<view class="u-divider-line" :class="[type ? 'u-divider-line--bordercolor--' + type : '']" :style="[lineStyle]"></view>
-		<view v-if="useSlot" class="u-divider-text" :style="{
-			color: color,
-			fontSize: fontSize + 'rpx'
-		}"><slot /></view>
-		<view class="u-divider-line" :class="[type ? 'u-divider-line--bordercolor--' + type : '']" :style="[lineStyle]"></view>
+	<view
+	    class="u-divider"
+	    :style="[$u.addStyle(customStyle)]"
+		@tap="click"
+	>
+		<u-line
+		    :color="lineColor"
+		    :customStyle="leftLineStyle"
+		    :hairline="hairline"
+			:dashed="dashed"
+		></u-line>
+		<text
+		    v-if="dot"
+		    class="u-divider__dot"
+		>●</text>
+		<text
+		    v-else-if="text"
+		    class="u-divider__text"
+		    :style="[textStyle]"
+		>{{text}}</text>
+		<u-line
+		    :color="lineColor"
+		    :customStyle="rightLineStyle"
+		    :hairline="hairline"
+			:dashed="dashed"
+		></u-line>
 	</view>
 </template>
 
 <script>
-/**
- * divider 分割线
- * @description 区隔内容的分割线,一般用于页面底部"没有更多"的提示。
- * @tutorial https://www.uviewui.com/components/divider.html
- * @property {String Number} half-width 文字左或右边线条宽度,数值或百分比,数值时单位为rpx
- * @property {String} border-color 线条颜色,优先级高于type(默认#dcdfe6)
- * @property {String} color 文字颜色(默认#909399)
- * @property {String Number} fontSize 字体大小,单位rpx(默认26)
- * @property {String} bg-color 整个divider的背景颜色(默认呢#ffffff)
- * @property {String Number} height 整个divider的高度,单位rpx(默认40)
- * @property {String} type 将线条设置主题色(默认primary)
- * @property {Boolean} useSlot 是否使用slot传入内容,如果不传入,中间不会有空隙(默认true)
- * @property {String Number} margin-top 与前一个组件的距离,单位rpx(默认0)
- * @property {String Number} margin-bottom 与后一个组件的距离,单位rpx(0)
- * @event {Function} click divider组件被点击时触发
- * @example <u-divider color="#fa3534">长河落日圆</u-divider>
- */
-export default {
-	name: 'u-divider',
-	props: {
-		// 单一边divider横线的宽度(数值),单位rpx。或者百分比
-		halfWidth: {
-			type: [Number, String],
-			default: 150
+	import props from './props.js';
+	/**
+	 * divider 分割线
+	 * @description 区隔内容的分割线,一般用于页面底部"没有更多"的提示。
+	 * @tutorial https://www.uviewui.com/components/divider.html
+	 * @property {Boolean}			dashed			是否虚线 (默认 false )
+	 * @property {Boolean}			hairline		是否细线 (默认  true )
+	 * @property {Boolean}			dot				是否以点替代文字,优先于text字段起作用 (默认 false )
+	 * @property {String}			textPosition	内容文本的位置,left-左边,center-中间,right-右边 (默认 'center' )
+	 * @property {String | Number}	text			文本内容
+	 * @property {String | Number}	textSize		文本大小 (默认 14)
+	 * @property {String}			textColor		文本颜色 (默认 '#909399' )
+	 * @property {String}			lineColor		线条颜色 (默认 '#dcdfe6' )
+	 * @property {Object}			customStyle		定义需要用到的外部样式
+	 *
+	 * @event {Function}	click	divider组件被点击时触发
+	 * @example <u-divider :color="color">锦瑟无端五十弦</u-divider>
+	 */
+	export default {
+		name:'u-divider',
+		mixins: [uni.$u.mpMixin, uni.$u.mixin,props],
+		computed: {
+			textStyle() {
+				const style = {}
+				style.fontSize = uni.$u.addUnit(this.textSize)
+				style.color = this.textColor
+				return style
+			},
+			// 左边线条的的样式
+			leftLineStyle() {
+				const style = {}
+				// 如果是在左边,设置左边的宽度为固定值
+				if (this.textPosition === 'left') {
+					style.width = '80rpx'
+				} else {
+					style.flex = 1
+				}
+				return style
+			},
+			// 右边线条的的样式
+			rightLineStyle() {
+				const style = {}
+				// 如果是在右边,设置右边的宽度为固定值
+				if (this.textPosition === 'right') {
+					style.width = '80rpx'
+				} else {
+					style.flex = 1
+				}
+				return style
+			}
 		},
-		// divider横线的颜色,如设置,
-		borderColor: {
-			type: String,
-			default: '#dcdfe6'
-		},
-		// 主题色,可以是primary|info|success|warning|error之一值
-		type: {
-			type: String,
-			default: 'primary'
-		},
-		// 文字颜色
-		color: {
-			type: String,
-			default: '#909399'
-		},
-		// 文字大小,单位rpx
-		fontSize: {
-			type: [Number, String],
-			default: 26
-		},
-		// 整个divider的背景颜色
-		bgColor: {
-			type: String,
-			default: '#ffffff'
-		},
-		// 整个divider的高度单位rpx
-		height: {
-			type: [Number, String],
-			default: 'auto'
-		},
-		// 上边距
-		marginTop: {
-			type: [String, Number],
-			default: 0
-		},
-		// 下边距
-		marginBottom: {
-			type: [String, Number],
-			default: 0
-		},
-		// 是否使用slot传入内容,如果不用slot传入内容,先的中间就不会有空隙
-		useSlot: {
-			type: Boolean,
-			default: true
-		}
-	},
-	computed: {
-		lineStyle() {
-			let style = {};
-			if(String(this.halfWidth).indexOf('%') != -1) style.width = this.halfWidth;
-			else style.width = this.halfWidth + 'rpx';
-			// borderColor优先级高于type值
-			if(this.borderColor) style.borderColor = this.borderColor;
-			return style;
-		}
-	},
-	methods: {
-		click() {
-			this.$emit('click');
+		methods: {
+			// divider组件被点击时触发
+			click() {
+				this.$emit('click');
+			}
 		}
 	}
-};
 </script>
 
 <style lang="scss" scoped>
-@import "../../libs/css/style.components.scss";
-.u-divider {
-	width: 100%;
-	position: relative;
-	text-align: center;
-	@include vue-flex;
-	justify-content: center;
-	align-items: center;
-	overflow: hidden;
-	flex-direction: row;
-}
+	@import '../../libs/css/components.scss';
+	$u-divider-margin:15px 0 !default;
+	$u-divider-text-margin:0 15px !default;
+	$u-divider-dot-font-size:12px !default;
+	$u-divider-dot-margin:0 12px !default;
+	$u-divider-dot-color: #c0c4cc !default;
 
-.u-divider-line {
-	border-bottom: 1px solid $u-border-color;
-	transform: scale(1, 0.5);
-	transform-origin: center;
-	
-	&--bordercolor--primary {
-		border-color: $u-type-primary;
-	}
-	
-	&--bordercolor--success {
-		border-color: $u-type-success;
-	}
-	
-	&--bordercolor--error {
-		border-color: $u-type-primary;
-	}
-	
-	&--bordercolor--info {
-		border-color: $u-type-info;
-	}
-	
-	&--bordercolor--warning {
-		border-color: $u-type-warning;
-	}
-}
+	.u-divider {
+		@include flex;
+		flex-direction: row;
+		align-items: center;
+		margin: $u-divider-margin;
 
-.u-divider-text {
-	white-space: nowrap;
-	padding: 0 16rpx;
-	/* #ifndef APP-NVUE */
-	display: inline-flex;		
-	/* #endif */
-}
+		&__text {
+			margin: $u-divider-text-margin;
+		}
+
+		&__dot {
+			font-size: $u-divider-dot-font-size;
+			margin: $u-divider-dot-margin;
+			color: $u-divider-dot-color;
+		}
+	}
 </style>

+ 36 - 0
uview-ui/components/u-dropdown-item/props.js

@@ -0,0 +1,36 @@
+export default {
+    props: {
+        // 当前选中项的value值
+        value: {
+            type: [Number, String, Array],
+            default: ''
+        },
+        // 菜单项标题
+        title: {
+            type: [String, Number],
+            default: ''
+        },
+        // 选项数据,如果传入了默认slot,此参数无效
+        options: {
+            type: Array,
+            default() {
+                return []
+            }
+        },
+        // 是否禁用此菜单项
+        disabled: {
+            type: Boolean,
+            default: false
+        },
+        // 下拉弹窗的高度
+        height: {
+            type: [Number, String],
+            default: 'auto'
+        },
+        // 点击遮罩是否可以收起弹窗
+        closeOnClickOverlay: {
+            type: Boolean,
+            default: true
+        }
+    }
+}

+ 118 - 104
uview-ui/components/u-dropdown-item/u-dropdown-item.vue

@@ -1,132 +1,146 @@
 <template>
-	<view class="u-dropdown-item" v-if="active" @touchmove.stop.prevent="() => {}" @tap.stop.prevent="() => {}">
-		<block v-if="!$slots.default && !$slots.$default">
-			<scroll-view scroll-y="true" :style="{
-				height: $u.addUnit(height)
-			}">
-				<view class="u-dropdown-item__options">
-					<u-cell-group>
-						<u-cell-item @click="cellClick(item.value)" :arrow="false" :title="item.label" v-for="(item, index) in options"
-						 :key="index" :title-style="{
-							color: value == item.value ? activeColor : inactiveColor
-						}">
-							<u-icon v-if="value == item.value" name="checkbox-mark" :color="activeColor" size="32"></u-icon>
-						</u-cell-item>
-					</u-cell-group>
-				</view>
-			</scroll-view>
-		</block>
-		<slot v-else />
+	<view class="u-drawdown-item">
+		<u-overlay
+			customStyle="top: 126px"
+			:show="show"
+			:closeOnClickOverlay="closeOnClickOverlay"
+			@click="overlayClick"
+		></u-overlay>
+		<view
+			class="u-drawdown-item__content"
+			:style="[style]"
+			:animation="animationData"
+			ref="animation"
+		>
+			<slot />
+		</view>
 	</view>
 </template>
 
 <script>
+	// #ifdef APP-NVUE
+	const animation = uni.requireNativePlugin('animation')
+	const dom = uni.requireNativePlugin('dom')
+	// #endif
+	import props from './props.js';
 	/**
-	 * dropdown-item 下拉菜单
-	 * @description 该组件一般用于向下展开菜单,同时可切换多个选项卡的场景
-	 * @tutorial http://uviewui.com/components/dropdown.html
-	 * @property {String | Number} v-model 双向绑定选项卡选择值
-	 * @property {String} title 菜单项标题
-	 * @property {Array[Object]} options 选项数据,如果传入了默认slot,此参数无效
-	 * @property {Boolean} disabled 是否禁用此选项卡(默认false)
-	 * @property {String | Number} duration 选项卡展开和收起的过渡时间,单位ms(默认300)
-	 * @property {String | Number} height 弹窗下拉内容的高度(内容超出将会滚动)(默认auto)
-	 * @example <u-dropdown-item title="标题"></u-dropdown-item>
+	 * Drawdownitem
+	 * @description 
+	 * @tutorial url
+	 * @property {String}
+	 * @event {Function}
+	 * @example
 	 */
 	export default {
-		name: 'u-dropdown-item',
-		props: {
-			// 当前选中项的value值
-			value: {
-				type: [Number, String, Array],
-				default: ''
-			},
-			// 菜单项标题
-			title: {
-				type: [String, Number],
-				default: ''
-			},
-			// 选项数据,如果传入了默认slot,此参数无效
-			options: {
-				type: Array,
-				default () {
-					return []
-				}
-			},
-			// 是否禁用此菜单项
-			disabled: {
-				type: Boolean,
-				default: false
-			},
-			// 下拉弹窗的高度
-			height: {
-				type: [Number, String],
-				default: 'auto'
-			},
-		},
+		name: 'u-drawdown-item',
+		mixins: [uni.$u.mpMixin, uni.$u.mixin, props],
 		data() {
 			return {
-				active: false, // 当前项是否处于展开状态
-				activeColor: '#2979ff', // 激活时左边文字和右边对勾图标的颜色
-				inactiveColor: '#606266', // 未激活时左边文字和右边对勾图标的颜色
+				show: false,
+				top: '126px',
+				// uni.createAnimation的导出数据
+				animationData: {},
 			}
 		},
-		computed: {
-			// 监听props是否发生了变化,有些值需要传递给父组件u-dropdown,无法双向绑定
-			propsChange() {
-				return `${this.title}-${this.disabled}`;
-			}
+		mounted() {
+			this.init()
 		},
 		watch: {
-			propsChange(n) {
-				// 当值变化时,通知父组件重新初始化,让父组件执行每个子组件的init()方法
-				// 将所有子组件数据重新整理一遍
-				if (this.parent) this.parent.init();
+			// 发生变化时,需要去更新父组件对应的值
+			dataChange(newValue, oldValue) {
+				this.updateParentData()
 			}
 		},
-		created() {
-			// 父组件的实例
-			this.parent = false;
+		computed: {
+			// 监听对应变量的变化
+			dataChange() {
+				return [this.title, this.disabled]
+			},
+			style() {
+				const style = {
+					zIndex: 10071,
+					position: 'fixed',
+					display: 'flex',
+					left: 0,
+					right: 0
+				}
+				style.top = uni.$u.addUnit(this.top)
+				return style
+			}
 		},
 		methods: {
 			init() {
+				this.updateParentData()
+			},
+			// 更新父组件所需的数据
+			updateParentData() {
 				// 获取父组件u-dropdown
-				let parent = this.$u.$parent.call(this, 'u-dropdown');
-				if (parent) {
-					this.parent = parent;
-					// 将子组件的激活颜色配置为父组件设置的激活和未激活时的颜色
-					this.activeColor = parent.activeColor;
-					this.inactiveColor = parent.inactiveColor;
-					// 将本组件的this,放入到父组件的children数组中,让父组件可以操作本(子)组件的方法和属性
-					// push进去前,显判断是否已经存在了本实例,因为在子组件内部数据变化时,会通过父组件重新初始化子组件
-					let exist = parent.children.find(val => {
-						return this === val;
-					})
-					if (!exist) parent.children.push(this);
-					if (parent.children.length == 1) this.active = true;
-					// 父组件无法监听children的变化,故将子组件的title,传入父组件的menuList数组中
-					parent.menuList.push({
-						title: this.title,
-						disabled: this.disabled
-					});
+				this.getParentData('u-dropdown')
+				if (!this.parent) uni.$u.error('u-dropdown-item必须配合u-dropdown使用')
+				// 查找父组件menuList数组中对应的标题数据
+				const menuIndex = this.parent.menuList.findIndex(item => item.title === this.title)
+				const menuContent = {
+					title: this.title,
+					disabled: this.disabled
+				}
+				if (menuIndex >= 0) {
+					// 如果能找到,则直接修改
+					this.parent.menuList[menuIndex] = menuContent;
+				} else {
+					// 如果无法找到,则为第一次添加,直接push即可
+					this.parent.menuList.push(menuContent);
 				}
 			},
-			// cell被点击
-			cellClick(value) {
-				// 修改通过v-model绑定的值
-				this.$emit('input', value);
-				// 通知父组件(u-dropdown)收起菜单
-				this.parent.close();
-				// 发出事件,抛出当前勾选项的value
-				this.$emit('change', value);
+			async setContentAnimate(height) {
+				this.animating = true
+				// #ifdef APP-NVUE
+				const ref = this.$refs['animation'].ref
+				animation.transition(ref, {
+					styles: {
+						height: uni.$u.addUnit(height)
+					},
+					duration: this.duration,
+					timingFunction: 'ease-in-out',
+				}, () => {
+					this.animating = false
+				})
+				// #endif
+			
+				// #ifndef APP-NVUE
+				const animation = uni.createAnimation({
+					timingFunction: 'ease-in-out',
+				});
+				animation
+					.height(height)
+					.step({
+						duration: this.duration,
+					})
+					.step()
+				// 导出动画数据给面板的animationData值
+				this.animationData = animation.export()
+				// 标识动画结束
+				uni.$u.sleep(this.duration).then(() => {
+					this.animating = false
+				})
+				// #endif
+			},
+			overlayClick() {
+				this.show = false
+				this.setContentAnimate(0)
 			}
 		},
-		mounted() {
-			this.init();
-		}
 	}
 </script>
 
-<style scoped lang="scss">
-	@import "../../libs/css/style.components.scss";
+<style lang="scss" scoped>
+	@import '../../libs/css/components.scss';
+	
+	.u-drawdown-item {
+		
+		&__content {
+			background-color: #FFFFFF;
+			overflow: hidden;
+			height: 0;
+		}
+	}
 </style>

+ 65 - 0
uview-ui/components/u-dropdown/props.js

@@ -0,0 +1,65 @@
+export default {
+    props: {
+        // 标题选中时的样式
+        activeStyle: {
+            type: [String, Object],
+            default: () => ({
+                color: '#2979ff',
+                fontSize: '14px'
+            })
+        },
+        // 标题未选中时的样式
+        inactiveStyle: {
+            type: [String, Object],
+            default: () => ({
+                color: '#606266',
+                fontSize: '14px'
+            })
+        },
+        // 点击遮罩是否关闭菜单
+        closeOnClickMask: {
+            type: Boolean,
+            default: true
+        },
+        // 点击当前激活项标题是否关闭菜单
+        closeOnClickSelf: {
+            type: Boolean,
+            default: true
+        },
+        // 过渡时间
+        duration: {
+            type: [Number, String],
+            default: 300
+        },
+        // 标题菜单的高度
+        height: {
+            type: [Number, String],
+            default: 40
+        },
+        // 是否显示下边框
+        borderBottom: {
+            type: Boolean,
+            default: false
+        },
+        // 标题的字体大小
+        titleSize: {
+            type: [Number, String],
+            default: 14
+        },
+        // 下拉出来的内容部分的圆角值
+        borderRadius: {
+            type: [Number, String],
+            default: 0
+        },
+        // 菜单右侧的icon图标
+        menuIcon: {
+            type: String,
+            default: 'arrow-down'
+        },
+        // 菜单右侧图标的大小
+        menuIconSize: {
+            type: [Number, String],
+            default: 14
+        }
+    }
+}

+ 80 - 251
uview-ui/components/u-dropdown/u-dropdown.vue

@@ -1,298 +1,127 @@
 <template>
-	<view class="u-dropdown">
-		<view class="u-dropdown__menu" :style="{
-			height: $u.addUnit(height)
-		}" :class="{
-			'u-border-bottom': borderBottom
-		}">
-			<view class="u-dropdown__menu__item" v-for="(item, index) in menuList" :key="index" @tap.stop="menuClick(index)">
-				<view class="u-flex">
-					<text class="u-dropdown__menu__item__text" :style="{
-						color: item.disabled ? '#c0c4cc' : (index === current || highlightIndex == index) ? activeColor : inactiveColor,
-						fontSize: $u.addUnit(titleSize)
-					}">{{item.title}}</text>
-					<view class="u-dropdown__menu__item__arrow" :class="{
-						'u-dropdown__menu__item__arrow--rotate': index === current
-					}">
-						<u-icon :custom-style="{display: 'flex'}" :name="menuIcon" :size="$u.addUnit(menuIconSize)" :color="index === current || highlightIndex == index ? activeColor : '#c0c4cc'"></u-icon>
+	<view class="u-drawdown">
+		<view
+			class="u-dropdown__menu"
+			:style="{
+				height: $u.addUnit(height)
+			}"
+			ref="u-dropdown__menu"
+		>
+			<view
+				class="u-dropdown__menu__item"
+				v-for="(item, index) in menuList"
+				:key="index"
+				@tap.stop="clickHandler(item, index)"
+			>
+				<view class="u-dropdown__menu__item__content">
+					<text
+						class="u-dropdown__menu__item__content__text"
+						:style="[index === current ? activeStyle : inactiveStyle]"
+					>{{item.title}}</text>
+					<view
+						class="u-dropdown__menu__item__content__arrow"
+						:class="[index === current && 'u-dropdown__menu__item__content__arrow--rotate']"
+					>
+						<u-icon
+							:name="menuIcon"
+							:size="$u.addUnit(menuIconSize)"
+						></u-icon>
 					</view>
 				</view>
 			</view>
 		</view>
-		<view class="u-dropdown__content" :style="[contentStyle, {
-			transition: `opacity ${duration / 1000}s linear`,
-			top: $u.addUnit(height),
-			height: contentHeight + 'px'
-		}]"
-		 @tap="maskClick" @touchmove.stop.prevent>
-			<view @tap.stop.prevent class="u-dropdown__content__popup" :style="[popupStyle]">
-				<slot></slot>
-			</view>
-			<view class="u-dropdown__content__mask"></view>
+		<view class="u-dropdown__content">
+			<slot />
 		</view>
 	</view>
 </template>
 
 <script>
+	import props from './props.js';
 	/**
-	 * dropdown 涓嬫媺鑿滃崟
-	 * @description 璇ョ粍浠朵竴鑸�敤浜庡悜涓嬪睍寮€鑿滃崟锛屽悓鏃跺彲鍒囨崲澶氫釜閫夐」鍗$殑鍦烘櫙
-	 * @tutorial http://uviewui.com/components/dropdown.html
-	 * @property {String} active-color 鏍囬�鍜岄€夐」鍗¢€変腑鐨勯�鑹诧紙榛樿�#2979ff锛�
-	 * @property {String} inactive-color 鏍囬�鍜岄€夐」鍗℃湭閫変腑鐨勯�鑹诧紙榛樿�#606266锛�
-	 * @property {Boolean} close-on-click-mask 鐐瑰嚮閬�僵鏄�惁鍏抽棴鑿滃崟锛堥粯璁�rue锛�
-	 * @property {Boolean} close-on-click-self 鐐瑰嚮褰撳墠婵€娲婚」鏍囬�鏄�惁鍏抽棴鑿滃崟锛堥粯璁�rue锛�
-	 * @property {String | Number} duration 閫夐」鍗″睍寮€鍜屾敹璧风殑杩囨浮鏃堕棿锛屽崟浣峬s锛堥粯璁�300锛�
-	 * @property {String | Number} height 鏍囬�鑿滃崟鐨勯珮搴︼紝鍗曚綅浠绘剰锛堥粯璁�80锛�
-	 * @property {String | Number} border-radius 鑿滃崟灞曞紑鍐呭�涓嬫柟鐨勫渾瑙掑€硷紝鍗曚綅浠绘剰锛堥粯璁�0锛�
-	 * @property {Boolean} border-bottom 鏍囬�鑿滃崟鏄�惁鏄剧ず涓嬭竟妗嗭紙榛樿�false锛�
-	 * @property {String | Number} title-size 鏍囬�鐨勫瓧浣撳ぇ灏忥紝鍗曚綅浠绘剰锛屾暟鍊奸粯璁や负rpx鍗曚綅锛堥粯璁�28锛�
-	 * @event {Function} open 涓嬫媺鑿滃崟琚�墦寮€鏃惰Е鍙�
-	 * @event {Function} close 涓嬫媺鑿滃崟琚�叧闂�椂瑙﹀彂
-	 * @example <u-dropdown></u-dropdown>
+	 * Dropdown  
+	 * @description 
+	 * @tutorial url
+	 * @property {String}
+	 * @event {Function}
+	 * @example
 	 */
 	export default {
 		name: 'u-dropdown',
-		props: {
-			// 鑿滃崟鏍囬�鍜岄€夐」鐨勬縺娲绘€侀�鑹�
-			activeColor: {
-				type: String,
-				default: '#2979ff'
-			},
-			// 鑿滃崟鏍囬�鍜岄€夐」鐨勬湭婵€娲绘€侀�鑹�
-			inactiveColor: {
-				type: String,
-				default: '#606266'
-			},
-			// 鐐瑰嚮閬�僵鏄�惁鍏抽棴鑿滃崟
-			closeOnClickMask: {
-				type: Boolean,
-				default: true
-			},
-			// 鐐瑰嚮褰撳墠婵€娲婚」鏍囬�鏄�惁鍏抽棴鑿滃崟
-			closeOnClickSelf: {
-				type: Boolean,
-				default: true
-			},
-			// 杩囨浮鏃堕棿
-			duration: {
-				type: [Number, String],
-				default: 300
-			},
-			// 鏍囬�鑿滃崟鐨勯珮搴︼紝鍗曚綅浠绘剰锛屾暟鍊奸粯璁や负rpx鍗曚綅
-			height: {
-				type: [Number, String],
-				default: 80
-			},
-			// 鏄�惁鏄剧ず涓嬭竟妗�
-			borderBottom: {
-				type: Boolean,
-				default: false
-			},
-			// 鏍囬�鐨勫瓧浣撳ぇ灏�
-			titleSize: {
-				type: [Number, String],
-				default: 28
-			},
-			// 涓嬫媺鍑烘潵鐨勫唴瀹归儴鍒嗙殑鍦嗚�鍊�
-			borderRadius: {
-				type: [Number, String],
-				default: 0
-			},
-			// 鑿滃崟鍙充晶鐨刬con鍥炬爣
-			menuIcon: {
-				type: String,
-				default: 'arrow-down'
-			},
-			// 鑿滃崟鍙充晶鍥炬爣鐨勫ぇ灏�
-			menuIconSize: {
-				type: [Number, String],
-				default: 26
-			}
-		},
+		mixins: [uni.$u.mixin, props],
 		data() {
 			return {
-				showDropdown: true, // 鏄�惁鎵撳紑涓嬫潵鑿滃崟,
-				menuList: [], // 鏄剧ず鐨勮彍鍗�
-				active: false, // 涓嬫媺鑿滃崟鐨勭姸鎬�
-				// 褰撳墠鏄��鍑犱釜鑿滃崟澶勪簬婵€娲荤姸鎬侊紝灏忕▼搴忎腑姝ゅ�涓嶈兘鍐欐垚false鎴栬€�""锛屽惁鍒欏悗缁�皢current璧嬪€间负0锛�
-				// 鏃犺兘鐨凾X娌℃湁浣跨敤===鑰屾槸浣跨敤==鍒ゆ柇锛屽�鑷寸▼搴忚�涓哄墠鍚庝簩鑰呮病鏈夊彉鍖栵紝浠庤€屼笉浼氳Е鍙戣�鍥炬洿鏂�
-				current: 99999,
-				// 澶栧眰鍐呭�鐨勬牱寮忥紝鍒濆�鏃跺�浜庡簳灞傦紝涓旈€忔槑
-				contentStyle: {
-					zIndex: -1,
-					opacity: 0
-				},
-				// 璁╂煇涓�彍鍗曚繚鎸侀珮浜�殑鐘舵€�
-				highlightIndex: 99999,
-				contentHeight: 0
+				// 菜单数组
+				menuList: [],
+				current: 0
 			}
 		},
 		computed: {
-			// 涓嬫媺鍑烘潵閮ㄥ垎鐨勬牱寮�
-			popupStyle() {
-				let style = {};
-				// 杩涜�Y杞翠綅绉伙紝灞曞紑鐘舵€佹椂锛屾仮澶嶅師浣嶃€傛敹榻愮姸鎬佹椂锛屽線涓婁綅绉�100%锛岃繘琛岄殣钘�
-				style.transform = `translateY(${this.active ? 0 : '-100%'})`
-				style['transition-duration'] = this.duration / 1000 + 's';
-				style.borderRadius = `0 0 ${this.$u.addUnit(this.borderRadius)} ${this.$u.addUnit(this.borderRadius)}`;
-				return style;
-			}
+		
 		},
 		created() {
-			// 寮曠敤鎵€鏈夊瓙缁勪欢(u-dropdown-item)鐨則his锛屼笉鑳藉湪data涓�0鏄庡彉閲忥紝鍚﹀垯鍦ㄥ井淇″皬绋嬪簭浼氶€犳垚寰�幆寮曠敤鑰屾姤閿�
+			// 引用所有子组件(u-dropdown-item)的this,不能在data中声明变量,否则在微信小程序会造成循环引用而报错
 			this.children = [];
 		},
-		mounted() {
-			this.getContentHeight();
-		},
 		methods: {
-			init() {
-				// 褰撴煇涓�瓙缁勪欢鍐呭�鍙樺寲鏃讹紝瑙﹀彂鐖剁粍浠剁殑init锛岀埗缁勪欢鍐嶈�姣忎竴涓�瓙缁勪欢閲嶆柊鍒濆�鍖栦竴閬�
-				// 浠ヤ繚璇佹暟鎹�殑姝g‘鎬�
-				this.menuList = [];
+			clickHandler(item, index) {
 				this.children.map(child => {
-					child.init();
+					if(child.title === item.title) {
+						// this.queryRect('u-dropdown__menu').then(size => {
+							child.$emit('click')
+							child.setContentAnimate(child.show ? 0 : 300)
+							child.show = !child.show
+						// })
+					} else {
+						child.show = false
+						child.setContentAnimate(0)
+					}
 				})
 			},
-			// 鐐瑰嚮鑿滃崟
-			menuClick(index) {
-				// 鍒ゆ柇鏄�惁琚��鐢�
-				if (this.menuList[index].disabled) return;
-				// 濡傛灉鐐瑰嚮鏃剁殑绱㈠紩鍜屽綋鍓嶆縺娲婚」绱㈠紩鐩稿悓锛屾剰鍛崇潃鐐瑰嚮浜嗘縺娲婚」锛岄渶瑕佹敹璧蜂笅鎷夎彍鍗�
-				if (index === this.current && this.closeOnClickSelf) {
-					this.close();
-					// 绛夊姩鐢荤粨鏉熷悗锛屽啀绉婚櫎涓嬫媺鑿滃崟涓�殑鍐呭�锛屽惁鍒欑洿鎺ョЩ闄わ紝涔熷氨娌℃湁涓嬫媺鑿滃崟鏀惰捣鐨勬晥鏋滀簡
-					setTimeout(() => {
-						this.children[index].active = false;
-					}, this.duration)
-					return;
-				}
-				this.open(index);
-			},
-			// 鎵撳紑涓嬫媺鑿滃崟
-			open(index) {
-				// 閲嶇疆楂樹寒绱㈠紩锛屽惁鍒欎細閫犳垚澶氫釜鑿滃崟鍚屾椂楂樹寒
-				// this.highlightIndex = 9999;
-				// 灞曞紑鏃讹紝璁剧疆涓嬫媺鍐呭�鐨勬牱寮�
-				this.contentStyle = {
-					zIndex: 11,
-				}
-				// 鏍囪�灞曞紑鐘舵€佷互鍙婂綋鍓嶅睍寮€椤圭殑绱㈠紩
-				this.active = true;
-				this.current = index;
-				// 鍘嗛亶鎵€鏈夌殑瀛愬厓绱狅紝灏嗙储寮曞尮閰嶇殑椤规爣璁颁负婵€娲荤姸鎬侊紝鍥犱负瀛愬厓绱犳槸閫氳繃v-if鎺у埗鍒囨崲鐨�
-				// 涔嬫墍浠ヤ笉鏄�洜display: none锛屾槸鍥犱负nvue娌℃湁display杩欎釜灞炴€�
-				this.children.map((val, idx) => {
-					val.active = index == idx ? true : false;
+			// 获取标签的尺寸位置
+			queryRect(el) {
+				// #ifndef APP-NVUE
+				// $uGetRect为uView自带的节点查询简化方法,详见文档介绍:https://www.uviewui.com/js/getRect.html
+				// 组件内部一般用this.$uGetRect,对外的为this.$u.getRect,二者功能一致,名称不同
+				return new Promise(resolve => {
+					this.$uGetRect(`.${el}`).then(size => {
+						resolve(size)
+					})
 				})
-				this.$emit('open', this.current);
-			},
-			// 璁剧疆涓嬫媺鑿滃崟澶勪簬鏀惰捣鐘舵€�
-			close() {
-				this.$emit('close', this.current);
-				// 璁剧疆涓烘敹璧风姸鎬侊紝鍚屾椂current褰掍綅锛岃�缃�负绌哄瓧绗︿覆
-				this.active = false;
-				this.current = 99999;
-				// 涓嬫媺鍐呭�鐨勬牱寮忚繘琛岃皟鏁达紝涓嶉€忔槑搴﹁�缃�负0
-				this.contentStyle = {
-					zIndex: -1,
-					opacity: 0
-				}
-			},
-			// 鐐瑰嚮閬�僵
-			maskClick() {
-				// 濡傛灉涓嶅厑璁哥偣鍑婚伄缃╋紝鐩存帴杩斿洖
-				if (!this.closeOnClickMask) return;
-				this.close();
-			},
-			// 澶栭儴鎵嬪姩璁剧疆鏌愪釜鑿滃崟楂樹寒
-			highlight(index = undefined) {
-				this.highlightIndex = index !== undefined ? index : 99999;
-			},
-			// 鑾峰彇涓嬫媺鑿滃崟鍐呭�鐨勯珮搴�
-			getContentHeight() {
-				// 杩欓噷鐨勫師鐞嗕负锛屽洜涓篸ropdown缁勪欢鏄�浉瀵瑰畾浣嶇殑锛屽畠鐨勪笅鎷夊嚭鏉ョ殑鍐呭�锛屽繀椤荤粰瀹氫竴涓�珮搴�
-				// 鎵嶈兘璁╅伄缃╁崰婊¤彍鍗曚竴涓嬶紝鐩村埌灞忓箷搴曢儴鐨勯珮搴�
-				// this.$u.sys()涓簎View灏佽�鐨勮幏鍙栬�澶囦俊鎭�殑鏂规硶
-				let windowHeight = this.$u.sys().windowHeight;
-				this.$uGetRect('.u-dropdown__menu').then(res => {
-					// 杩欓噷鑾峰彇鐨勬槸dropdown鐨勫昂瀵革紝鍦℉5涓婏紝uniapp鑾峰彇灏哄�鏄�湁bug鐨�(浠ュ墠鎻愬嚭淇��杩囷紝鍚庢潵鍙堝嚭鐜颁簡姝�ug锛岀洰鍓峢x2.8.11鐗堟湰)
-					// H5绔痓ug琛ㄧ幇涓哄厓绱犲昂瀵哥殑top鍊间负瀵艰埅鏍忓簳閮ㄥ埌鍒板厓绱犵殑涓婅竟娌跨殑璺濈�锛屼絾鏄�厓绱犵殑bottom鍊肩‘鏄��鑸�爮椤堕儴鍒板厓绱犲簳閮ㄧ殑璺濈�
-					// 浜岃€呮槸浜掔浉鐭涚浘鐨勶紝鏈�川鍘熷洜鏄疕5绔��鑸�爮闈炲師鐢燂紝uni鐨勫紑鍙戣€呭ぇ鎰忛€犳垚
-					// 杩欓噷鍙栬彍鍗曟爮鐨刡otton鍊煎悎鐞嗙殑锛屼笉鑳界敤res.top锛屽惁鍒欓〉闈�細閫犳垚婊氬姩
-					this.contentHeight = windowHeight - res.bottom;
+				// #endif
+			
+				// #ifdef APP-NVUE 
+				// nvue下,使用dom模块查询元素高度
+				// 返回一个promise,让调用此方法的主体能使用then回调
+				return new Promise(resolve => {
+					dom.getComponentRect(this.$refs[el], res => {
+						resolve(res.size)
+					})
 				})
-			}
-		}
+				// #endif
+			},
+		},
 	}
 </script>
 
-<style scoped lang="scss">
-	@import "../../libs/css/style.components.scss";
+<style lang="scss">
+	@import '../../libs/css/components.scss';
 
 	.u-dropdown {
-		flex: 1;
-		width: 100%;
-		position: relative;
 
 		&__menu {
-			@include vue-flex;
-			position: relative;
-			z-index: 11;
-			height: 80rpx;
+			@include flex;
 
 			&__item {
 				flex: 1;
-				@include vue-flex;
+				@include flex;
 				justify-content: center;
-				align-items: center;
-
-				&__text {
-					font-size: 28rpx;
-					color: $u-content-color;
-				}
 
-				&__arrow {
-					margin-left: 6rpx;
-					transition: transform .3s;
+				&__content {
+					@include flex;
 					align-items: center;
-					@include vue-flex;
-
-					&--rotate {
-						transform: rotate(180deg);
-					}
 				}
 			}
 		}
-
-		&__content {
-			position: absolute;
-			z-index: 8;
-			width: 100%;
-			left: 0px;
-			bottom: 0;
-			overflow: hidden;
-			
-
-			&__mask {
-				position: absolute;
-				z-index: 9;
-				background: rgba(0, 0, 0, .3);
-				width: 100%;
-				left: 0;
-				top: 0;
-				bottom: 0;
-			}
-
-			&__popup {
-				position: relative;
-				z-index: 10;
-				transition: all 0.3s;
-				transform: translate3D(0, -100%, 0);
-				overflow: hidden;
-			}
-		}
-
 	}
 </style>

+ 59 - 0
uview-ui/components/u-empty/props.js

@@ -0,0 +1,59 @@
+export default {
+    props: {
+        // 内置图标名称,或图片路径,建议绝对路径
+        icon: {
+            type: String,
+            default: uni.$u.props.empty.icon
+        },
+        // 提示文字
+        text: {
+            type: String,
+            default: uni.$u.props.empty.text
+        },
+        // 文字颜色
+        textColor: {
+            type: String,
+            default: uni.$u.props.empty.textColor
+        },
+        // 文字大小
+        textSize: {
+            type: [String, Number],
+            default: uni.$u.props.empty.textSize
+        },
+        // 图标的颜色
+        iconColor: {
+            type: String,
+            default: uni.$u.props.empty.iconColor
+        },
+        // 图标的大小
+        iconSize: {
+            type: [String, Number],
+            default: uni.$u.props.empty.iconSize
+        },
+        // 选择预置的图标类型
+        mode: {
+            type: String,
+            default: uni.$u.props.empty.mode
+        },
+        //  图标宽度,单位px
+        width: {
+            type: [String, Number],
+            default: uni.$u.props.empty.width
+        },
+        // 图标高度,单位px
+        height: {
+            type: [String, Number],
+            default: uni.$u.props.empty.height
+        },
+        // 是否显示组件
+        show: {
+            type: Boolean,
+            default: uni.$u.props.empty.show
+        },
+        // 组件距离上一个元素之间的距离,默认px单位
+        marginTop: {
+            type: [String, Number],
+            default: uni.$u.props.empty.marginTop
+        }
+    }
+}

+ 81 - 146
uview-ui/components/u-empty/u-empty.vue

@@ -1,107 +1,62 @@
 <template>
-	<view class="u-empty" v-if="show" :style="{
-		marginTop: marginTop + 'rpx'
-	}">
+	<view
+	    class="u-empty"
+	    :style="[emptyStyle]"
+	    v-if="show"
+	>
 		<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"
+		    v-if="!isSrc"
+		    :name="mode === 'message' ? 'chat' : `empty-${mode}`"
+		    :size="iconSize"
+		    :color="iconColor"
+		    margin-top="14"
 		></u-icon>
-		<view class="u-slot-wrap">
-			<slot name="bottom"></slot>
+		<image
+		    v-else
+		    :style="{
+				width: $u.addUnit(width),
+				height: $u.addUnit(height),
+			}"
+		    :src="icon"
+		    mode="widthFix"
+		></image>
+		<text
+		    class="u-empty__text"
+		    :style="[textStyle]"
+		>{{text ? text : icons[mode]}}</text>
+		<view class="u-empty__wrap" v-if="$slots.default || $slots.$default">
+			<slot />
 		</view>
 	</view>
 </template>
 
 <script>
+	import props from './props.js';
+
 	/**
 	 * 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)
+	 * @property {String}			icon		内置图标名称,或图片路径,建议绝对路径
+	 * @property {String}			text		提示文字
+	 * @property {String}			textColor	文字颜色 (默认 '#c0c4cc' )
+	 * @property {String | Number}	textSize	文字大小 (默认 14 )
+	 * @property {String}			iconColor	图标的颜色 (默认 '#c0c4cc' )
+	 * @property {String | Number}	iconSize	图标的大小 (默认 90 )
+	 * @property {String}			mode		选择预置的图标类型 (默认 'data' )
+	 * @property {String | Number}	width		图标宽度,单位px (默认 160 )
+	 * @property {String | Number}	height		图标高度,单位px (默认 160 )
+	 * @property {Boolean}			show		是否显示组件 (默认 true )
+	 * @property {String | Number}	marginTop	组件距离上一个元素之间的距离,默认px单位 (默认 0 )
+	 * @property {Object}			customStyle	定义需要用到的外部样式
+	 * 
 	 * @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 {}
-				}
-			}
-		},
+		mixins: [uni.$u.mpMixin, uni.$u.mixin, props],
 		data() {
 			return {
 				icons: {
@@ -118,76 +73,56 @@
 					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: '数据为空'
-				// }],
-
+					data: '数据为空',
+					comment: '暂无评论',
+				}
+			}
+		},
+		computed: {
+			// 组件样式
+			emptyStyle() {
+				const style = {}
+				style.marginTop = uni.$u.addUnit(this.marginTop)
+				// 合并customStyle样式,此参数通过mixin中的props传递
+				return uni.$u.deepMerge(uni.$u.addStyle(this.customStyle), style)
+			},
+			// 文本样式
+			textStyle() {
+				const style = {}
+				style.color = this.textColor
+				style.fontSize = uni.$u.addUnit(this.textSize)
+				return style
+			},
+			// 判断icon是否图片路径
+			isSrc() {
+				return this.icon.indexOf('/') >= 0
 			}
 		}
 	}
 </script>
 
-<style scoped lang="scss">
-	@import "../../libs/css/style.components.scss";
+<style lang="scss" scoped>
+	@import '../../libs/css/components.scss';
+	$u-empty-text-margin-top:20rpx !default;
+	$u-empty-slot-margin-top:20rpx !default;
 
 	.u-empty {
-		@include vue-flex;
+		@include 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;
+		&__text {
+			@include flex;
+			justify-content: center;
+			align-items: center;
+			margin-top: $u-empty-text-margin-top;
+		}
 	}
+		.u-slot-wrap {
+			@include flex;
+			justify-content: center;
+			align-items: center;
+			margin-top:$u-empty-slot-margin-top;
+		}
 </style>

+ 0 - 384
uview-ui/components/u-field/u-field.vue

@@ -1,384 +0,0 @@
-<template>
-	<view class="u-field" :class="{'u-border-top': borderTop, 'u-border-bottom': borderBottom }">
-		<view class="u-field-inner" :class="[type == 'textarea' ? 'u-textarea-inner' : '', 'u-label-postion-' + labelPosition]">
-			<view class="u-label" :class="[required ? 'u-required' : '']" :style="{
-				justifyContent: justifyContent, 
-				flex: labelPosition == 'left' ? `0 0 ${labelWidth}rpx` : '1'
-			}">
-				<view class="u-icon-wrap" v-if="icon">
-					<u-icon size="32" :custom-style="iconStyle" :name="icon" :color="iconColor" class="u-icon"></u-icon>
-				</view>
-				<slot name="icon"></slot>
-				<text class="u-label-text" :class="[this.$slots.icon || icon ? 'u-label-left-gap' : '']">{{ label }}</text>
-			</view>
-			<view class="fild-body">
-				<view class="u-flex-1 u-flex" :style="[inputWrapStyle]">
-					<textarea v-if="type == 'textarea'" class="u-flex-1 u-textarea-class" :style="[fieldStyle]" :value="value"
-					 :placeholder="placeholder" :placeholderStyle="placeholderStyle" :disabled="disabled" :maxlength="inputMaxlength"
-					 :focus="focus" :autoHeight="autoHeight" :fixed="fixed" @input="onInput" @blur="onBlur" @focus="onFocus" @confirm="onConfirm"
-					 @tap="fieldClick" />
-					<input
-						v-else
-						:style="[fieldStyle]"
-						:type="type"
-						class="u-flex-1 u-field__input-wrap"
-						:value="value"
-						:password="password || this.type === 'password'"
-						:placeholder="placeholder"
-						:placeholderStyle="placeholderStyle"
-						:disabled="disabled"
-						:maxlength="inputMaxlength"
-						:focus="focus"
-						:confirmType="confirmType"
-						@focus="onFocus"
-						@blur="onBlur"
-						@input="onInput"
-						@confirm="onConfirm"
-						@tap="fieldClick"
-					/>
-				</view>
-				<u-icon :size="clearSize" v-if="clearable && value != '' && focused" name="close-circle-fill" color="#c0c4cc" class="u-clear-icon" @click="onClear"/>
-				<view class="u-button-wrap"><slot name="right" /></view>
-				<u-icon v-if="rightIcon" @click="rightIconClick" :name="rightIcon" color="#c0c4cc" :style="[rightIconStyle]" size="26" class="u-arror-right" />
-			</view>
-		</view>
-		<view v-if="errorMessage !== false && errorMessage != ''" class="u-error-message" :style="{
-			paddingLeft: labelWidth + 'rpx'
-		}">{{ errorMessage }}</view>
-	</view>
-</template>
-
-<script>
-/**
- * field 输入框
- * @description 借助此组件,可以实现表单的输入, 有"text"和"textarea"类型的,此外,借助uView的picker和actionSheet组件可以快速实现上拉菜单,时间,地区选择等, 为表单解决方案的利器。
- * @tutorial https://www.uviewui.com/components/field.html
- * @property {String} type 输入框的类型(默认text)
- * @property {String} icon label左边的图标,限uView的图标名称
- * @property {Object} icon-style 左边图标的样式,对象形式
- * @property {Boolean} right-icon 输入框右边的图标名称,限uView的图标名称(默认false)
- * @property {Boolean} required 是否必填,左边您显示红色"*"号(默认false)
- * @property {String} label 输入框左边的文字提示
- * @property {Boolean} password 是否密码输入方式(用点替换文字),type为text时有效(默认false)
- * @property {Boolean} clearable 是否显示右侧清空内容的图标控件(输入框有内容,且获得焦点时才显示),点击可清空输入框内容(默认true)
- * @property {Number String} label-width label的宽度,单位rpx(默认130)
- * @property {String} label-align label的文字对齐方式(默认left)
- * @property {Object} field-style 自定义输入框的样式,对象形式
- * @property {Number | String} clear-size 清除图标的大小,单位rpx(默认30)
- * @property {String} input-align 输入框内容对齐方式(默认left)
- * @property {Boolean} border-bottom 是否显示field的下边框(默认true)
- * @property {Boolean} border-top 是否显示field的上边框(默认false)
- * @property {String} icon-color 左边通过icon配置的图标的颜色(默认#606266)
- * @property {Boolean} auto-height 是否自动增高输入区域,type为textarea时有效(默认true)
- * @property {String Boolean} error-message 显示的错误提示内容,如果为空字符串或者false,则不显示错误信息
- * @property {String} placeholder 输入框的提示文字
- * @property {String} placeholder-style placeholder的样式(内联样式,字符串),如"color: #ddd"
- * @property {Boolean} focus 是否自动获得焦点(默认false)
- * @property {Boolean} fixed 如果type为textarea,且在一个"position:fixed"的区域,需要指明为true(默认false)
- * @property {Boolean} disabled 是否不可输入(默认false)
- * @property {Number String} maxlength 最大输入长度,设置为 -1 的时候不限制最大长度(默认140)
- * @property {String} confirm-type 设置键盘右下角按钮的文字,仅在type="text"时生效(默认done)
- * @event {Function} input 输入框内容发生变化时触发
- * @event {Function} focus 输入框获得焦点时触发
- * @event {Function} blur 输入框失去焦点时触发
- * @event {Function} confirm 点击完成按钮时触发
- * @event {Function} right-icon-click 通过right-icon生成的图标被点击时触发
- * @event {Function} click 输入框被点击或者通过right-icon生成的图标被点击时触发,这样设计是考虑到传递右边的图标,一般都为需要弹出"picker"等操作时的场景,点击倒三角图标,理应发出此事件,见上方说明
- * @example <u-field v-model="mobile" label="手机号" required :error-message="errorMessage"></u-field>
- */
-export default {
-	name:"u-field",
-	props: {
-		icon: String,
-		rightIcon: String,
-		// arrowDirection: {
-		// 	type: String,
-		// 	default: 'right'
-		// },
-		required: Boolean,
-		label: String,
-		password: Boolean,
-		clearable: {
-			type: Boolean,
-			default: true
-		},
-		// 左边标题的宽度单位rpx
-		labelWidth: {
-			type: [Number, String],
-			default: 130
-		},
-		// 对齐方式,left|center|right
-		labelAlign: {
-			type: String,
-			default: 'left'
-		},
-		inputAlign: {
-			type: String,
-			default: 'left'
-		},
-		iconColor: {
-			type: String,
-			default: '#606266'
-		},
-		autoHeight: {
-			type: Boolean,
-			default: true
-		},
-		errorMessage: {
-			type: [String, Boolean],
-			default: ''
-		},
-		placeholder: String,
-		placeholderStyle: String,
-		focus: Boolean,
-		fixed: Boolean,
-		value: [Number, String],
-		type: {
-			type: String,
-			default: 'text'
-		},
-		disabled: {
-			type: Boolean,
-			default: false
-		},
-		maxlength: {
-			type: [Number, String],
-			default: 140
-		},
-		confirmType: {
-			type: String,
-			default: 'done'
-		},
-		// lable的位置,可选为 left-左边,top-上边
-		labelPosition: {
-			type: String,
-			default: 'left'
-		},
-		// 输入框的自定义样式
-		fieldStyle: {
-			type: Object,
-			default() {
-				return {}
-			}
-		},
-		// 清除按钮的大小
-		clearSize: {
-			type: [Number, String],
-			default: 30
-		},
-		// lable左边的图标样式,对象形式
-		iconStyle: {
-			type: Object,
-			default() {
-				return {}
-			}
-		},
-		// 是否显示上边框
-		borderTop: {
-			type: Boolean,
-			default: false
-		},
-		// 是否显示下边框
-		borderBottom: {
-			type: Boolean,
-			default: true
-		},
-		// 是否自动去除两端的空格
-		trim: {
-			type: Boolean,
-			default: true
-		}
-	},
-	data() {
-		return {
-			focused: false,
-			itemIndex: 0,
-		};
-	},
-	computed: {
-		inputWrapStyle() {
-			let style = {};
-			style.textAlign = this.inputAlign;
-			// 判断lable的位置,如果是left的话,让input左边两边有间隙
-			if(this.labelPosition == 'left') {
-				style.margin = `0 8rpx`;
-			} else {
-				// 如果lable是top的,input的左边就没必要有间隙了
-				style.marginRight = `8rpx`;
-			}
-			return style;
-		},
-		rightIconStyle() {
-			let style = {};
-			if (this.arrowDirection == 'top') style.transform = 'roate(-90deg)';
-			if (this.arrowDirection == 'bottom') style.transform = 'roate(90deg)';
-			else style.transform = 'roate(0deg)';
-			return style;
-		},
-		labelStyle() {
-			let style = {};
-			if(this.labelAlign == 'left') style.justifyContent = 'flext-start';
-			if(this.labelAlign == 'center') style.justifyContent = 'center';
-			if(this.labelAlign == 'right') style.justifyContent = 'flext-end';
-			return style;
-		},
-		// uni不支持在computed中写style.justifyContent = 'center'的形式,故用此方法
-		justifyContent() {
-			if(this.labelAlign == 'left') return 'flex-start';
-			if(this.labelAlign == 'center') return 'center';
-			if(this.labelAlign == 'right') return 'flex-end';
-		},
-		// 因为uniapp的input组件的maxlength组件必须要数值,这里转为数值,给用户可以传入字符串数值
-		inputMaxlength() {
-			return Number(this.maxlength)
-		},
-		// label的位置
-		fieldInnerStyle() {
-			let style = {};
-			if(this.labelPosition == 'left') {
-				style.flexDirection = 'row';
-			} else {
-				style.flexDirection = 'column';
-			}
-			
-			return style;
-		}
-	},
-	methods: {
-		onInput(event) {
-			let value = event.detail.value;
-			// 判断是否去除空格
-			if(this.trim) value = this.$u.trim(value);
-			this.$emit('input', value);
-		},
-		onFocus(event) {
-			this.focused = true;
-			this.$emit('focus', event);
-		},
-		onBlur(event) {
-			// 最开始使用的是监听图标@touchstart事件,自从hx2.8.4后,此方法在微信小程序出错
-			// 这里改为监听点击事件,手点击清除图标时,同时也发生了@blur事件,导致图标消失而无法点击,这里做一个延时
-			setTimeout(() => {
-				this.focused = false;
-			}, 100)
-			this.$emit('blur', event);
-		},
-		onConfirm(e) {
-			this.$emit('confirm', e.detail.value);
-		},
-		onClear(event) {
-			this.$emit('input', '');
-		},
-		rightIconClick() {
-			this.$emit('right-icon-click');
-			this.$emit('click');
-		},
-		fieldClick() {
-			this.$emit('click');
-		}
-	}
-};
-</script>
-
-<style lang="scss" scoped>
-@import "../../libs/css/style.components.scss";
-	
-.u-field {
-	font-size: 28rpx;
-	padding: 20rpx 28rpx;
-	text-align: left;
-	position: relative;
-	color: $u-main-color;
-}
-
-.u-field-inner {
-	@include vue-flex;
-	align-items: center;
-}
-
-.u-textarea-inner {
-	align-items: flex-start;
-}
-
-.u-textarea-class {
-	min-height: 96rpx;
-	width: auto;
-	font-size: 28rpx;
-}
-
-.fild-body {
-	@include vue-flex;
-	flex: 1;
-	align-items: center;
-}
-
-.u-arror-right {
-	margin-left: 8rpx;
-}
-
-.u-label-text {
-	/* #ifndef APP-NVUE */
-	display: inline-flex;		
-	/* #endif */
-}
-
-.u-label-left-gap {
-	margin-left: 6rpx;
-}
-
-.u-label-postion-top {
-	flex-direction: column;
-	align-items: flex-start;
-}
-
-.u-label {
-	width: 130rpx;
-	flex: 1 1 130rpx;
-	text-align: left;
-	position: relative;
-	@include vue-flex;
-	align-items: center;
-}
-
-.u-required::before {
-	content: '*';
-	position: absolute;
-	left: -16rpx;
-	font-size: 14px;
-	color: $u-type-error;
-	height: 9px;
-	line-height: 1;
-}
-
-.u-field__input-wrap {
-	position: relative;
-	overflow: hidden;
-	font-size: 28rpx;
-	height: 48rpx;
-	flex: 1;
-	width: auto;
-}
-
-.u-clear-icon {
-	@include vue-flex;
-	align-items: center;
-}
-
-.u-error-message {
-	color: $u-type-error;
-	font-size: 26rpx;
-	text-align: left;
-}
-
-.placeholder-style {
-	color: rgb(150, 151, 153);
-}
-
-.u-input-class {
-	font-size: 28rpx;
-}
-
-.u-button-wrap {
-	margin-left: 8rpx;
-}
-</style>

+ 43 - 0
uview-ui/components/u-form-item/props.js

@@ -0,0 +1,43 @@
+export default {
+    props: {
+        // input的label提示语
+        label: {
+            type: String,
+            default: uni.$u.props.formItem.label
+        },
+        // 绑定的值
+        prop: {
+            type: String,
+            default: uni.$u.props.formItem.prop
+        },
+        // 是否显示表单域的下划线边框
+        borderBottom: {
+            type: [String, Boolean],
+            default: uni.$u.props.formItem.borderBottom
+        },
+        // label的宽度,单位px
+        labelWidth: {
+            type: [String, Number],
+            default: uni.$u.props.formItem.labelWidth
+        },
+        // 右侧图标
+        rightIcon: {
+            type: String,
+            default: uni.$u.props.formItem.rightIcon
+        },
+        // 左侧图标
+        leftIcon: {
+            type: String,
+            default: uni.$u.props.formItem.leftIcon
+        },
+        // 是否显示左边的必填星号,只作显示用,具体校验必填的逻辑,请在rules中配置
+        required: {
+            type: Boolean,
+            default: uni.$u.props.formItem.required
+        },
+        leftIconStyle: {
+            type: [String, Object],
+            default: uni.$u.props.formItem.leftIconStyle,
+        }
+    }
+}

+ 172 - 368
uview-ui/components/u-form-item/u-form-item.vue

@@ -1,431 +1,235 @@
 <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'
-		}">
+	<view class="u-form-item">
+		<view
+			class="u-form-item__body"
+			@tap="clickHandler"
+			:style="[$u.addStyle(customStyle), {
+				flexDirection: parentData.labelPosition === '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}}
+			<slot name="label">
+				<!-- {{required}} -->
+				<view
+					class="u-form-item__body__left"
+					v-if="required || leftIcon || label"
+					:style="{
+						width: $u.addUnit(labelWidth || parentData.labelWidth),
+						marginBottom: parentData.labelPosition === 'left' ? 0 : '5px',
+					}"
+				>
+					<!-- 为了块对齐 -->
+					<view class="u-form-item__body__left__content">
+						<!-- nvue不支持伪元素before -->
+						<text
+							v-if="required"
+							class="u-form-item__body__left__content__required"
+						>*</text>
+						<view
+							class="u-form-item__body__left__content__icon"
+							v-if="leftIcon"
+						>
+							<u-icon
+								:name="leftIcon"
+								:custom-style="leftIconStyle"
+							></u-icon>
+						</view>
+						<text
+							class="u-form-item__body__left__content__label"
+							:style="[parentData.labelStyle, {
+								justifyContent: parentData.labelAlign === 'left' ? 'flex-start' : parentData.labelAlign === 'center' ? 'center' : 'flex-end'
+							}]"
+						>{{ label }}</text>
 					</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 class="u-form-item__body__right">
+				<view class="u-form-item__body__right__content">
+					<view class="u-form-item__body__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>
+					<view
+						class="item__body__right__content__icon"
+						v-if="$slots.right"
+					>
 						<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>
+		<slot name="error">
+			<text
+				v-if="!!message && parentData.errorType === 'message'"
+				class="u-form-item__body__right__message"
+				:style="{
+					marginLeft:  $u.addUnit(parentData.labelPosition === 'top' ? 0 : (labelWidth || parentData.labelWidth))
+				}"
+			>{{ message }}</text>
+		</slot>
+		<u-line
+			v-if="borderBottom"
+			:color="message && parentData.errorType === 'border-bottom' ? $u.color.error : propsLine.color"
+			:customStyle="`margin-top: ${message && parentData.errorType === 'message' ? '5px' : 0}`"
+		></u-line>
 	</view>
 </template>
 
 <script>
-	import Emitter from '../../libs/util/emitter.js';
-	import schema from '../../libs/util/async-validator';
-	// 去除警告信息
-	schema.warning = function() {};
-
+	import props from './props.js';
 	/**
-	 * form-item 表单item
+	 * Form 表单
 	 * @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>
+	 * @tutorial https://www.uviewui.com/components/form.html
+	 * @property {String}			label			input的label提示语
+	 * @property {String}			prop			绑定的值
+	 * @property {String | Boolean}	borderBottom	是否显示表单域的下划线边框
+	 * @property {String | Number}	labelWidth		label的宽度,单位px
+	 * @property {String}			rightIcon		右侧图标
+	 * @property {String}			leftIcon		左侧图标
+	 * @property {String | Object} leftIconStyle 左侧图标的样式
+	 * @property {Boolean}			required		是否显示左边的必填星号,只作显示用,具体校验必填的逻辑,请在rules中配置 (默认 false )
+	 *
+	 * @example <u-form-item label="姓名" prop="userInfo.name" borderBottom ref="item1"></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
-			}
-		},
+		mixins: [uni.$u.mpMixin, uni.$u.mixin, props],
 		data() {
 			return {
-				initialValue: '', // 存储的默认值
-				// isRequired: false, // 是否必填,由于人性化考虑,必填"*"号通过props的required配置,不再通过rules的规则自动生成
-				validateState: '', // 是否校验成功
-				validateMessage: '', // 校验失败的提示语
-				// 有错误时的提示方式,message-提示信息,border-如果input设置了边框,变成呈红色,
-				errorType: ['message'],
-				fieldValue: '', // 获取当前子组件input的输入的值
-				// 父组件的参数,在computed计算中,无法得知this.parent发生变化,故将父组件的参数值,放到data中
+				// 错误提示语
+				message: '',
 				parentData: {
-					borderBottom: true,
-					labelWidth: 90,
+					// 提示文本的位置
 					labelPosition: 'left',
-					labelStyle: {},
+					// 提示文本对齐方式
 					labelAlign: 'left',
+					// 提示文本的样式
+					labelStyle: {},
+					// 提示文本的宽度
+					labelWidth: 45,
+					// 错误提示方式
+					errorType: 'message'
 				}
-			};
-		},
-		watch: {
-			validateState(val) {
-				this.broadcastInputError();
-			},
-			// 监听u-form组件的errorType的变化
-			"uForm.errorType"(val) {
-				this.errorType = val;
-				this.broadcastInputError();
-			},
+			}
 		},
+		// 组件创建完成时,将当前实例保存到u-form中
 		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;
+			propsLine() {
+				return uni.$u.props.line
 			}
 		},
+		mounted() {
+			this.init()
+		},
 		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');
+			init() {
+				// 父组件的实例
+				this.updateParentData()
+				if (!this.parent) {
+					uni.$u.error('u-form-item需要结合u-form组件使用')
+				}
 			},
-
-			// change事件进行表单校验
-			onFieldChange() {
-				this.validation('change');
+			// 获取父组件的参数
+			updateParentData() {
+				// 此方法写在mixin中
+				this.getParentData('u-form');
 			},
-
-			// 过滤出符合要求的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的校验结果
+			clearValidate() {
+				this.message = null
 			},
-
-			// 清空当前的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);
-				})
+				// 找到原始值
+				const value = uni.$u.getProperty(this.parent.originalModel, this.prop)
+				// 将u-form的model的prop属性链还原原始值
+				uni.$u.setProperty(this.parent.model, this.prop, value)
+				// 移除校验结果
+				this.message = null
+			},
+			// 点击组件
+			clickHandler() {
+				this.$emit('click')
 			}
 		},
-	};
+	}
 </script>
 
 <style lang="scss" scoped>
-	@import "../../libs/css/style.components.scss";
+	@import "../../libs/css/components.scss";
 
 	.u-form-item {
-		@include vue-flex;
-		// align-items: flex-start;
-		padding: 20rpx 0;
-		font-size: 28rpx;
+		@include flex(column);
+		font-size: 14px;
 		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;
-		}
+			@include flex;
+			padding: 10px 0;
 
-		&--left {
-			@include vue-flex;
-			align-items: center;
-
-			&__content {
-				position: relative;
-				@include vue-flex;
+			&__left {
+				@include 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;
+				&__content {
+					position: relative;
+					@include flex;
 					align-items: center;
+					padding-right: 10rpx;
 					flex: 1;
+
+					&__icon {
+						margin-right: 8rpx;
+					}
+
+					&__required {
+						position: absolute;
+						left: -9px;
+						color: $u-error;
+						line-height: 20px;
+						font-size: 20px;
+						top: 3px;
+					}
+
+					&__label {
+						@include flex;
+						align-items: center;
+						flex: 1;
+						color: $u-main-color;
+						font-size: 15px;
+					}
 				}
 			}
-		}
 
-		&--right {
-			flex: 1;
-
-			&__content {
-				@include vue-flex;
-				align-items: center;
+			&__right {
 				flex: 1;
 
-				&__slot {
-					flex: 1;
-					/* #ifndef MP */
-					@include vue-flex;
+				&__content {
+					@include flex;
 					align-items: center;
-					/* #endif */
+					flex: 1;
+
+					&__slot {
+						flex: 1;
+						/* #ifndef MP */
+						@include flex;
+						align-items: center;
+						/* #endif */
+					}
+
+					&__icon {
+						margin-left: 10rpx;
+						color: $u-light-color;
+						font-size: 30rpx;
+					}
 				}
 
-				&__icon {
-					margin-left: 10rpx;
-					color: $u-light-color;
-					font-size: 30rpx;
+				&__message {
+					font-size: 12px;
+					line-height: 12px;
+					color: $u-error;
 				}
 			}
 		}
-
-		&__message {
-			font-size: 24rpx;
-			line-height: 24rpx;
-			color: $u-type-error;
-			margin-top: 12rpx;
-		}
 	}
 </style>

+ 45 - 0
uview-ui/components/u-form/props.js

@@ -0,0 +1,45 @@
+export default {
+    props: {
+        // 当前form的需要验证字段的集合
+        model: {
+            type: Object,
+            default: uni.$u.props.form.model
+        },
+        // 验证规则
+        rules: {
+            type: [Object, Function, Array],
+            default: uni.$u.props.form.rules
+        },
+        // 有错误时的提示方式,message-提示信息,toast-进行toast提示
+        // border-bottom-下边框呈现红色,none-无提示
+        errorType: {
+            type: String,
+            default: uni.$u.props.form.errorType
+        },
+        // 是否显示表单域的下划线边框
+        borderBottom: {
+            type: Boolean,
+            default: uni.$u.props.form.borderBottom
+        },
+        // label的位置,left-左边,top-上边
+        labelPosition: {
+            type: String,
+            default: uni.$u.props.form.labelPosition
+        },
+        // label的宽度,单位px
+        labelWidth: {
+            type: [String, Number],
+            default: uni.$u.props.form.labelWidth
+        },
+        // lable字体的对齐方式
+        labelAlign: {
+            type: String,
+            default: uni.$u.props.form.labelAlign
+        },
+        // lable的样式,对象形式
+        labelStyle: {
+            type: Object,
+            default: uni.$u.props.form.labelStyle
+        }
+    }
+}

+ 195 - 115
uview-ui/components/u-form/u-form.vue

@@ -1,134 +1,214 @@
 <template>
-	<view class="u-form"><slot /></view>
+	<view class="u-form">
+		<slot />
+	</view>
 </template>
 
 <script>
+	import props from "./props.js";
+	import Schema from "../../libs/util/async-validator";
+	// 去除警告信息
+	Schema.warning = function() {};
 	/**
-	 * form 表单
+	 * 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>
+	 * @tutorial https://www.uviewui.com/components/form.html
+	 * @property {Object}						model			当前form的需要验证字段的集合
+	 * @property {Object | Function | Array}	rules			验证规则
+	 * @property {String}						errorType		错误的提示方式,见上方说明 ( 默认 message )
+	 * @property {Boolean}						borderBottom	是否显示表单域的下划线边框   ( 默认 true 
+	 * @property {String}						labelPosition	表单域提示文字的位置,left-左侧,top-上方 ( 默认 'left' )
+	 * @property {String | Number}				labelWidth		提示文字的宽度,单位px  ( 默认 45 )
+	 * @property {String}						labelAlign		lable字体的对齐方式   ( 默认 ‘left' )
+	 * @property {Object}						labelStyle		lable的样式,对象形式
+	 * @example <u--formlabelPosition="left" :model="model1" :rules="rules" ref="form1"></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'
+	export default {
+		name: "u-form",
+		mixins: [uni.$u.mpMixin, uni.$u.mixin, props],
+		provide() {
+			return {
+				uForm: this,
+			};
 		},
-		// label的宽度,单位rpx
-		labelWidth: {
-			type: [String, Number],
-			default: 90
+		data() {
+			return {
+				formRules: {},
+				// 规则校验器
+				validator: {},
+				// 原始的model快照,用于resetFields方法重置表单时使用
+				originalModel: null,
+			};
 		},
-		// lable字体的对齐方式
-		labelAlign: {
-			type: String,
-			default: 'left'
-		},
-		// lable的样式,对象形式
-		labelStyle: {
-			type: Object,
-			default() {
-				return {}
-			}
+		watch: {
+			// 监听规则的变化
+			rules: {
+				immediate: true,
+				handler(n) {
+					this.setRules(n);
+				},
+			},
+			// 监听属性的变化,通知子组件u-form-item重新获取信息
+			propsChange(n) {
+				if (this.children?.length) {
+					this.children.map((child) => {
+						// 判断子组件(u-form-item)如果有updateParentData方法的话,就就执行(执行的结果是子组件重新从父组件拉取了最新的值)
+						typeof child.updateParentData == "function" &&
+							child.updateParentData();
+					});
+				}
+			},
+			// 监听model的初始值作为重置表单的快照
+			model: {
+				immediate: true,
+				handler(n) {
+					if (!this.originalModel) {
+						this.originalModel = uni.$u.deepClone(n);
+					}
+				},
+			},
 		},
-	},
-	provide() {
-		return {
-			uForm: this
-		};
-	},
-	data() {
-		return {
-			rules: {}
-		};
-	},
-	created() {
-		// 存储当前form下的所有u-form-item的实例
-		// 不能定义在data中,否则微信小程序会造成循环引用而报错
-		this.fields = [];
-	},
-	methods: {
-		setRules(rules) {
-			this.rules = rules;
+		computed: {
+			propsChange() {
+				return [
+					this.errorType,
+					this.borderBottom,
+					this.labelPosition,
+					this.labelWidth,
+					this.labelAlign,
+					this.labelStyle,
+				];
+			},
 		},
-		// 清空所有u-form-item组件的内容,本质上是调用了u-form-item组件中的resetField()方法
-		resetFields() {
-			this.fields.map(field => {
-				field.resetField();
-			});
+		created() {
+			// 存储当前form下的所有u-form-item的实例
+			// 不能定义在data中,否则微信小程序会造成循环引用而报错
+			this.children = [];
 		},
-		// 校验全部数据
-		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]);
+		methods: {
+			// 手动设置校验的规则,如果规则中有函数的话,微信小程序中会过滤掉,所以只能手动调用设置规则
+			setRules(rules) {
+				// 判断是否有规则
+				if (Object.keys(rules).length === 0) return;
+				if (process.env.NODE_ENV === 'development' && Object.keys(this.model).length === 0) {
+					uni.$u.error('设置rules,model必须设置!如果已经设置,请刷新页面。');
+					return;
+				};
+				this.formRules = rules;
+				// 重新将规则赋予Validator
+				this.validator = new Schema(rules);
+			},
+			// 清空所有u-form-item组件的内容,本质上是调用了u-form-item组件中的resetField()方法
+			resetFields() {
+				this.resetModel();
+			},
+			// 重置model为初始值的快照
+			resetModel(obj) {
+				// 历遍所有u-form-item,根据其prop属性,还原model的原始快照
+				this.children.map((child) => {
+					const prop = child?.prop;
+					const value = uni.$u.getProperty(this.originalModel, prop);
+					uni.$u.setProperty(this.model, prop, value);
+				});
+			},
+			// 清空校验结果
+			clearValidate(props) {
+				props = [].concat(props);
+				this.children.map((child) => {
+					// 如果u-form-item的prop在props数组中,则清除对应的校验结果信息
+					if (props[0] === undefined || props.includes(child.prop)) {
+						child.message = null;
+					}
+				});
+			},
+			// 对部分表单字段进行校验
+			async validateField(value, callback, event = null) {
+				// $nextTick是必须的,否则model的变更,可能会延后于此方法的执行
+				this.$nextTick(() => {
+					// 校验错误信息,返回给回调方法,用于存放所有form-item的错误信息
+					const errorsRes = [];
+					// 如果为字符串,转为数组
+					value = [].concat(value);
+					// 历遍children所有子form-item
+					this.children.map((child) => {
+						// 用于存放form-item的错误信息
+						const childErrors = [];
+						if (value.includes(child.prop)) {
+							// 获取对应的属性,通过类似'a.b.c'的形式
+							const propertyVal = uni.$u.getProperty(
+								this.model,
+								child.prop
+							);
+							// 属性链数组
+							const propertyChain = child.prop.split(".");
+							const propertyName =
+								propertyChain[propertyChain.length - 1];
+
+							const rule = this.formRules[child.prop];
+							// 如果不存在对应的规则,直接返回,否则校验器会报错
+							if (!rule) return;
+							// rule规则可为数组形式,也可为对象形式,此处拼接成为数组
+							const rules = [].concat(rule);
+
+							// 对rules数组进行校验
+							for (let i = 0; i < rules.length; i++) {
+								const ruleItem = rules[i];
+								// 将u-form-item的触发器转为数组形式
+								const trigger = [].concat(ruleItem?.trigger);
+								// 如果是有传入触发事件,但是此form-item却没有配置此触发器的话,不执行校验操作
+								if (event && !trigger.includes(event)) continue;
+								// 实例化校验对象,传入构造规则
+								const validator = new Schema({
+									[propertyName]: ruleItem,
+								});
+								validator.validate({
+										[propertyName]: propertyVal,
+									},
+									(errors, fields) => {
+										if (uni.$u.test.array(errors)) {
+											errorsRes.push(...errors);
+											childErrors.push(...errors);
+										}
+										child.message =
+											childErrors[0]?.message ?? null;
+									}
+								);
 							}
-							// 调用回调方法
-							if (typeof callback == 'function') callback(valid);
 						}
 					});
+					// 执行回调函数
+					typeof callback === "function" && callback(errorsRes);
 				});
-			});
-		}
-	}
-};
+			},
+			// 校验全部数据
+			validate(callback) {
+				// 开发环境才提示,生产环境不会提示
+				if (process.env.NODE_ENV === 'development' && Object.keys(this.formRules).length === 0) {
+					uni.$u.error('未设置rules,请看文档说明!如果已经设置,请刷新页面。');
+					return;
+				}
+				return new Promise((resolve, reject) => {
+					// $nextTick是必须的,否则model的变更,可能会延后于validate方法
+					this.$nextTick(() => {
+						// 获取所有form-item的prop,交给validateField方法进行校验
+						const formItemProps = this.children.map(
+							(item) => item.prop
+						);
+						this.validateField(formItemProps, (errors) => {
+							if(errors.length) {
+								// 如果错误提示方式为toast,则进行提示
+								this.errorType === 'toast' && uni.$u.toast(errors[0].message)
+								reject(errors)
+							} else {
+								resolve(true)
+							}
+						});
+					});
+				});
+			},
+		},
+	};
 </script>
 
-<style scoped lang="scss">
-@import "../../libs/css/style.components.scss";
+<style lang="scss" scoped>
 </style>

+ 0 - 52
uview-ui/components/u-full-screen/u-full-screen.vue

@@ -1,52 +0,0 @@
-<template>
-	<u-modal v-model="show" :show-cancel-button="true" confirm-text="升级" title="发现新版本" @cancel="cancel" @confirm="confirm">
-		<view class="u-update-content">
-			<rich-text :nodes="content"></rich-text>
-		</view>
-	</u-modal>
-</template>
-
-<script>
-	export default {
-		data() {
-			return {
-				show: false,
-				content: `
-					1. 修复badge组件的size参数无效问题<br>
-					2. 新增Modal模态框组件<br>
-					3. 新增压窗屏组件,可以在APP上以弹窗的形式遮盖导航栏和底部tabbar<br>
-					4. 修复键盘组件在微信小程序上遮罩无效的问题
-				`,
-			}
-		},
-		onReady() {
-			this.show = true;
-		},
-		methods: {
-			cancel() {
-				this.closeModal();
-			},
-			confirm() {
-				this.closeModal();
-			},
-			closeModal() {
-				uni.navigateBack();
-			}
-		}
-	}
-</script>
-
-<style scoped lang="scss">
-	@import "../../libs/css/style.components.scss";
-	
-	.u-full-content {
-		background-color: #00C777;
-	}
-	
-	.u-update-content {
-		font-size: 26rpx;
-		color: $u-content-color;
-		line-height: 1.7;
-		padding: 30rpx;
-	}
-</style>

+ 24 - 0
uview-ui/components/u-gap/props.js

@@ -0,0 +1,24 @@
+export default {
+    props: {
+        // 背景颜色(默认transparent)
+        bgColor: {
+            type: String,
+            default: uni.$u.props.gap.bgColor
+        },
+        // 分割槽高度,单位px(默认30)
+        height: {
+            type: [String, Number],
+            default: uni.$u.props.gap.height
+        },
+        // 与上一个组件的距离
+        marginTop: {
+            type: [String, Number],
+            default: uni.$u.props.gap.marginTop
+        },
+        // 与下一个组件的距离
+        marginBottom: {
+            type: [String, Number],
+            default: uni.$u.props.gap.marginBottom
+        }
+    }
+}

+ 28 - 44
uview-ui/components/u-gap/u-gap.vue

@@ -3,52 +3,36 @@
 </template>
 
 <script>
-/**
- * gap 间隔槽
- * @description 该组件一般用于内容块之间的用一个灰色块隔开的场景,方便用户风格统一,减少工作量
- * @tutorial https://www.uviewui.com/components/gap.html
- * @property {String} bg-color 背景颜色(默认#f3f4f6)
- * @property {String Number} height 分割槽高度,单位rpx(默认30)
- * @property {String Number} margin-top 与前一个组件的距离,单位rpx(默认0)
- * @property {String Number} margin-bottom 与后一个组件的距离,单位rpx(0)
- * @example <u-gap height="80" bg-color="#bbb"></u-gap>
- */
-export default {
-	name: "u-gap",
-	props: {
-		bgColor: {
-			type: String,
-			default: 'transparent ' // 背景透明
-		},
-		// 高度
-		height: {
-			type: [String, Number],
-			default: 30
-		},
-		// 与上一个组件的距离
-		marginTop: {
-			type: [String, Number],
-			default: 0
-		},
-		// 与下一个组件的距离
-		marginBottom: {
-			type: [String, Number],
-			default: 0
-		},
-	},
-	computed: {
-		gapStyle() {
-			return {
-				backgroundColor: this.bgColor,
-				height: this.height + 'rpx',
-				marginTop: this.marginTop + 'rpx',
-				marginBottom: this.marginBottom + 'rpx'
-			};
+	import props from './props.js';
+	/**
+	 * gap 间隔槽
+	 * @description 该组件一般用于内容块之间的用一个灰色块隔开的场景,方便用户风格统一,减少工作量
+	 * @tutorial https://www.uviewui.com/components/gap.html
+	 * @property {String}			bgColor			背景颜色 (默认 'transparent' )
+	 * @property {String | Number}	height			分割槽高度,单位px (默认 20 )
+	 * @property {String | Number}	marginTop		与前一个组件的距离,单位px( 默认 0 )
+	 * @property {String | Number}	marginBottom	与后一个组件的距离,单位px (默认 0 )
+	 * @property {Object}			customStyle		定义需要用到的外部样式
+	 * 
+	 * @example <u-gap height="80" bg-color="#bbb"></u-gap>
+	 */
+	export default {
+		name: "u-gap",
+		mixins: [uni.$u.mpMixin, uni.$u.mixin,props],
+		computed: {
+			gapStyle() {
+				const style = {
+					backgroundColor: this.bgColor,
+					height: uni.$u.addUnit(this.height),
+					marginTop: uni.$u.addUnit(this.marginTop),
+					marginBottom: uni.$u.addUnit(this.marginBottom),
+				}
+				return uni.$u.deepMerge(style, uni.$u.addStyle(this.customStyle))
+			}
 		}
-	}
-};
+	};
 </script>
 
 <style lang="scss" scoped>
-	@import "../../libs/css/style.components.scss";
+	@import "../../libs/css/components.scss";
 </style>

+ 14 - 0
uview-ui/components/u-grid-item/props.js

@@ -0,0 +1,14 @@
+export default {
+    props: {
+        // 宫格的name
+        name: {
+            type: [String, Number, null],
+            default: uni.$u.props.gridItem.name
+        },
+        // 背景颜色
+        bgColor: {
+            type: String,
+            default: uni.$u.props.gridItem.bgColor
+        }
+    }
+}

+ 155 - 72
uview-ui/components/u-grid-item/u-grid-item.vue

@@ -1,126 +1,209 @@
 <template>
-	<view class="u-grid-item" :hover-class="parentData.hoverClass"
-	 :hover-stay-time="200" @tap="click" :style="{
-			background: bgColor,
-			width: width,
-		}">
-		<view class="u-grid-item-box" :style="[customStyle]" :class="[parentData.border ? 'u-border-right u-border-bottom' : '']">
-			<slot />
-		</view>
+	<!-- #ifndef APP-NVUE -->
+	<view
+	    class="u-grid-item"
+	    hover-class="u-grid-item--hover-class"
+	    :hover-stay-time="200"
+	    @tap="clickHandler"
+	    :class="classes"
+	    :style="[itemStyle]"
+	>
+		<slot />
 	</view>
+	<!-- #endif -->
+	<!-- #ifdef APP-NVUE -->
+	<view
+	    class="u-grid-item"
+	    :hover-stay-time="200"
+	    @tap="clickHandler"
+	    :class="classes"
+	    :style="[itemStyle]"
+	>
+		<slot />
+	</view>
+	<!-- #endif -->
 </template>
 
 <script>
+	import props from './props.js';
 	/**
 	 * gridItem 提示
 	 * @description 宫格组件一般用于同时展示多个同类项目的场景,可以给宫格的项目设置徽标组件(badge),或者图标等,也可以扩展为左右滑动的轮播形式。搭配u-grid使用
 	 * @tutorial https://www.uviewui.com/components/grid.html
-	 * @property {String} bg-color 宫格的背景颜色(默认#ffffff)
-	 * @property {String Number} index 点击宫格时,返回的值
-	 * @property {Object} custom-style 自定义样式,对象形式
+	 * @property {String | Number}	name		宫格的name ( 默认 null )
+	 * @property {String}			bgColor		宫格的背景颜色 (默认 'transparent' )
+	 * @property {Object}			customStyle	自定义样式,对象形式
 	 * @event {Function} click 点击宫格触发
 	 * @example <u-grid-item></u-grid-item>
 	 */
 	export default {
 		name: "u-grid-item",
-		props: {
-			// 背景颜色
-			bgColor: {
-				type: String,
-				default: '#ffffff'
-			},
-			// 点击时返回的index
-			index: {
-				type: [Number, String],
-				default: ''
-			},
-			// 自定义样式,对象形式
-			customStyle: {
-				type: Object,
-				default() {
-					return {
-						padding: '30rpx 0'
-					}
-				}
-			}
-		},
+		mixins: [uni.$u.mpMixin, uni.$u.mixin,props],
 		data() {
 			return {
 				parentData: {
-					hoverClass: '', // 按下去的时候,是否显示背景灰色
 					col: 3, // 父组件划分的宫格数
 					border: true, // 是否显示边框,根据父组件决定
-				}
+				},
+				// #ifdef APP-NVUE
+				width: 0, // nvue下才这么计算,vue下放到computed中,否则会因为延时造成闪烁
+				// #endif
+				classes: [], // 类名集合,用于判断是否显示右边和下边框
 			};
 		},
-		created() {
-			// 父组件的实例
-			this.updateParentData();
-			// this.parent在updateParentData()中定义
-			this.parent.children.push(this);
+		mounted() {
+			this.init()
 		},
 		computed: {
-			// 每个grid-item的宽度
+			// #ifndef APP-NVUE
+			// vue下放到computed中,否则会因为延时造成闪烁
 			width() {
-				return 100 / Number(this.parentData.col) + '%';
+				return 100 / Number(this.parentData.col) + '%'
 			},
+			// #endif
+			itemStyle() {
+				const style = {
+					background: this.bgColor,
+					width: this.width
+				}
+				return uni.$u.deepMerge(style, uni.$u.addStyle(this.customStyle))
+			}
 		},
 		methods: {
+			init() {
+				// 用于在父组件u-grid的children中被添加入子组件时,
+				// 重新计算item的边框
+				uni.$on('$uGridItem', () => {
+					this.gridItemClasses()
+				})
+				// 父组件的实例
+				this.updateParentData()
+				// #ifdef APP-NVUE
+				// 获取元素该有的长度,nvue下要延时才准确
+				this.$nextTick(function(){
+					this.getItemWidth()
+				})
+				// #endif
+				// 发出事件,通知所有的grid-item都重新计算自己的边框
+				uni.$emit('$uGridItem')
+				this.gridItemClasses()
+			},
 			// 获取父组件的参数
 			updateParentData() {
 				// 此方法写在mixin中
 				this.getParentData('u-grid');
 			},
-			click() {
-				this.$emit('click', this.index);
-				this.parent && this.parent.click(this.index);
+			clickHandler() {
+				let name = this.name
+				// 如果没有设置name属性,历遍父组件的children数组,判断当前的元素是否和本实例this相等,找出当前组件的索引
+				const children = this.parent?.children
+				if(children && this.name === null) {
+					name = children.findIndex(child => child === this)
+				}
+				// 调用父组件方法,发出事件
+				this.parent && this.parent.childClick(name)
+				this.$emit('click', name)
+			},
+			async getItemWidth() {
+				// 如果是nvue,不能使用百分比,只能使用固定宽度
+				let width = 0
+				if(this.parent) {
+					// 获取父组件宽度后,除以栅格数,得出每个item的宽度
+					const parentWidth = await this.getParentWidth()
+					width = parentWidth / Number(this.parentData.col) + 'px'
+				}
+				this.width = width
+			},
+			// 获取父元素的尺寸
+			getParentWidth() {
+				// #ifdef APP-NVUE
+				// 返回一个promise,让调用者可以用await同步获取
+				const dom = uni.requireNativePlugin('dom')
+				return new Promise(resolve => {
+					// 调用父组件的ref
+					dom.getComponentRect(this.parent.$refs['u-grid'], res => {
+						resolve(res.size.width)
+					})
+				})
+				// #endif
+			},
+			gridItemClasses() {
+				if(this.parentData.border) {
+					const classes = []
+					this.parent.children.map((child, index) =>{
+						if(this === child) {
+							const len = this.parent.children.length
+							// 贴近右边屏幕边沿的child,并且最后一个(比如只有横向2个的时候),无需右边框
+							if((index + 1) % this.parentData.col !== 0 && index + 1 !== len) {
+								classes.push('u-border-right')
+							}
+							// 总的宫格数量对列数取余的值
+							// 如果取余后,值为0,则意味着要将最后一排的宫格,都不需要下边框
+							const lessNum = len % this.parentData.col === 0 ? this.parentData.col : len % this.parentData.col
+							// 最下面的一排child,无需下边框
+							if(index < len - lessNum) {
+								classes.push('u-border-bottom')
+							}
+						}
+					})
+					// 支付宝,头条小程序无法动态绑定一个数组类名,否则解析出来的结果会带有",",而导致失效
+					// #ifdef MP-ALIPAY || MP-TOUTIAO
+					classes = classes.join(' ')
+					// #endif
+					this.classes = classes
+				}
 			}
+		},
+		beforeDestroy() {
+			// 移除事件监听,释放性能
+			uni.$off('$uGridItem')
 		}
 	};
 </script>
 
-<style scoped lang="scss">
-	@import "../../libs/css/style.components.scss";
-	
+<style lang="scss" scoped>
+	@import "../../libs/css/components.scss";
+      $u-grid-item-hover-class-opcatiy:.5 !default;
+      $u-grid-item-margin-top:1rpx !default;
+      $u-grid-item-border-right-width:0.5px !default;
+      $u-grid-item-border-bottom-width:0.5px !default;
+      $u-grid-item-border-right-color:$u-border-color !default;
+      $u-grid-item-border-bottom-color:$u-border-color !default;
 	.u-grid-item {
-		box-sizing: border-box;
-		background: #fff;
-		@include vue-flex;
 		align-items: center;
 		justify-content: center;
 		position: relative;
 		flex-direction: column;
-		
+		/* #ifndef APP-NVUE */
+		box-sizing: border-box;
+		display: flex;
+		/* #endif */
+
 		/* #ifdef MP */
 		position: relative;
 		float: left;
 		/* #endif */
-	}
-
-	.u-grid-item-hover {
-		background: #f7f7f7 !important;
-	}
 
-	.u-grid-marker-box {
-		position: absolute;
-		/* #ifndef APP-NVUE */
-		display: inline-flex;		
+		/* #ifdef MP-WEIXIN */
+		margin-top:$u-grid-item-margin-top;
 		/* #endif */
-		line-height: 0;
+
+		&--hover-class {
+			opacity:$u-grid-item-hover-class-opcatiy;
+		}
 	}
 
-	.u-grid-marker-wrap {
-		position: absolute;
+	/* #ifdef APP-NVUE */
+	// 由于nvue不支持组件内引入app.vue中再引入的样式,所以需要写在这里
+	.u-border-right {
+		border-right-width:$u-grid-item-border-right-width;
+		border-color: $u-grid-item-border-right-color;
 	}
 
-	.u-grid-item-box {
-		padding: 30rpx 0;
-		@include vue-flex;
-		align-items: center;
-		justify-content: center;
-		flex-direction: column;
-		flex: 1;
-		width: 100%;
-		height: 100%;
+	.u-border-bottom {
+		border-bottom-width:$u-grid-item-border-bottom-width;
+		border-color:$u-grid-item-border-bottom-color;
 	}
+
+	/* #endif */
 </style>

+ 19 - 0
uview-ui/components/u-grid/props.js

@@ -0,0 +1,19 @@
+export default {
+    props: {
+        // 分成几列
+        col: {
+            type: [String, Number],
+            default: uni.$u.props.grid.col
+        },
+        // 是否显示边框
+        border: {
+            type: Boolean,
+            default: uni.$u.props.grid.border
+        },
+        // 宫格对齐方式,表现为数量少的时候,靠左,居中,还是靠右
+        align: {
+            type: String,
+            default: uni.$u.props.grid.align
+        }
+    }
+}

Niektoré súbory nie sú zobrazené, pretože je v týchto rozdielových dátach zmenené mnoho súborov