Sfoglia il codice sorgente

2024.01.17
- 项目更新合并

zweiqin 1 anno fa
parent
commit
215d17f9d4
100 ha cambiato i file con 10207 aggiunte e 207 eliminazioni
  1. 1 1
      another-tf/another-serve/discount/discount.vue
  2. 1 1
      another-tf/another-serve/discount/groupBuy.vue
  3. 1 1
      another-tf/another-serve/discount/platformDiscount.vue
  4. 1 1
      another-tf/another-serve/distributionModule/recruit.vue
  5. 2 2
      another-tf/another-serve/evaSuccessful/index.vue
  6. 1 1
      another-tf/another-serve/memberCenter/activityList.vue
  7. 1 1
      another-tf/another-serve/memberCenter/index.vue
  8. 2 2
      another-tf/another-serve/paySuccessful/index.vue
  9. 1 1
      another-tf/another-serve/refundDetails/index.vue
  10. 1 1
      another-tf/another-serve/returnDetails/index.vue
  11. 1 1
      another-tf/another-serve/shopCar/shopCar.vue
  12. 1 1
      another-tf/another-user/advanced-role-application/index.vue
  13. 1 1
      another-tf/another-user/commission-statistics/commission-statistics.vue
  14. 1 1
      another-tf/another-user/shop/shop-detail.vue
  15. 1 1
      another-tf/another-user/shop/shop-enter.vue
  16. 1 1
      another-tf/another-user/user-upgrade/user-upgrade-application.vue
  17. 1 1
      components/BeeBack/BeeBack.vue
  18. 1 1
      components/canvasShow/basics/live/mixin.js
  19. 2 2
      manifest.json
  20. 94 18
      pages.json
  21. 119 0
      pages/classification/index.vue
  22. 11 11
      pages/jump/jump.vue
  23. BIN
      pages/login/image/bg.png
  24. 274 0
      pages/login/login copy.vue
  25. 154 152
      pages/login/login.vue
  26. 1 1
      pages/login/register.vue
  27. 232 0
      pages/new-tabbar/Hall/index.scss
  28. 94 0
      pages/new-tabbar/Hall/index.vue
  29. 129 0
      pages/new-tabbar/components/Nav.vue
  30. 271 0
      pages/new-tabbar/findServices/index.vue
  31. 327 0
      pages/new-tabbar/findStore/index.vue
  32. 365 0
      pages/new-tabbar/home/index.scss
  33. 210 0
      pages/new-tabbar/home/index.vue
  34. 201 0
      pages/new-tabbar/user/index.vue
  35. 245 0
      pages/register/index.vue
  36. BIN
      static/new-images/findServices/give_1.png
  37. BIN
      static/new-images/findServices/give_2.png
  38. BIN
      static/new-images/findServices/give_3.png
  39. BIN
      static/new-images/findServices/give_4.png
  40. BIN
      static/new-images/findServices/give_5.png
  41. BIN
      static/new-images/findServices/institution_1.png
  42. BIN
      static/new-images/findServices/top_1.png
  43. BIN
      static/new-images/findServices/top_2.png
  44. BIN
      static/new-images/findServices/top_3.png
  45. BIN
      static/new-images/findStore/icon.png
  46. BIN
      static/new-images/findStore/product.png
  47. BIN
      static/new-images/hall/head.png
  48. BIN
      static/new-images/home/bg-icon.png
  49. BIN
      static/new-images/home/classification.png
  50. BIN
      static/new-images/home/icon_1.png
  51. BIN
      static/new-images/home/icon_2.png
  52. BIN
      static/new-images/home/icon_3.png
  53. BIN
      static/new-images/home/icon_4.png
  54. BIN
      static/new-images/home/icon_5.png
  55. BIN
      static/new-images/home/icon_6.png
  56. BIN
      static/new-images/home/icon_7.png
  57. BIN
      static/new-images/home/icon_8.png
  58. BIN
      static/new-images/home/swiper.png
  59. BIN
      static/new-images/tabbar/findServices.png
  60. BIN
      static/new-images/tabbar/findServices_1.png
  61. BIN
      static/new-images/tabbar/findStore.png
  62. BIN
      static/new-images/tabbar/findStore_1.png
  63. BIN
      static/new-images/tabbar/hall.png
  64. BIN
      static/new-images/tabbar/hall_1.png
  65. BIN
      static/new-images/tabbar/home.png
  66. BIN
      static/new-images/tabbar/home_1.png
  67. BIN
      static/new-images/tabbar/user.png
  68. BIN
      static/new-images/tabbar/user_1.png
  69. BIN
      static/new-images/user/bg.png
  70. BIN
      static/new-images/user/go.png
  71. BIN
      static/new-images/user/header.jpg
  72. 3 3
      store/modules/auth.js
  73. 21 0
      uview-ui/LICENSE
  74. 102 0
      uview-ui/README.md
  75. 182 0
      uview-ui/components/u-action-sheet/u-action-sheet.vue
  76. 256 0
      uview-ui/components/u-alert-tips/u-alert-tips.vue
  77. 290 0
      uview-ui/components/u-avatar-cropper/u-avatar-cropper.vue
  78. 1265 0
      uview-ui/components/u-avatar-cropper/weCropper.js
  79. 24 0
      uview-ui/components/u-avatar/u-avatar.vue
  80. 153 0
      uview-ui/components/u-back-top/u-back-top.vue
  81. 213 0
      uview-ui/components/u-badge/u-badge.vue
  82. 576 0
      uview-ui/components/u-button/u-button.vue
  83. 641 0
      uview-ui/components/u-calendar/u-calendar.vue
  84. 254 0
      uview-ui/components/u-car-keyboard/u-car-keyboard.vue
  85. 298 0
      uview-ui/components/u-card/u-card.vue
  86. 69 0
      uview-ui/components/u-cell-group/u-cell-group.vue
  87. 300 0
      uview-ui/components/u-cell-item/u-cell-item.vue
  88. 122 0
      uview-ui/components/u-checkbox-group/u-checkbox-group.vue
  89. 275 0
      uview-ui/components/u-checkbox/u-checkbox.vue
  90. 218 0
      uview-ui/components/u-circle-progress/u-circle-progress.vue
  91. 135 0
      uview-ui/components/u-col/u-col.vue
  92. 206 0
      uview-ui/components/u-collapse-item/u-collapse-item.vue
  93. 104 0
      uview-ui/components/u-collapse/u-collapse.vue
  94. 233 0
      uview-ui/components/u-column-notice/u-column-notice.vue
  95. 315 0
      uview-ui/components/u-count-down/u-count-down.vue
  96. 239 0
      uview-ui/components/u-count-to/u-count-to.vue
  97. 151 0
      uview-ui/components/u-divider/u-divider.vue
  98. 242 0
      uview-ui/components/u-dropdown-list/u-dropdown-list.vue
  99. 193 0
      uview-ui/components/u-empty/u-empty.vue
  100. 378 0
      uview-ui/components/u-field/u-field.vue

+ 1 - 1
another-tf/another-serve/discount/discount.vue

@@ -209,7 +209,7 @@ export default {
 				let netxHou = hou
 				if (netxHou == 0 && netxMin == 0 && netxSec == -1) {
 					clearTimeout(timeOut)
-					this.$switchTab('/pages/another-index/index')
+					this.$switchTab('/')
 					uni.showToast({
 						title: '活动结束',
 						duration: 2000,

+ 1 - 1
another-tf/another-serve/discount/groupBuy.vue

@@ -219,7 +219,7 @@ export default {
 				let netxHou = hou
 				if (netxHou == 0 && netxMin == 0 && netxSec == -1) {
 					clearTimeout(timeOut)
-					this.$switchTab('/pages/another-index/index')
+					this.$switchTab('/')
 					uni.showToast({
 						title: '活动结束',
 						duration: 2000,

+ 1 - 1
another-tf/another-serve/discount/platformDiscount.vue

@@ -188,7 +188,7 @@ export default {
 				let netxHou = hou
 				if (netxHou == 0 && netxMin == 0 && netxSec == -1) {
 					clearTimeout(timeOut)
-					this.$switchTab('/pages/another-index/index')
+					this.$switchTab('/')
 					uni.showToast({
 						title: '活动结束',
 						duration: 2000,

+ 1 - 1
another-tf/another-serve/distributionModule/recruit.vue

@@ -124,7 +124,7 @@ export default {
 					title: '请先绑定手机号!',
 					icon: 'none'
 				})
-				this.$switchTab('/pages/another-index/index')
+				this.$switchTab('/')
 			}
 		},
 		exitButClick() {

+ 2 - 2
another-tf/another-serve/evaSuccessful/index.vue

@@ -29,10 +29,10 @@ export default {
 	},
 	methods: {
 		goToOrder() {
-			this.$switchTab('/pages/order/order')
+			this.$switchTab('/')
 		},
 		goToHome() {
-			this.$switchTab('/pages/another-index/index')
+			this.$switchTab('/')
 		}
 	}
 }

+ 1 - 1
another-tf/another-serve/memberCenter/activityList.vue

@@ -261,7 +261,7 @@ export default {
 		//
 		//     if (netxHou == 0 && netxMin == 0 && netxSec == -1) {
 		//       clearTimeout(timeOut)
-		//       this.$switchTab('/pages/another-index/index')
+		//       this.$switchTab('/')
 		//       uni.showToast({
 		//         title:"活动结束",
 		//         duration:2000,

+ 1 - 1
another-tf/another-serve/memberCenter/index.vue

@@ -195,7 +195,7 @@ export default {
 		},
 		// 去首页
 		goToShopping() {
-			this.$switchTab('/pages/another-index/index')
+			this.$switchTab('/')
 		},
 		getPercent(num, total) {
 			num = parseFloat(num)

+ 2 - 2
another-tf/another-serve/paySuccessful/index.vue

@@ -67,10 +67,10 @@ export default {
 	},
 	methods: {
 		goToOrder() {
-			this.$switchTab('/pages/order/order')
+			this.$switchTab('/')
 		},
 		goToHome() {
-			this.$switchTab('/pages/another-index/index')
+			this.$switchTab('/')
 		},
 		getOrderPoliteFn() {
 			uni.showLoading({

+ 1 - 1
another-tf/another-serve/refundDetails/index.vue

@@ -214,7 +214,7 @@ export default {
 				uni.showToast({
 					title: '撤销成功'
 				})
-				this.$switchTab('/pages/user/user')
+				this.$switchTab('/')
 			})
 				.catch((e) => {
 					uni.hideLoading()

+ 1 - 1
another-tf/another-serve/returnDetails/index.vue

@@ -248,7 +248,7 @@ export default {
 				uni.showToast({
 					title: '撤销成功'
 				})
-				this.$switchTab('/pages/user/user')
+				this.$switchTab('/')
 			})
 		},
 		goAddLogistics() {

+ 1 - 1
another-tf/another-serve/shopCar/shopCar.vue

@@ -37,7 +37,7 @@
 						<image class="emptyCart-img" src="../../../static/images/new-business/shop/bgnull.png"></image>
 						<label class="font-color-999 fs26 mar-top-30">你的购物车还没有宝贝哦</label>
 						<label class="font-color-999 fs26 mar-top-10">快去首页选一个吧~</label>
-						<view class="goToShopping" @click="$switchTab('/pages/another-index/index')">
+						<view class="goToShopping" @click="$switchTab('/')">
 							去购物
 						</view>
 					</view>

+ 1 - 1
another-tf/another-user/advanced-role-application/index.vue

@@ -111,7 +111,7 @@ export default {
 						addPlatformRelationshipApplyApi(this.form).then((res) => {
 							this.$showToast('提交成功')
 							setTimeout(() => {
-								this.$switchTab('/pages/user/user')
+								this.$switchTab('/')
 							}, 3000)
 						})
 					}

+ 1 - 1
another-tf/another-user/commission-statistics/commission-statistics.vue

@@ -84,7 +84,7 @@ export default {
 	methods: {
 		handleBack() {
 			uni.switchTab({
-				url: '/pages/user/user'
+				url: '/'
 			})
 		},
 

+ 1 - 1
another-tf/another-user/shop/shop-detail.vue

@@ -149,7 +149,7 @@ export default {
 		return {
 			successCb: () => {
 				const pages = getCurrentPages()
-				if (pages[pages.length - 2].route === 'pages/store/store') uni.$emit('sendStoreDetailMsg', { data: { meaning: 'refreshCurrentData' } })
+				if (pages[pages.length - 2].route === 'pages/business-district/business-district') uni.$emit('sendStoreDetailMsg', { data: { meaning: 'refreshCurrentData' } })
 			},
 			shopId: null,
 			brandDetail: {},

+ 1 - 1
another-tf/another-user/shop/shop-enter.vue

@@ -591,7 +591,7 @@ export default {
 	methods: {
 		back() {
 			uni.switchTab({
-				url: '/pages/business-district/business-district'
+				url: '/'
 			})
 		},
 		handleSelectPrimaryFilter(e) {

+ 1 - 1
another-tf/another-user/user-upgrade/user-upgrade-application.vue

@@ -154,7 +154,7 @@ export default {
 							this.displayBadgesMsg.isShowBadges = true
 							// setTimeout(() => {
 							// 	// uni.navigateBack()
-							// 	this.$switchTab('/pages/user/user')
+							// 	this.$switchTab('/')
 							// }, 3000)
 						})
 					}

+ 1 - 1
components/BeeBack/BeeBack.vue

@@ -45,7 +45,7 @@ export default {
 			const pagesLength = pages.length
 			let backLevel = 1
 			if (pages.length === 1) {
-				this.$switchTab('/pages/another-index/index')
+				this.$switchTab('/')
 			} else {
 				const lastUrl = pages[pagesLength - 1].route + this.getParams(pages[pagesLength - 1].options)
 				for (

+ 1 - 1
components/canvasShow/basics/live/mixin.js

@@ -5,7 +5,7 @@ export const commonMixin = {
 	mixins: [ funMixin ],
 	data() {
 		return {
-			appid: 'wx123456789abcdefg',
+			appid: 'wxc0aec85efc6b05e5',
 			roomId: [], // 填写具体的房间号
 			roomList: [],
 			page: {

+ 2 - 2
manifest.json

@@ -155,11 +155,11 @@
     },
     "quickapp" : {},
     "mp-weixin" : {
-        "appid" : "1234",
+        "appid" : "wxc0aec85efc6b05e5",
         "requiredPrivateInfos" : [ "chooseLocation", "getLocation", "chooseAddress" ],
         "setting" : {
             "urlCheck" : false,
-            "es6" : false,
+            "es6" : true,
             "postcss" : true,
             "minified" : true
         },

+ 94 - 18
pages.json

@@ -1,6 +1,36 @@
 {
 	"pages": [
 		{
+			"path": "pages/new-tabbar/home/index",
+			"style": {
+				"navigationStyle": "custom"
+			}
+		},
+		{
+			"path": "pages/new-tabbar/findStore/index",
+			"style": {
+				"navigationStyle": "custom"
+			}
+		},
+		{
+			"path": "pages/new-tabbar/Hall/index",
+			"style": {
+				"navigationStyle": "custom"
+			}
+		},
+		{
+			"path": "pages/new-tabbar/findServices/index",
+			"style": {
+				"navigationStyle": "custom"
+			}
+		},
+		{
+			"path": "pages/new-tabbar/user/index",
+			"style": {
+				"navigationStyle": "custom"
+			}
+		},
+		{
 			"path": "pages/another-index/index",
 			"style": {
 				"navigationBarTitleText": "商城",
@@ -61,7 +91,7 @@
 		{
 			"path": "pages/login/login",
 			"style": {
-				"navigationBarTitleText": "手机号登录"
+				"navigationBarTitleText": "登陆"
 			}
 		},
 		{
@@ -71,6 +101,21 @@
 			}
 		},
 		{
+			"path": "pages/register/index",
+			"style": {
+				"navigationBarTitleText": "注册",
+				"enablePullDownRefresh": true,
+				"navigationStyle": "custom"
+			}
+		},
+		{
+			"path": "pages/classification/index",
+			"style": {
+				"navigationBarTitleText": "分类",
+				"enablePullDownRefresh": false
+			}
+		},
+		{
 			"path": "pages/user/kuai-di/index",
 			"style": {
 				"navigationBarTitleText": "",
@@ -116,7 +161,8 @@
 	"easycom": {
 		"autoscan": true,
 		"custom": {
-			"tui-(.*)": "@/components/thorui/tui-$1/tui-$1.vue"
+			"tui-(.*)": "@/components/thorui/tui-$1/tui-$1.vue",
+			"^u-(.*)": "@/uview-ui/components/u-$1/u-$1.vue"
 		}
 	},
 	"subPackages": [
@@ -719,29 +765,59 @@
 		"height": "60px",
 		"list": [
 			{
-				"pagePath": "pages/another-index/index",
-				"text": "商城",
-				"iconPath": "/static/images/tabbar/furniture.png",
-				"selectedIconPath": "/static/images/tabbar/furniture-active.png"
+				"pagePath": "pages/new-tabbar/home/index",
+				"iconPath": "static/new-images/tabbar/home.png",
+				"selectedIconPath": "static/new-images/tabbar/home_1.png",
+				"text": "找材料"
+			},
+			{
+				"pagePath": "pages/new-tabbar/findStore/index",
+				"iconPath": "static/new-images/tabbar/findStore.png",
+				"selectedIconPath": "static/new-images/tabbar/findStore_1.png",
+				"text": "找店铺"
 			},
 			{
-				"pagePath": "pages/business-district/business-district",
-				"text": "社区商圈",
-				"iconPath": "/static/images/tabbar/business.png",
-				"selectedIconPath": "/static/images/tabbar/business-active.png"
+				"pagePath": "pages/new-tabbar/Hall/index",
+				"iconPath": "static/new-images/tabbar/hall.png",
+				"selectedIconPath": "static/new-images/tabbar/hall_1.png",
+				"text": "信息大厅"
 			},
 			{
-				"pagePath": "pages/order/order",
-				"text": "订单",
-				"iconPath": "/static/images/tabbar/order.png",
-				"selectedIconPath": "/static/images/tabbar/order-active.png"
+				"pagePath": "pages/new-tabbar/findServices/index",
+				"iconPath": "static/new-images/tabbar/findServices.png",
+				"selectedIconPath": "static/new-images/tabbar/findServices_1.png",
+				"text": "找服务"
 			},
 			{
-				"pagePath": "pages/user/user",
-				"text": "我的",
-				"iconPath": "/static/images/tabbar/mine.png",
-				"selectedIconPath": "/static/images/tabbar/mine-active.png"
+				"pagePath": "pages/new-tabbar/user/index",
+				"iconPath": "static/new-images/tabbar/user.png",
+				"selectedIconPath": "static/new-images/tabbar/user_1.png",
+				"text": "我的"
 			}
+			// {
+			// 	"pagePath": "pages/another-index/index",
+			// 	"text": "商城",
+			// 	"iconPath": "/static/images/tabbar/furniture.png",
+			// 	"selectedIconPath": "/static/images/tabbar/furniture-active.png"
+			// },
+			// {
+			// 	"pagePath": "pages/business-district/business-district",
+			// 	"text": "社区商圈",
+			// 	"iconPath": "/static/images/tabbar/business.png",
+			// 	"selectedIconPath": "/static/images/tabbar/business-active.png"
+			// },
+			// {
+			// 	"pagePath": "pages/order/order",
+			// 	"text": "订单",
+			// 	"iconPath": "/static/images/tabbar/order.png",
+			// 	"selectedIconPath": "/static/images/tabbar/order-active.png"
+			// },
+			// {
+			// 	"pagePath": "pages/user/user",
+			// 	"text": "我的",
+			// 	"iconPath": "/static/images/tabbar/mine.png",
+			// 	"selectedIconPath": "/static/images/tabbar/mine-active.png"
+			// }
 		]
 	},
 	"sitemapLocation": "sitemap.json",

+ 119 - 0
pages/classification/index.vue

@@ -0,0 +1,119 @@
+<template>
+	<view class="classification">
+		<main>
+			<view class="left-class">
+				<scroll-view :scroll-top="leftScroll" scroll-y="true" class="left-scroll">
+					<view
+						v-for="(item, index) in leftData" :key="index" class="class-item"
+						:class="index == idx ? 'active' : ''" @click="leftClick(index, item.classifyId)"
+					>
+						{{ item.classifyName }}
+					</view>
+				</scroll-view>
+			</view>
+			<view class="right-scroll">
+				<template v-if="ifEmpty">
+					<div class="empty-box">
+						<image src="../../static/images/new-business/shop/bgnull.png"></image>
+						<span>该分类没有商品</span>
+					</div>
+				</template>
+			</view>
+		</main>
+	</view>
+</template>
+
+<script>
+import { getFirstClassifyApi } from '../../api/anotherTFInterface'
+export default {
+	data() {
+		return {
+			leftScroll: 0,
+			leftData: [],
+			idx: 0,
+			classForm: {
+				classifyId: ''
+			},
+			//  控制右边空状态的显示
+			ifEmpty: true
+		}
+	},
+	created() {
+		this.getLeft()
+	},
+	methods: {
+		leftClick(val, id) {
+			this.idx = val
+			this.classForm.classifyId = id
+		},
+		// 获取左边侧边栏的数据
+		async getLeft() {
+			const res = await getFirstClassifyApi(this.classForm)
+			console.log(res)
+			this.leftData = res.data
+		}
+	}
+}
+</script>
+
+<style lang="scss" scoped>
+uni-page-body {
+	height: 100%;
+}
+
+.classification {
+	height: 100%;
+
+	main {
+			display: flex;
+			height: 100%;
+
+			.left-class {
+					width: 200rpx;
+					background-color: #f8f8f8;
+					padding-top: 20rpx;
+
+					.left-scroll {
+							white-space: nowrap;
+							width: 100%;
+							height: 100%;
+							.class-item {
+									width: 100%;
+									height: 100rpx;
+									text-align: center;
+									line-height: 100rpx;
+									font-size: 28rpx;
+							}
+
+							.active {
+									background-color: #fff;
+									color: #EF530E;
+									font-weight: 700;
+							}
+					}
+			}
+
+			.right-scroll {
+					flex: 1;
+					position: relative;
+					width: 100%;
+					height: 100%;
+					.empty-box{
+							position: absolute;
+							left: 50%;
+							top: 30%;
+							transform: translateX(-50%);
+							display: flex;
+							flex-direction: column;
+							align-items: center;
+							gap: 30rpx;
+							color: #999;
+							image{
+									width: 200rpx;
+									height: 200rpx;
+							}
+					}
+			}
+	}
+}
+</style>

+ 11 - 11
pages/jump/jump.vue

@@ -1,7 +1,7 @@
 <template>
 	<view class="jump-container">
 		<view v-if="viewType === 'verification'">
-			<JHeader tabbar="/pages/user/user" width="50" height="50" title="订单核销"></JHeader>
+			<JHeader tabbar="/" width="50" height="50" title="订单核销"></JHeader>
 			<view v-if="orderInfo && orderInfo.orderId" style="margin-top: 40upx;">
 				<ATFOrderInfo :data="orderInfo"></ATFOrderInfo>
 			</view>
@@ -147,7 +147,7 @@ export default {
 			uni.removeStorageSync(T_NEW_BIND_CODE)
 			uni.removeStorageSync(T_NEW_BIND_ID)
 			if (this.type === 'nothing') {
-				this.$switchTab('/pages/user/user')
+				this.$switchTab('/')
 			} else if (this.type === 'bindingSalesCustomer') { // 绑定分销关系,salesId分销员ID
 				const shareType = Number(this.code.split('~')[0])
 				const shopId = this.code.split('~')[1]
@@ -166,7 +166,7 @@ export default {
 						}, 2000)
 					})
 					.catch((e) => {
-						setTimeout(() => { this.$switchTab('/pages/user/user') }, 2000)
+						setTimeout(() => { this.$switchTab('/') }, 2000)
 					})
 			} else if (this.type === 'verification') {
 				this.orderId = this.code.split('~')[0]
@@ -179,7 +179,7 @@ export default {
 					this.orderInfo = data
 				})
 			} else if (this.type === 'invitation') {
-				setTimeout(() => { this.$switchTab('/pages/user/user') }, 1000)
+				setTimeout(() => { this.$switchTab('/') }, 1000)
 			} else if (this.type === 'toAnotherTFSettle') {
 				const storageKeyToken = getStorageKeyToken()
 				if (storageKeyToken) {
@@ -190,15 +190,15 @@ export default {
 			} else if (this.type === 'bindingUser') {
 				bindPlatformRelationshipCodeApi({ code: this.code })
 					.then((res) => { this.$showToast('绑定成功', 'success') })
-					.finally((e) => { setTimeout(() => { this.$switchTab('/pages/user/user') }, 2000) })
+					.finally((e) => { setTimeout(() => { this.$switchTab('/') }, 2000) })
 			} else if (this.type === 'bindingFranchisee') {
 				bindPlatformRelationshipShopApi({ code: this.code })
 					.then((res) => { this.$showToast('绑定成功', 'success') })
-					.finally((e) => { setTimeout(() => { this.$switchTab('/pages/user/user') }, 2000) })
+					.finally((e) => { setTimeout(() => { this.$switchTab('/') }, 2000) })
 			} else if (this.type === 'bindingShop') {
 				bindPlatformInfoCodeBindingApi({ phone: this.code, type: 1 })
 					.then((res) => { this.$showToast('绑定成功', 'success') })
-					.finally((e) => { setTimeout(() => { this.$switchTab('/pages/user/user') }, 2000) })
+					.finally((e) => { setTimeout(() => { this.$switchTab('/') }, 2000) })
 			} else if (this.type === 'bindingTeamMembers') { // 这里指团长和合伙人,其它还有会员、股东
 				const storageKeyToken = getStorageKeyToken()
 				if (storageKeyToken) {
@@ -210,13 +210,13 @@ export default {
 				checkBindApi({ userId: this.userId })
 					.then(() => {
 						this.$showToast('已存在上级绑定')
-						setTimeout(() => { this.$switchTab('/pages/user/user') }, 2000)
+						setTimeout(() => { this.$switchTab('/') }, 2000)
 					})
 					.catch(() => {
 						uni.hideToast()
 						bindLastUserApi({ userId: this.userId, userCode: this.code })
 							.then((res) => { this.$showToast('绑定成功', 'success') })
-							.finally((e) => { setTimeout(() => { this.$switchTab('/pages/user/user') }, 2000) })
+							.finally((e) => { setTimeout(() => { this.$switchTab('/') }, 2000) })
 					})
 			} else if (this.type === 'bindCommunityService') { // 旧系统
 				const serverTypeId = this.code.split('~')[0]
@@ -252,7 +252,7 @@ export default {
 						}
 					})
 					.catch((e) => { this.$showToast('操作失败,请重试', 'error') })
-					.finally((e) => { setTimeout(() => { this.$switchTab('/pages/user/user') }, 2000) })
+					.finally((e) => { setTimeout(() => { this.$switchTab('/') }, 2000) })
 			} else if (this.type === 'bindActivityUser') { // 旧系统 // 活动码,399活动绑定
 				const campaignsType = this.code.split('-')[0] * 1
 				const bindActivityId = this.code.split('-')[1]
@@ -264,7 +264,7 @@ export default {
 		handleVerification() {
 			updateSetHxCodeApi({ orderId: this.orderId, noticeId: 0 })
 				.then((res) => { this.$showToast('核销成功', 'success') })
-				.finally((e) => { setTimeout(() => { this.$switchTab('/pages/user/user') }, 2000) })
+				.finally((e) => { setTimeout(() => { this.$switchTab('/') }, 2000) })
 		}
 	}
 }

BIN
pages/login/image/bg.png


+ 274 - 0
pages/login/login copy.vue

@@ -0,0 +1,274 @@
+<template>
+	<view class="containers" style="min-height: 100vh;">
+		<view class="header" style="width: 100vw;height: 122rpx">
+			<image class="backFn" src="../../static/images/common/back.png" @click="$switchTab('/')"></image>
+		</view>
+		<view class="logoText">
+			<view class="texts" style="font-weight: bold;">
+				<view class="txt">欢迎来到</view>
+				<view class="txt">团蜂材料网</view>
+			</view>
+			<view class="" style="margin-top: 48rpx;margin-left: -6rpx;">
+				<text class="appTitle">年轻的材料生活元宇宙</text>
+			</view>
+		</view>
+		<view class="login-form">
+			<tui-form ref="refLoginForm" :show-message="false">
+				<view>
+					<tui-input
+						v-model="loginQuery.phone" label="+ 86" type="number" padding="26upx 20upx 26upx 0"
+						placeholder-style="color: #f3c1c4;font-size: 32upx;" label-color="#ffffff" border-color="#EA5B1D"
+						placeholder="请输入手机号码" background-color="transparent" :border-top="false" color="#ffffff"
+						style="border-bottom: 2upx solid #ffffff;"
+					></tui-input>
+				</view>
+				<view v-if="loginType === 'password'">
+					<tui-input
+						v-model="loginQuery.password" type="password" padding="26upx 20upx 26upx 0"
+						placeholder-style="color: #f3c1c4;font-size: 32upx;" background-color="transparent" :border-top="false"
+						border-color="#EA5B1D" label-color="#ffffff" placeholder="请输入密码" color="#ffffff"
+						style="border-bottom: 2upx solid #ffffff;"
+					>
+					</tui-input>
+				</view>
+				<view v-if="loginType === 'verificationCode'">
+					<tui-input
+						v-model="loginQuery.verificationCode" padding="26upx 20upx 26upx 0"
+						placeholder-style="color: #f3c1c4;font-size: 32upx;" background-color="transparent" :border-top="false"
+						border-color="#EA5B1D" label-color="#ffffff" placeholder="请输入验证码" color="#ffffff"
+						style="border-bottom: 2upx solid #ffffff;"
+					>
+						<template #right>
+							<tui-countdown-verify
+								v-if="loginType === 'verificationCode'" ref="refLoginVerify" width="188upx" height="48upx"
+								border-width="0" text="获取验证码"
+								:size="30" color="#dddddd" @send="handleSendVerify"
+							></tui-countdown-verify>
+						</template>
+					</tui-input>
+				</view>
+			</tui-form>
+		</view>
+		<view style="display: flex;flex-direction: column;justify-content: center;align-items: center;margin-top: 80upx;">
+			<view style="text-align: center;">
+				<tui-button
+					type="white" width="640rpx" height="82rpx" :size="38"
+					margin="40upx 0 0" shape="circle"
+					style="font-weight: bold;color: #ea5b1d!important;" @click="handleLogin"
+				>
+					登录
+				</tui-button>
+			</view>
+		</view>
+		<view style="margin-top: 120upx;display: flex;flex-direction: column;align-items: center;">
+			<view style="display: flex;justify-content: space-around;white-space: nowrap;">
+				<view style="display: flex;flex-direction: column;align-items: center;">
+					<view style="width: fit-content;padding: 14upx;border: 1upx solid #ffffff;border-radius: 48upx;">
+						<tui-icon name="friendadd-fill" color="#ffffff" :size="22" @click="go('/pages/login/register')"></tui-icon>
+					</view>
+					<view style="margin-top: 12upx;font-size: 26upx;color: #ffffff;">密码注册</view>
+				</view>
+				<view
+					v-if="!(loginType === 'password')"
+					style="display: flex;flex-direction: column;align-items: center;padding-left: 48upx;"
+				>
+					<view style="width: fit-content;padding: 14upx;border: 1upx solid #ffffff;border-radius: 48upx;">
+						<tui-icon name="pwd" color="#ffffff" :size="22" @click="loginType = 'password'"></tui-icon>
+					</view>
+					<view style="margin-top: 12upx;font-size: 26upx;color: #ffffff;">密码登录</view>
+				</view>
+				<view
+					v-if="!(loginType === 'verificationCode')"
+					style="display: flex;flex-direction: column;align-items: center;padding-left: 48upx;"
+				>
+					<view style="width: fit-content;padding: 14upx;border: 1upx solid #ffffff;border-radius: 48upx;">
+						<tui-icon name="mobile" color="#ffffff" :size="22" @click="loginType = 'verificationCode'"></tui-icon>
+					</view>
+					<view style="margin-top: 12upx;font-size: 26upx;color: #ffffff;">短信登录</view>
+				</view>
+				<!-- #ifdef MP-ALIPAY -->
+				<view
+					v-if="!(loginType === 'verificationCode')"
+					style="display: flex;flex-direction: column;align-items: center;padding-left: 48upx;"
+				>
+					<view style="width: fit-content;padding: 14upx;border: 1upx solid #ffffff;border-radius: 48upx;">
+						<tui-icon name="mobile" color="#ffffff" :size="22" @click="handleAliPayLogin"></tui-icon>
+					</view>
+					<view style="margin-top: 12upx;font-size: 26upx;color: #ffffff;">支付宝登录</view>
+				</view>
+				<!-- #endif -->
+				<view
+					v-if="($store.state.app.terminal === 3) || ($store.state.app.terminal === 2)"
+					style="display: flex;flex-direction: column;align-items: center;padding-left: 48upx;"
+				>
+					<view style="width: fit-content;padding: 14upx;border: 1upx solid #ffffff;border-radius: 48upx;">
+						<tui-icon name="wechat" color="#ffffff" :size="22" @click="handleWXLogin"></tui-icon>
+					</view>
+					<view style="margin-top: 12upx;font-size: 26upx;color: #ffffff;">微信登录</view>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+import { T_REDIRECT_TYPE, T_STORAGE_KEY } from '../../constant'
+import { getVerifyCodeApi } from '../../api/anotherTFInterface'
+import { CHANGE_IS_IN_MINIPROGRAM } from '../../store/modules/type'
+import { getUrlCode } from '../../utils'
+
+export default {
+	name: 'Login',
+	data() {
+		return {
+			loginType: 'verificationCode', // password,verificationCode
+			loginQuery: {
+				phone: '',
+				verificationCode: '',
+				password: ''
+			}
+		}
+	},
+	onLoad(options) {
+		if (options.to) uni.setStorageSync(T_REDIRECT_TYPE, options.to)
+		if (options.miniProgram) {
+			this.$store.commit(`app/${CHANGE_IS_IN_MINIPROGRAM}`, !!options.miniProgram)
+		}
+	},
+	onShow() {
+		const userInfo = uni.getStorageSync(T_STORAGE_KEY)
+		if (userInfo && userInfo.token) {
+			uni.switchTab({
+				url: '/'
+			})
+		} else if (this.$store.state.app.terminal === 3) {
+			const code = getUrlCode().code
+			if (code) this.handleWXLogin()
+		}
+	},
+	methods: {
+		// 获取验证码
+		handleSendVerify() {
+			if (!this.loginQuery.phone) {
+				this.$refs.refLoginVerify.reset()
+				return this.$showToast('请填写手机号')
+			}
+			if (!/^1[3-9]\d{9}$/.test(this.loginQuery.phone)) {
+				this.$refs.refLoginVerify.reset()
+				return this.$showToast('请输入正确的手机号')
+			}
+			getVerifyCodeApi({ phone: this.loginQuery.phone })
+				.then((res) => {
+					this.$refs.refLoginVerify.success()
+					this.$showToast('发送成功,请注意查看手机短信')
+				})
+				.catch(() => {
+					this.$refs.refLoginVerify.reset()
+				})
+		},
+		// 点击登录
+		handleLogin() {
+			const validateRules = [ {
+				name: 'phone',
+				rule: ['required', 'isMobile'],
+				msg: ['请输入手机号', '请输入正确的手机号']
+			} ]
+			let loginFilterQuery
+			if (this.loginType === 'verificationCode') {
+				validateRules.push({
+					name: 'verificationCode',
+					rule: ['required', 'isNum'],
+					msg: [ '请输入验证码' ]
+				})
+				loginFilterQuery = {
+					phone: this.loginQuery.phone,
+					verificationCode: this.loginQuery.verificationCode
+				}
+			} else if (this.loginType === 'password') {
+				validateRules.push({
+					name: 'password',
+					rule: ['required', 'isEnAndNo'],
+					msg: ['请输入密码', '密码为8~20位英文和数字组合']
+				})
+				loginFilterQuery = {
+					phone: this.loginQuery.phone,
+					password: this.loginQuery.password
+				}
+			}
+			this.$refs.refLoginForm
+				.validate(this.loginQuery, validateRules)
+				.then(() => {
+					this.$store.dispatch('auth/phoneLoginRegisterAction', {
+						isAfter: true,
+						loginData: {
+							type: 2, // 1注册,2登录
+							...loginFilterQuery
+						}
+					})
+				})
+				.catch((e) => {
+					this.$showToast(JSON.stringify(e.errorMsg))
+				})
+		},
+		async handleAliPayLogin() {
+			await this.$store.dispatch('auth/aliPayLoginAction', { isAfter: true })
+		},
+		async handleWXLogin() {
+			await this.$store.dispatch('auth/wxLoginAction', { isAfter: true })
+		}
+	}
+}
+</script>
+
+<style lang="less" scoped>
+.containers {
+	display: flex;
+	flex-direction: column;
+	position: relative;
+	box-sizing: border-box;
+	padding-bottom: 66rpx;
+	background-color: #EA5B1D;
+
+	.backFn {
+		padding: 30rpx 30rpx;
+		width: 62rpx;
+		height: 62rpx;
+	}
+
+	.logoText {
+		width: 100vw;
+		height: 280rpx;
+		box-sizing: border-box;
+		padding: 30rpx 0 0 56rpx;
+		color: #ffffff;
+		clear: both;
+
+		.texts {
+
+			/* font-style: oblique; */
+			.txt {
+				transform: skewX(-6deg);
+			}
+
+			line-height: 80rpx;
+			font-size: 74rpx;
+			letter-spacing: 6rpx;
+			-webkit-box-reflect: below 2px -webkit-linear-gradient(top, rgba(250, 250, 250, 0), rgba(250, 250, 250, 0) 70%, rgba(255, 255, 255, 0.644));
+			box-reflect: below 0px linear-gradient(top, rgba(250, 250, 250, 0), rgba(250, 250, 250, .05) 70%, rgba(250, 250, 250, 0.3));
+		}
+	}
+
+	.appTitle {
+		font-family: 思源黑体;
+		font-size: 34rpx;
+		font-weight: normal;
+		line-height: 42rpx;
+		color: rgba(255, 255, 255, 0.914);
+	}
+
+	.login-form {
+		margin-top: 20upx;
+		padding: 146rpx 48rpx 10rpx 48rpx;
+	}
+}
+</style>

+ 154 - 152
pages/login/login.vue

@@ -1,114 +1,53 @@
 <template>
-	<view class="containers" style="min-height: 100vh;">
-		<view class="header" style="width: 100vw;height: 122rpx">
-			<image class="backFn" src="../../static/images/common/back.png" @click="$switchTab('/pages/another-index/index')"></image>
-		</view>
-		<view class="logoText">
-			<view class="texts" style="font-weight: bold;">
-				<view class="txt">欢迎来到</view>
-				<view class="txt">团蜂材料网</view>
-			</view>
-			<view class="" style="margin-top: 48rpx;margin-left: -6rpx;">
-				<text class="appTitle">年轻的材料生活元宇宙</text>
-			</view>
-		</view>
-		<view class="login-form">
-			<tui-form ref="refLoginForm" :show-message="false">
-				<view>
-					<tui-input
-						v-model="loginQuery.phone" label="+ 86" type="number" padding="26upx 20upx 26upx 0"
-						placeholder-style="color: #f3c1c4;font-size: 32upx;" label-color="#ffffff" border-color="#EA5B1D"
-						placeholder="请输入手机号码" background-color="transparent" :border-top="false" color="#ffffff"
-						style="border-bottom: 2upx solid #ffffff;"
-					></tui-input>
-				</view>
-				<view v-if="loginType === 'password'">
-					<tui-input
-						v-model="loginQuery.password" type="password" padding="26upx 20upx 26upx 0"
-						placeholder-style="color: #f3c1c4;font-size: 32upx;" background-color="transparent" :border-top="false"
-						border-color="#EA5B1D" label-color="#ffffff" placeholder="请输入密码" color="#ffffff"
-						style="border-bottom: 2upx solid #ffffff;"
+	<div class="login">
+		<div class="contain">
+			<div class="header">
+				<div class="back">
+					<tui-icon name="arrowleft" color="#000" :size="30" @click="handleBack"></tui-icon>
+				</div>
+				<div class="bg-box">
+					<img src="./image/bg.png" alt="">
+				</div>
+				<div class="title-box">
+					<h2>您好!</h2>
+					<p>欢迎进入全球家具原材料交易平台</p>
+				</div>
+			</div>
+			<div class="main">
+				<div class="ipt">
+					<tui-form ref="refLoginForm" :show-message="false">
+						<tui-input v-model="loginQuery.phone" placeholder="请输入手机号"></tui-input>
+						<div class="code-box">
+							<tui-input v-model="loginQuery.verificationCode" :maxlength="4" placeholder="请输入验证码"></tui-input>
+							<div class="code">
+								<tui-countdown-verify
+									ref="refLoginVerify" color="#EF530E" border-color="transparent"
+									:size="28" :seconds="60" @send="handleSendVerify"
+								></tui-countdown-verify>
+							</div>
+						</div>
+						<div class="text">
+							<p>还没有账号?</p>
+							<span @click="go('/pages/register/index')">立即注册</span>
+						</div>
+					</tui-form>
+				</div>
+				<div class="btn-list">
+					<tui-form-button color="#fff" background="#EF530E" @click="handleLogin">验证码一键登陆</tui-form-button>
+					<tui-form-button
+						style="display: none;" color="#EF530E"
+						background="rgba(234, 91, 29, 0.1)"
 					>
-					</tui-input>
-				</view>
-				<view v-if="loginType === 'verificationCode'">
-					<tui-input
-						v-model="loginQuery.verificationCode" padding="26upx 20upx 26upx 0"
-						placeholder-style="color: #f3c1c4;font-size: 32upx;" background-color="transparent" :border-top="false"
-						border-color="#EA5B1D" label-color="#ffffff" placeholder="请输入验证码" color="#ffffff"
-						style="border-bottom: 2upx solid #ffffff;"
-					>
-						<template #right>
-							<tui-countdown-verify
-								v-if="loginType === 'verificationCode'" ref="refLoginVerify" width="188upx" height="48upx"
-								border-width="0" text="获取验证码"
-								:size="30" color="#dddddd" @send="handleSendVerify"
-							></tui-countdown-verify>
-						</template>
-					</tui-input>
-				</view>
-			</tui-form>
-		</view>
-		<view style="display: flex;flex-direction: column;justify-content: center;align-items: center;margin-top: 80upx;">
-			<view style="text-align: center;">
-				<tui-button
-					type="white" width="640rpx" height="82rpx" :size="38"
-					margin="40upx 0 0" shape="circle"
-					style="font-weight: bold;color: #ea5b1d!important;" @click="handleLogin"
-				>
-					登录
-				</tui-button>
-			</view>
-		</view>
-		<view style="margin-top: 120upx;display: flex;flex-direction: column;align-items: center;">
-			<view style="display: flex;justify-content: space-around;white-space: nowrap;">
-				<view style="display: flex;flex-direction: column;align-items: center;">
-					<view style="width: fit-content;padding: 14upx;border: 1upx solid #ffffff;border-radius: 48upx;">
-						<tui-icon name="friendadd-fill" color="#ffffff" :size="22" @click="go('/pages/login/register')"></tui-icon>
-					</view>
-					<view style="margin-top: 12upx;font-size: 26upx;color: #ffffff;">密码注册</view>
-				</view>
-				<view
-					v-if="!(loginType === 'password')"
-					style="display: flex;flex-direction: column;align-items: center;padding-left: 48upx;"
-				>
-					<view style="width: fit-content;padding: 14upx;border: 1upx solid #ffffff;border-radius: 48upx;">
-						<tui-icon name="pwd" color="#ffffff" :size="22" @click="loginType = 'password'"></tui-icon>
-					</view>
-					<view style="margin-top: 12upx;font-size: 26upx;color: #ffffff;">密码登录</view>
-				</view>
-				<view
-					v-if="!(loginType === 'verificationCode')"
-					style="display: flex;flex-direction: column;align-items: center;padding-left: 48upx;"
-				>
-					<view style="width: fit-content;padding: 14upx;border: 1upx solid #ffffff;border-radius: 48upx;">
-						<tui-icon name="mobile" color="#ffffff" :size="22" @click="loginType = 'verificationCode'"></tui-icon>
-					</view>
-					<view style="margin-top: 12upx;font-size: 26upx;color: #ffffff;">短信登录</view>
-				</view>
-				<!-- #ifdef MP-ALIPAY -->
-				<view
-					v-if="!(loginType === 'verificationCode')"
-					style="display: flex;flex-direction: column;align-items: center;padding-left: 48upx;"
-				>
-					<view style="width: fit-content;padding: 14upx;border: 1upx solid #ffffff;border-radius: 48upx;">
-						<tui-icon name="mobile" color="#ffffff" :size="22" @click="handleAliPayLogin"></tui-icon>
-					</view>
-					<view style="margin-top: 12upx;font-size: 26upx;color: #ffffff;">支付宝登录</view>
-				</view>
-				<!-- #endif -->
-				<view
-					v-if="($store.state.app.terminal === 3) || ($store.state.app.terminal === 2)"
-					style="display: flex;flex-direction: column;align-items: center;padding-left: 48upx;"
-				>
-					<view style="width: fit-content;padding: 14upx;border: 1upx solid #ffffff;border-radius: 48upx;">
-						<tui-icon name="wechat" color="#ffffff" :size="22" @click="handleWXLogin"></tui-icon>
-					</view>
-					<view style="margin-top: 12upx;font-size: 26upx;color: #ffffff;">微信登录</view>
-				</view>
-			</view>
-		</view>
-	</view>
+						密码登陆
+					</tui-form-button>
+				</div>
+			</div>
+		</div>
+		<footer>
+			<p>登录即代表你已阅读并同意</p>
+			<span>《用户服务协议》</span>
+		</footer>
+	</div>
 </template>
 
 <script>
@@ -147,6 +86,9 @@ export default {
 		}
 	},
 	methods: {
+		handleBack() {
+			uni.navigateBack()
+		},
 		// 获取验证码
 		handleSendVerify() {
 			if (!this.loginQuery.phone) {
@@ -220,55 +162,115 @@ export default {
 }
 </script>
 
-<style lang="less" scoped>
-.containers {
-	display: flex;
-	flex-direction: column;
-	position: relative;
-	box-sizing: border-box;
-	padding-bottom: 66rpx;
-	background-color: #EA5B1D;
+<style lang="scss" scoped>
+.login {
+    .contain {
+        width: 100%;
+        position: relative;
 
-	.backFn {
-		padding: 30rpx 30rpx;
-		width: 62rpx;
-		height: 62rpx;
-	}
+        .header {
+            position: relative;
 
-	.logoText {
-		width: 100vw;
-		height: 280rpx;
-		box-sizing: border-box;
-		padding: 30rpx 0 0 56rpx;
-		color: #ffffff;
-		clear: both;
+            .back {
+                position: absolute;
+                top: 80rpx;
+                left: 35rpx;
+            }
 
-		.texts {
+            .bg-box {
+                width: 100%;
+                height: 636rpx;
 
-			/* font-style: oblique; */
-			.txt {
-				transform: skewX(-6deg);
-			}
+                img {
+                    width: 100%;
+                    height: 100%;
+                    display: block;
+                }
+            }
 
-			line-height: 80rpx;
-			font-size: 74rpx;
-			letter-spacing: 6rpx;
-			-webkit-box-reflect: below 2px -webkit-linear-gradient(top, rgba(250, 250, 250, 0), rgba(250, 250, 250, 0) 70%, rgba(255, 255, 255, 0.644));
-			box-reflect: below 0px linear-gradient(top, rgba(250, 250, 250, 0), rgba(250, 250, 250, .05) 70%, rgba(250, 250, 250, 0.3));
-		}
-	}
+            .title-box {
+                position: absolute;
+                left: 35rpx;
+                top: 280rpx;
 
-	.appTitle {
-		font-family: 思源黑体;
-		font-size: 34rpx;
-		font-weight: normal;
-		line-height: 42rpx;
-		color: rgba(255, 255, 255, 0.914);
-	}
+                h2 {
+                    font-size: 42rpx;
+                    font-weight: bold;
+                }
 
-	.login-form {
-		margin-top: 20upx;
-		padding: 146rpx 48rpx 10rpx 48rpx;
-	}
+                p {
+                    font-size: 32rpx;
+                    margin-top: 15rpx;
+                }
+            }
+        }
+
+        .main {
+            width: 100%;
+            height: 200rpx;
+            position: absolute;
+            left: 0;
+            top: 550rpx;
+            border-radius: 40rpx 40rpx 0 0;
+            background-color: rgb(255, 255, 255);
+            padding: 60rpx 40rpx;
+            box-sizing: border-box;
+
+            .ipt {
+
+                .code-box {
+                    position: relative;
+
+                    .code {
+                        position: absolute;
+                        right: 0;
+                        top: 50%;
+                        transform: translateY(-50%);
+                    }
+                }
+
+                .text {
+                    margin-top: 30rpx;
+                    display: flex;
+                    font-size: 24rpx;
+                    margin-left: 30rpx;
+
+                    p {
+                        color: #646466;
+                    }
+
+                    span {
+                        color: #397BFF;
+                    }
+                }
+            }
+
+            .btn-list {
+                margin-top: 120rpx;
+                display: flex;
+                flex-direction: column;
+                gap: 50rpx;
+            }
+        }
+    }
+
+    footer {
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        width: 100%;
+        position: fixed;
+        left: 0;
+        bottom: 100rpx;
+        font-size: 24rpx;
+
+        p {
+            color: #888889;
+        }
+
+        span {
+            font-weight: 500;
+        }
+    }
 }
 </style>

+ 1 - 1
pages/login/register.vue

@@ -75,7 +75,7 @@
 				</view>
 				<button
 					style="width: 484rpx;height: 80rpx;border-radius: 8rpx;background: #EF530E;color: #fff;line-height: 80rpx;margin-top: 80rpx;"
-					@click="$switchTab('/pages/another-index/index')"
+					@click="$switchTab('/')"
 				>
 					立即跳转
 				</button>

+ 232 - 0
pages/new-tabbar/Hall/index.scss

@@ -0,0 +1,232 @@
+page {
+    background-color: #f5f5f5;
+    overflow-x: hidden;
+}
+
+.hall {
+    header {
+        padding: 30rpx 30rpx 25rpx;
+        height: 500rpx;
+        background: linear-gradient(to bottom, #ffe8d1, #00000000);
+        position: absolute;
+        top: 0;
+        left: 0;
+        width: 100%;
+        box-sizing: border-box;
+        .title {
+            font-size: 42rpx;
+            font-weight: 700;
+        }
+
+        .info {
+            display: flex;
+            gap: 30rpx;
+            align-items: end;
+            width: 100%;
+            box-sizing: border-box;
+            margin-top: 40rpx;
+            justify-content: space-between;
+
+            .scroll-view_H {
+                white-space: nowrap;
+                width: 75%;
+                display: flex;
+                padding-left: 15rpx;
+                color: rgba(0, 0, 0, 0.5);
+                font-size: 28rpx;
+
+                .scroll-view-item_H {
+                    display: inline-block;
+                    margin-left: 25rpx;
+
+                    &:first-of-type {
+                        margin-left: 0;
+                    }
+                }
+
+                .active {
+                    color: #000;
+                    font-size: 36rpx;
+                    font-weight: 550;
+                }
+            }
+
+            .release {
+                display: flex;
+                align-items: center;
+                gap: 10rpx;
+                color: #000;
+                font-size: 28rpx;
+
+                span {
+                    margin-top: 6rpx;
+                }
+            }
+        }
+    }
+
+    main {
+        width: 100%;
+        box-sizing: border-box;
+        position: absolute;
+        top: 220rpx;
+        left: 0;
+        padding: 0 30rpx;
+        padding-bottom: 150rpx;
+        overflow-x: hidden;
+
+        .communicate-list {
+            display: flex;
+            flex-direction: column;
+            gap: 25rpx;
+            .communicate-item {
+                padding: 30rpx;
+                background-color: #fff;
+                border-radius: 20rpx;
+                box-sizing: border-box;
+
+                .release {
+                    display: flex;
+                    align-items: center;
+                    justify-content: space-between;
+
+                    .prople {
+                        display: flex;
+                        align-items: center;
+                        height: 50rpx;
+
+                        .img-box {
+                            width: 50rpx;
+                            height: 100%;
+                            margin-right: 10rpx;
+                        }
+
+                        .text {
+                            display: flex;
+                            align-items: center;
+                            gap: 10rpx;
+
+                            span {
+                                margin-bottom: 3rpx;
+                            }
+
+                            p {
+                                width: 80rpx;
+                                border-radius: 30rpx;
+                                background-color: #FFF2EC;
+                                color: #EF530E;
+                                text-align: center;
+                                line-height: 50rpx;
+                                font-size: 24rpx;
+                            }
+                            .business{
+                                background-color: rgba(76, 148, 249, 0.1);
+                                color: #4C94F9;
+                            }
+                        }
+                    }
+
+                    .time {
+                        color: #9E9E9E;
+                        font-size: 22rpx;
+                    }
+                }
+                .ask{
+                    display: flex;
+                    gap: 15rpx;
+                    align-items: center;
+                    position: relative;
+                    margin-top: 30rpx;
+                    .ask-box{
+                        background-color: #EF530E;
+                        color: #fff;
+                        padding: 3rpx 8rpx 4rpx;
+                        font-size: 32rpx;
+                        font-weight: 600;
+                        border-radius: 7rpx;
+                    }
+                    .ask-text{
+                        width: 420rpx;
+                        overflow: hidden;
+                        white-space: nowrap;
+                        text-overflow: ellipsis;
+                        font-size: 32rpx;
+                        color: #000;
+                        font-weight: 600;
+                    }
+                    .ask-state{
+                        color: #ffffff;
+                        position: absolute;
+                        top: 0;
+                        right: -30rpx;
+                        width: 80rpx;
+                        height: 60rpx;
+                        line-height: 60rpx;
+                        background-color: #FD5E53;
+                        span{
+                            position: relative;
+                            z-index: 2;
+                        }
+                        &::after{
+                            content: "";
+                            width: 60rpx;
+                            height: 60rpx;
+                            border-radius: 50%;
+                            position: absolute;
+                            left: -30rpx;
+                            z-index: 1;
+                            top: 49%;
+                            transform: translateY(-49%);
+                            background-color: inherit;
+                        }
+                    }
+                    .lose{
+                        background-color: #b8b8b8;
+                    }
+                    .sell{
+                        background-color: #0BC5B6;
+                    }
+                }
+                .answer{
+                    margin-top: 25rpx;
+                    height: 120rpx;
+                    background-color: #FFF9F0;
+                    display: flex;
+                    flex-direction: column;
+                    justify-content: center;
+                    padding: 10rpx 30rpx ;
+                    box-sizing: border-box;
+                    gap: 20rpx;
+                    p{
+                        font-size: 24rpx;
+                        color: #9E9E9E;
+                        width: 300rpx;
+                        overflow: hidden;
+                        text-overflow: ellipsis;
+                        white-space: nowrap;
+                        span{
+                            color: #EF530E;
+                        }
+                    }
+                }
+                .mation{
+                    display: flex;
+                    justify-content: flex-end;
+                    margin-top: 20rpx;
+                    .list{
+                        display: flex;
+                        align-items: center;
+                        gap: 20rpx;
+                        color: rgba(0, 0, 0, 0.5);
+                        font-size: 22rpx;
+                        .item{
+                            display: flex;
+                            align-items: center;
+                            gap: 10rpx;
+                        }
+                    }
+                }
+            }
+        }
+    }
+}

+ 94 - 0
pages/new-tabbar/Hall/index.vue

@@ -0,0 +1,94 @@
+<template>
+    <div class="hall">
+        <header>
+            <div class="title">信息大厅</div>
+            <div class="info">
+                <scroll-view class="scroll-view_H" scroll-x="true" :scroll-left="scrollLeft" scroll-with-animation>
+                    <view class="scroll-view-item_H" v-for="(item, index) in infoList" :key="index"
+                        :class="index == idx ? 'active' : ''" @click="infoClick(index)">{{ item }}</view>
+                </scroll-view>
+                <div class="release">
+                    <span><tui-icon name="add-fill" size="18" color="#000"></tui-icon></span>
+                    <p>发布</p>
+                </div>
+            </div>
+        </header>
+        <main>
+            <div class="communicate-list">
+                <div class="communicate-item" v-for="(item,index) in 10" :key="index">
+                    <div class="release">
+                        <div class="prople">
+                            <div class="img-box">
+                                <tui-lazyload-img width="100%" height="100%" radius="50%"
+                                    src="../../../static/new-images/hall/head.png"></tui-lazyload-img>
+                            </div>
+                            <div class="text">
+                                <span>发布人</span>
+                                <span>陈先生</span>
+                                <p>会员</p>
+                            </div>
+                        </div>
+                        <div class="time">
+                            <span>佛山 01 - 22 9:00</span>
+                        </div>
+                    </div>
+                    <div class="ask">
+                        <div class="ask-box">
+                            问
+                        </div>
+                        <div class="ask-text">需采购大批量皮革真实有效</div>
+                        <div class="ask-state">
+                            <span>急需</span>
+                        </div>
+                    </div>
+                    <div class="answer">
+                        <p>
+                            代先生
+                            <span>的回答</span>
+                        </p>
+                        <p>需要多少,我这可提供需要多少,我这可提供需要多少,我这可提供需要多少,我这可提供需要多少,我这可提供需要多少,我这可提供...</p>
+                    </div>
+                    <div class="mation">
+                        <div class="list">
+                            <div class="item">
+                                <span>1235+</span>
+                                <p><tui-icon name="seen"  size="20"></tui-icon></p>
+                            </div>
+                            <div class="item">
+                                <span>123</span>
+                                <p><tui-icon name="community"  size="20"></tui-icon></p>
+                            </div>
+                            <div class="item">
+                                <span>123</span>
+                                <p><tui-icon name="share"  size="20"></tui-icon></p>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </main>
+    </div>
+</template>
+
+<script>
+export default {
+    data() {
+        return {
+            scrollLeft: 0,
+            //  供应信息激活态
+            idx: 0,
+            infoList: ['供应信息', '采购信息', '库存信息', '店铺信息', '人才招聘', '行业新闻', '其他信息']
+        }
+    },
+    methods: {
+        infoClick(val) {
+            this.idx = val
+            this.scrollLeft = val * 40
+        }
+    }
+}
+</script>
+
+<style lang="scss" scoped>
+@import "./index.scss";
+</style>

+ 129 - 0
pages/new-tabbar/components/Nav.vue

@@ -0,0 +1,129 @@
+<template>
+    <div class="nav">
+        <header>
+            <div class="title">
+                <slot name="title"></slot>
+            </div>
+            <div class="search">
+                <div class="search-box">
+                    <div class="text">
+                        <span>龙江镇</span>
+                        <p><tui-icon color="#000" name="turningdown"></tui-icon></p>
+                    </div>
+                    <tui-input placeholder="团蜂科技" :size="28" padding="0rpx 0rpx" :borderBottom="false"></tui-input>
+                    <div class="search-icon">
+                        <tui-icon name="search" color="#000" :size="24"></tui-icon>
+                    </div>
+                </div>
+                <div class="classification" @click="goClass">
+                    <img src="../../../static/new-images/home/classification.png" alt="">
+                    <span>分类</span>
+                </div>
+            </div>
+        </header>
+    </div>
+</template>
+
+<script>
+export default {
+    methods:{
+        goClass(){
+            uni.navigateTo({
+                url:"/pages/classification/index"
+            })
+        }
+    }
+}
+</script>
+
+<style lang="scss" scoped>
+header {
+    padding: 30rpx 30rpx 25rpx;
+    background-color: #fff;
+
+    .title {
+        padding: 15rpx 0;
+        display: flex;
+        align-items: flex-end;
+
+        p {
+            font-size: 42rpx;
+            font-weight: 700;
+        }
+
+        span {
+            margin-left: 10rpx;
+            font-size: 28rpx;
+            color: #000;
+            font-weight: 500;
+            margin-bottom: 5rpx;
+        }
+    }
+
+    .search {
+        height: 72rpx;
+        margin-top: 40rpx;
+        box-sizing: border-box;
+        position: relative;
+        display: flex;
+        gap: 30rpx;
+
+        .search-box {
+            border: 2rpx solid #000;
+            border-radius: 50rpx;
+            padding-left: 30rpx;
+            display: flex;
+            align-items: center;
+            position: relative;
+            width: 580rpx;
+
+            .text {
+                display: flex;
+                gap: 10rpx;
+                align-items: center;
+                margin-right: 5rpx;
+                span{
+                    white-space: nowrap;
+                }
+                p {
+                    position: relative;
+                    &::after {
+                        content: "";
+                        width: 1px;
+                        height: 60%;
+                        background-color: #F5F5F5;
+                        position: absolute;
+                        right: 5rpx;
+                        top: 50%;
+                        transform: translateY(-50%);
+                    }
+                }
+            }
+
+            .search-icon {
+                position: absolute;
+                right: 30rpx;
+                top: 10rpx;
+            }
+        }
+
+        .classification {
+            display: flex;
+            align-items: center;
+            flex-direction: column;
+            justify-content: center;
+            font-size: 24rpx;
+            margin-top: 8rpx;
+            gap: 5rpx;
+            span{
+                white-space: nowrap;
+            }
+            img {
+                width: 36rpx;
+                height: 36rpx;
+                display: block;
+            }
+        }
+    }
+}
+</style>

+ 271 - 0
pages/new-tabbar/findServices/index.vue

@@ -0,0 +1,271 @@
+<template>
+    <div class="findServices">
+        <header>
+            <div class="title">找服务</div>
+            <div class="service-list">
+                <div class="service-item">
+                    <img src="../../../static/new-images/findServices/give_1.png" alt="">
+                    <span class="act">材料检测</span>
+                </div>
+                <div class="service-item">
+                    <img src="../../../static/new-images/findServices/give_2.png" alt="">
+                    <span>跨境支付</span>
+                </div>
+                <div class="service-item">
+                    <img src="../../../static/new-images/findServices/give_3.png" alt="">
+                    <span>跨境物流</span>
+                </div>
+                <div class="service-item">
+                    <img src="../../../static/new-images/findServices/give_4.png" alt="">
+                    <span>国内物流</span>
+                </div>
+                <div class="service-item">
+                    <img src="../../../static/new-images/findServices/give_5.png" alt="">
+                    <span>金融服务</span>
+                </div>
+            </div>
+        </header>
+        <main>
+            <div class="left">
+                <scroll-view scroll-top="0" scroll-y="true" class="detection-list">
+                    <view class="detection-item" v-for="(item, index) in detectionList" :key="index"
+                        :class="index == leftIdx ? 'active' : ''" @click="leftClick(index)">{{ item }}</view>
+                </scroll-view>
+            </div>
+            <div class="right">
+                <div class="bg-box"></div>
+                <scroll-view scroll-top="0" scroll-y="true" class="institution-list">
+                    <view class="institution-item" v-for="(item, index) in 10" :key="index">
+                        <div class="img-box">
+                            <tui-lazyload-img width="100%" height="100%"
+                                :src="'../../../static/new-images/findServices/institution_1.png' || ''"></tui-lazyload-img>
+                        </div>
+                        <div class="text-box">
+                            <div class="title">天鉴检测机构</div>
+                            <ul>
+                                <li>专业鉴定</li>
+                                <li>官方机构</li>
+                            </ul>
+                            <span><tui-rate :current="5" :quantity="5" active="#FFC01F" :size="15"></tui-rate></span>
+                            <p>广东 · 佛山</p>
+                        </div>
+                        <template v-if="index < 3">
+                            <div class="top">
+                                <img :src="'../../../static/new-images/findServices/top_' + (index + 1) + '.png'" alt="">
+                            </div>
+                        </template>
+                    </view>
+                </scroll-view>
+            </div>
+        </main>
+    </div>
+</template>
+
+<script>
+export default {
+    data() {
+        return {
+            //  左边侧边栏的激活态
+            leftIdx: 0,
+            detectionList: ['棉花检测', '金属检测', '石材检测', '木材检测', '涂料检测', '甲醛检测', '皮革检测', '房屋检测', '其他检测']
+        }
+    },
+    methods: {
+        leftClick(val) {
+            this.leftIdx = val
+        }
+    }
+}
+</script>
+
+<style lang="scss" scoped>
+page {
+    background-color: #f5f5f5;
+}
+
+.findServices {
+    display: flex;
+    flex-direction: column;
+    height: 100%;
+
+    header {
+        padding: 30rpx 30rpx 25rpx;
+        background-color: #fff;
+
+        .title {
+            font-size: 42rpx;
+            font-weight: 700;
+        }
+
+        .service-list {
+            display: flex;
+            align-items: center;
+            justify-content: space-between;
+            padding: 0 20rpx;
+            box-sizing: border-box;
+            margin-top: 50rpx;
+            gap: 40rpx;
+
+            .service-item {
+                display: flex;
+                align-items: center;
+                justify-content: center;
+                flex-direction: column;
+                gap: 20rpx;
+                color: rgba(0, 0, 0, 0.5);
+                font-size: 24rpx;
+
+                img {
+                    width: 80rpx;
+                    height: 80rpx;
+                }
+
+                .act {
+                    color: #000;
+                }
+            }
+        }
+    }
+
+    main {
+        width: 100%;
+        flex: 1;
+        display: flex;
+        overflow: hidden;
+
+        .left {
+            width: 175rpx;
+            height: 100%;
+            box-sizing: border-box;
+            font-size: 26rpx;
+            color: rgba(0, 0, 0, 0.5);
+            padding-bottom: 50rpx;
+            background-color: #fff;
+
+            .detection-list {
+                white-space: nowrap;
+                height: 100%;
+                padding-bottom: 100rpx;
+
+                .detection-item {
+                    height: 65rpx;
+                    text-align: center;
+                    line-height: 65rpx;
+                    margin-top: 25rpx;
+                }
+
+                .active {
+                    color: #000000;
+                    font-weight: 600;
+                    position: relative;
+
+                    &::after {
+                        content: "";
+                        width: 8rpx;
+                        height: 70%;
+                        position: absolute;
+                        top: 50%;
+                        transform: translateY(-50%);
+                        left: 0;
+                        background-color: #EF530E;
+                    }
+                }
+            }
+        }
+
+        .right {
+            flex: 1;
+            height: 100%;
+            box-sizing: border-box;
+            position: relative;
+            padding-top: 15rpx;
+            .bg-box {
+                width: 100%;
+                position: absolute;
+                top: 0;
+                left: 0;
+                height: 500rpx;
+                background: linear-gradient(to bottom, #fde9d5, #00000000);
+            }
+
+            .institution-list {
+                white-space: nowrap;
+                height: 100%;
+                padding: 0 30rpx;
+                box-sizing: border-box;
+                padding-bottom: 20rpx;
+
+                .institution-item {
+                    display: flex;
+                    align-items: flex-start;
+                    background-color: #fff;
+                    border-radius: 20rpx;
+                    margin-top: 30rpx;
+                    padding: 20rpx;
+                    position: relative;
+
+                    .img-box {
+                        width: 175rpx;
+                        height: 175rpx;
+                        box-sizing: border-box;
+                        background-color: #fff;
+                        border-radius: 20rpx;
+                        margin-right: 20rpx;
+                    }
+
+                    .text-box {
+                        display: flex;
+                        flex-direction: column;
+                        gap: 10rpx;
+
+                        .title {
+                            font-size: 32rpx;
+                            font-weight: 600;
+                            width: 270rpx;
+                            overflow: hidden;
+                            white-space: nowrap;
+                            text-overflow: ellipsis;
+                        }
+
+                        ul {
+                            list-style: none;
+                            padding: 0;
+                            display: flex;
+                            gap: 12rpx;
+
+                            li {
+                                font-size: 20rpx;
+                                color: #EF530E;
+                                padding: 4rpx 12rpx;
+                                background-color: #FFF0DE;
+                                border-radius: 8rpx;
+                            }
+                        }
+
+                        >p {
+                            font-size: 24rpx;
+                        }
+                    }
+
+                    .top {
+                        width: 68rpx;
+                        height: 83rpx;
+                        position: absolute;
+                        top: -15rpx;
+                        left: 20rpx;
+
+                        img {
+                            width: 100%;
+                            height: 100%;
+                            display: block;
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
+
+uni-page-body {
+    height: 100%;
+}</style>

+ 327 - 0
pages/new-tabbar/findStore/index.vue

@@ -0,0 +1,327 @@
+<template>
+    <div class="findStore">
+        <Nav>
+            <template #title>
+                <p>找店铺</p>
+            </template>
+        </Nav>
+        <div class="market">
+            <div class="mark-list">
+                <scroll-view class="scroll-view_H" scroll-x="true" :scroll-left="marketScrollLeft" scroll-with-animation>
+                    <view class="scroll-view-item_H" v-for="(item, index) in marketList" :key="index"
+                        :class="index == idx ? 'active' : ''" @click="marketClick(index)">{{ item }}
+                    </view>
+                </scroll-view>
+            </div>
+            <div class="material-list">
+                <scroll-view class="scroll-view-material" scroll-x="true" :scroll-left="scrollLeft" scroll-with-animation>
+                    <view class="material-item" v-for="(item, index) in materialList" :key="index"
+                        :class="index == materIdx ? 'active' : ''" @click="materialClick(index)">{{ item }}</view>
+                </scroll-view>
+            </div>
+        </div>
+        <main>
+            <div class="shops-list">
+                <div class="shop-item" v-for="item in 10" :key="item">
+                    <div class="title">
+                        <p>佛山市顺德区正一<span>五金工具</span>有限公司</p>
+                        <div class="talk">洽谈</div>
+                    </div>
+                    <div class="info">
+                        <div class="favorable">
+                            <div class="finger">好评指数</div>
+                            <div class="star">
+                                <tui-rate :hollow="true" active="#EF530E" normal="#EF530E" :current="4" :score="0.5" :size="16" disabled></tui-rate>
+                                <p>
+                                    <span>5星工厂</span>
+                                    <tui-icon name="arrowright" size="16" color="#EF530E"></tui-icon>
+                                </p>
+                            </div>
+                        </div>
+                        <div class="brand">
+                            <div class="img-box">
+                                <img src="../../../static/new-images/findStore/icon.png" alt="">
+                                <p>品牌工厂</p>
+                            </div>
+                            <span>7年</span>
+                        </div>
+                    </div>
+                    <div class="text">
+                        <span>支持定制</span>
+                        <span>批量采购</span>
+                        <span>58%回头率</span>
+                        <p>广东佛山</p>
+                    </div>
+                    <div class="product-list">
+                        <div class="product-item" v-for="(item, index) in 6">
+                            <template v-if="index <= 2">
+                                <div class="img">
+                                    <tui-lazyload-img width="100%" height="100%" radius="20rpx"
+                                        :src="'../../../static/new-images/findStore/product.png' || ''"></tui-lazyload-img>
+                                </div>
+                                <p>兔拉拉环保型高 温中温低热54522温中温低热54522</p>
+                            </template>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </main>
+    </div>
+</template>
+
+<script>
+import Nav from "../components/Nav.vue"
+export default {
+    components: {
+        Nav
+    },
+    data() {
+        return {
+            marketList: ['亚洲材料城', '亚太木业城', '水藤木材市场', '沙龙材料市场', '亚洲材料城', '亚太木业城', '水藤木材市场', '沙龙材料市场'],
+            marketScrollLeft: 0,
+            materialList: ['皮革', '布艺材料', '塑料五金', '石材', '木材', '化工涂料', '填充材料', '辅料', '快捷服务'],
+            scrollLeft: 0,
+            idx: 0, // 材料城的激活态
+            materIdx: 0,  //  材料的激活态
+        }
+    },
+    methods: {
+        marketClick(index) {
+            this.idx = index
+            this.marketScrollLeft = index * 30
+        },
+        materialClick(index) {
+            this.materIdx = index
+            this.scrollLeft = index * 15
+        }
+    }
+}
+</script>
+
+<style lang="scss" scoped>
+page {
+    background-color: #F5F5F5;
+}
+
+.findStore {
+    .market {
+        padding: 30rpx 25rpx;
+        box-sizing: border-box;
+
+        .mark-list {
+            color: rgba(0, 0, 0, .5);
+
+            .scroll-view_H {
+                white-space: nowrap;
+                width: 100%;
+
+                .scroll-view-item_H {
+                    display: inline-block;
+                    margin-left: 40rpx;
+
+                    &:first-of-type {
+                        margin-left: 0;
+                    }
+                }
+
+                .active {
+                    font-size: 36rpx !important;
+                    color: #000;
+                    font-weight: 600;
+                }
+
+            }
+        }
+
+        .material-list {
+            color: rgba(0, 0, 0, .5);
+            white-space: nowrap;
+            width: 100%;
+            margin-top: 35rpx;
+
+            .material-item {
+                display: inline-block;
+                padding: 4rpx 8rpx;
+                background-color: #fff;
+                margin-left: 15rpx;
+                box-sizing: border-box;
+                border: 1rpx solid transparent;
+
+                &:first-of-type {
+                    margin-left: 0;
+                }
+            }
+
+            .active {
+                background-color: #FBF4F4;
+                border-color: #EF530E;
+                color: #EF530E;
+            }
+        }
+    }
+
+    main {
+        padding: 30rpx;
+
+        .shops-list {
+            display: flex;
+            flex-direction: column;
+            gap: 25rpx;
+
+            .shop-item {
+                padding: 30rpx;
+                background-color: #fff;
+                border-radius: 20rpx;
+
+                .title {
+                    display: flex;
+                    align-items: center;
+                    justify-content: space-between;
+
+                    p {
+                        font-size: 32rpx;
+                        color: #000;
+                        font-weight: 550;
+                        width: 82%;
+                        overflow: hidden;
+                        white-space: nowrap;
+                        text-overflow: ellipsis;
+
+                        span {
+                            color: #EF530E;
+                        }
+                    }
+
+                    .talk {
+                        padding: 10rpx 25rpx;
+                        background-color: #FFF2EC;
+                        color: #EF530E;
+                        font-size: 28rpx;
+                        border-radius: 50rpx;
+                    }
+                }
+
+                .info {
+                    display: flex;
+                    align-items: center;
+                    padding-top: 30rpx;
+
+                    .favorable {
+                        display: flex;
+                        align-items: center;
+                        height: 50rpx;
+
+                        .finger {
+                            height: 100%;
+                            background-color: #EF530E;
+                            padding: 0 15rpx;
+                            color: #fff;
+                            font-weight: 500;
+                            font-size: 28rpx;
+                            text-align: center;
+                            line-height: 50rpx;
+                            border-radius: 10rpx;
+                        }
+
+                        .star {
+                            display: flex;
+                            align-items: center;
+                            background-color: #FBF4F4;
+                            height: 100%;
+                            gap: 5rpx;
+                            padding-left: 10rpx;
+                            color: #EF530E;
+                            font-size: 22rpx;
+                            p {
+                                display: flex;
+                                gap: 5rpx;
+                                align-items: center;
+                            }
+                        }
+                    }
+
+                    .brand {
+                        display: flex;
+                        align-items: center;
+                        margin-left: 10rpx;
+                        gap: 10rpx;
+
+                        .img-box {
+                            display: flex;
+                            align-items: center;
+                            gap: 3rpx;
+                            font-size: 24rpx;
+                            font-weight: 600;
+
+                            img {
+                                width: 25rpx;
+                                height: 25rpx;
+                                display: block;
+                            }
+
+                            p {
+                                border-right: 1rpx solid #D8D8D8;
+                                padding-right: 10rpx;
+                            }
+                        }
+                    }
+                }
+
+                .text {
+                    margin-top: 25rpx;
+                    display: flex;
+                    gap: 15rpx;
+                    align-items: center;
+                    font-size: 24rpx;
+                    color: rgba(0, 0, 0, 0.5);
+
+                    span {
+                        position: relative;
+                        padding-right: 10rpx;
+
+                        &::after {
+                            content: "";
+                            position: absolute;
+                            right: 0;
+                            top: 50%;
+                            transform: translateY(-50%);
+                            width: 1rpx;
+                            height: 80%;
+                            background-color: #D8D8D8;
+                        }
+                    }
+                }
+
+                .product-list {
+                    display: flex;
+                    align-items: center;
+                    gap: 25rpx;
+                    margin-top: 20rpx;
+
+                    .product-item {
+                        width: 200rpx;
+
+                        .img {
+                            width: 200rpx;
+                            height: 200rpx;
+                        }
+
+                        p {
+                            margin-top: 10rpx;
+                            padding-right: 10rpx;
+                            box-sizing: border-box;
+                            font-size: 26rpx;
+                            height: 72rpx;
+                            overflow: hidden;
+                            -webkit-line-clamp: 2;
+                            text-overflow: ellipsis;
+                            display: -webkit-box;
+                            -webkit-box-orient: vertical;
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
+</style>

+ 365 - 0
pages/new-tabbar/home/index.scss

@@ -0,0 +1,365 @@
+page {
+  background-color: #f5f5f5;
+}
+
+.home {
+  padding-bottom: 100rpx;
+  .uni-margin-wrap {
+    width: 690rpx;
+    width: 100%;
+  }
+
+  main {
+    padding: 10rpx 20rpx 0;
+
+    .swiper {
+      height: 300rpx;
+      border-radius: 30rpx;
+
+      .swiper-item {
+        display: block;
+        height: 300rpx;
+        line-height: 300rpx;
+        text-align: center;
+      }
+
+      img {
+        width: 100%;
+        height: 100%;
+        display: block;
+      }
+
+      .swiper-list {
+        margin-top: 40rpx;
+        margin-bottom: 0;
+      }
+
+      .uni-common-mt {
+        margin-top: 60rpx;
+        position: relative;
+      }
+
+      .info {
+        position: absolute;
+        right: 20rpx;
+      }
+
+      .uni-padding-wrap {
+        width: 550rpx;
+        padding: 0 100rpx;
+      }
+    }
+
+    .find-container {
+      margin-top: 20rpx;
+      padding: 20rpx 30rpx;
+      box-sizing: border-box;
+      background-color: #fff;
+      border-radius: 20rpx;
+
+      .find-top,
+      .find-bottom {
+        display: flex;
+        justify-content: space-between;
+        margin-top: 20rpx;
+
+        .find-item {
+          display: flex;
+          align-items: center;
+          justify-content: center;
+          flex-direction: column;
+          gap: 20rpx;
+
+          img {
+            width: 100rpx;
+            height: 100rpx;
+          }
+        }
+      }
+    }
+
+    .seek-advice {
+      display: flex;
+      align-items: center;
+      gap: 30rpx;
+      height: 75rpx;
+      background-color: #fff;
+      border-radius: 20rpx;
+      margin-top: 25rpx;
+      box-sizing: border-box;
+      padding: 0 30rpx;
+
+      p {
+        i {
+          color: #ef530e;
+          font-size: 36rpx;
+          font-weight: 700;
+        }
+      }
+
+      .over {
+        // width: 370rpx;
+        flex: 1;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        white-space: nowrap;
+      }
+
+      span {
+        position: relative;
+
+        &::after {
+          content: "";
+          width: 1px;
+          height: 80%;
+          background-color: #f5f5f5;
+          position: absolute;
+          left: -20rpx;
+          top: 50%;
+          transform: translateY(-50%);
+        }
+      }
+    }
+
+    .market {
+      padding: 30rpx 22rpx 0rpx;
+      background-color: #fff;
+      margin-top: 25rpx;
+      border-radius: 20rpx;
+
+      .market-top {
+        display: flex;
+        align-items: center;
+        gap: 70rpx;
+        border-bottom: 1px solid #f5f5f5;
+        padding-bottom: 25rpx;
+
+        .material {
+          font-size: 42rpx;
+          font-weight: 700;
+          color: #000;
+          display: flex;
+          font-style: italic;
+
+          span {
+            color: #0bc5b6;
+          }
+        }
+
+        .percentage {
+          display: flex;
+          flex-direction: column;
+          gap: 10rpx;
+
+          .item-box {
+            display: flex;
+            gap: 20rpx;
+
+            > span {
+              color: rgba(0, 0, 0, 0.5);
+            }
+
+            .item {
+              display: flex;
+              align-items: center;
+              gap: 15rpx;
+
+              p {
+                width: 25rpx;
+                height: 25rpx;
+                background-color: #0bc5b6;
+                border-radius: 50%;
+                display: flex;
+              }
+            }
+          }
+
+          > p {
+            color: #000;
+            font-weight: 500;
+          }
+        }
+
+        .deal {
+          span {
+            color: rgba(0, 0, 0, 0.5);
+          }
+
+          p {
+            margin-top: 15rpx;
+            font-weight: 500;
+          }
+        }
+      }
+
+      .market-bottom {
+        display: flex;
+
+        .find-left {
+          padding: 30rpx 30rpx 30rpx 0rpx;
+          flex: 1;
+          display: flex;
+          flex-direction: column;
+          gap: 25rpx;
+          box-sizing: border-box;
+          border-right: 1px solid #f5f5f5;
+
+          .title {
+            display: flex;
+            align-items: center;
+            font-size: 38rpx;
+            font-weight: 700;
+
+            span {
+              color: #ef530e;
+            }
+
+            .img-box {
+              margin-left: 15rpx;
+              width: 110rpx;
+              height: 35rpx;
+              background: url("../../../static/new-images/home/bg-icon.png")
+                no-repeat center;
+              background-size: 100% 100%;
+              text-align: center;
+              padding-left: 20rpx;
+              line-height: 35rpx;
+              font-size: 24rpx;
+              color: #fff;
+            }
+          }
+
+          .text {
+            font-size: 26rpx;
+          }
+
+          > p {
+            color: #ef530e;
+            font-size: 32rpx;
+            font-weight: 600;
+          }
+        }
+
+        .find-right {
+          padding: 30rpx 0 30rpx 30rpx;
+          flex: 1;
+          display: flex;
+          flex-direction: column;
+          gap: 25rpx;
+
+          .title {
+            font-size: 38rpx;
+            font-weight: 700;
+          }
+
+          span {
+            color: #ef530e;
+            font-size: 32rpx;
+            font-weight: 600;
+          }
+        }
+      }
+    }
+    .hotSales {
+      .title {
+        margin-left: 15rpx;
+        padding: 30rpx 0;
+        i {
+          font-size: 36rpx;
+          font-weight: 700;
+          color: #000;
+        }
+      }
+      .commodity-list {
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+        flex-wrap: wrap;
+        gap: 20rpx;
+        padding: 0 10rpx;
+        .commodity-item {
+          border-radius: 20rpx;
+          padding: 10rpx 10rpx 30rpx 10rpx;
+          box-sizing: border-box;
+          width: 335rpx;
+          background-color: #fff;
+          .img-box {
+            height: 300rpx;
+          }
+          .text-title {
+            display: flex;
+            margin-top: 20rpx;
+            font-size: 26rpx;
+            gap: 7rpx;
+            .text-left {
+              display: flex;
+              align-items: center;
+              font-size: 20rpx;
+              p {
+                padding: 3rpx 5rpx;
+                background-color: #ef530e;
+                color: #fff;
+                border-radius: 7rpx 0 0 7rpx;
+              }
+              span {
+                padding: 0rpx 5rpx;
+                background-color: #fff;
+                color: #ef530e;
+                border: 1rpx solid #ef530e;
+                border-left: 0;
+                border-radius: 0rpx 7rpx 7rpx 0rpx;
+              }
+            }
+            > p {
+              &:last-of-type {
+                width: 120rpx;
+                overflow: hidden;
+                text-overflow: ellipsis;
+                white-space: nowrap;
+              }
+            }
+          }
+          .detail-text {
+            overflow: hidden;
+            white-space: nowrap;
+            text-overflow: ellipsis;
+            margin-top: 15rpx;
+          }
+          .identifying {
+            display: flex;
+            gap: 10rpx;
+            margin-top: 15rpx;
+            span {
+              font-size: 20rpx;
+              padding: 3rpx 10rpx;
+              border-radius: 10rpx;
+              border: 1rpx solid #ef530e;
+              color: #ef530e;
+              &:last-of-type {
+                border-color: #08377f;
+                color: #08377f;
+              }
+            }
+          }
+          .price {
+            display: flex;
+            align-items: center;
+            margin-top: 20rpx;
+            gap: 10rpx;
+            p {
+              font-size: 38rpx;
+              color: #000;
+              font-weight: 700;
+              span{
+                font-size: 28rpx;
+              }
+            }
+            span {
+              font-size: 26rpx;
+            }
+          }
+        }
+      }
+    }
+  }
+}

+ 210 - 0
pages/new-tabbar/home/index.vue

@@ -0,0 +1,210 @@
+<template>
+    <div class="home">
+        <Nav>
+            <template #title>
+                <p>中国·龙江</p>
+                <span>全球最大的家具材料产业集群</span>
+            </template>
+        </Nav>
+        <main>
+            <div class="swiper">
+                <view class="uni-margin-wrap">
+                    <swiper class="swiper" circular :indicator-dots="true" :autoplay="true" :interval="1500" :duration="500"
+                        indicator-color="#a0a0a0" indicator-active-color="#fff">
+                        <swiper-item>
+                            <view class="swiper-item uni-bg-red">
+                                <img src="../../../static/new-images/home/swiper.png" alt="">
+                            </view>
+                        </swiper-item>
+                        <swiper-item>
+                            <img src="../../../static/new-images/home/swiper.png" alt="">
+                        </swiper-item>
+                        <swiper-item>
+                            <img src="../../../static/new-images/home/swiper.png" alt="">
+                        </swiper-item>
+                    </swiper>
+                </view>
+            </div>
+            <div class="find-container">
+                <div class="find-top">
+                    <div class="find-item">
+                        <img src="../../../static/new-images/home/icon_1.png" alt="">
+                        <p>找布匹</p>
+                    </div>
+                    <div class="find-item">
+                        <img src="../../../static/new-images/home/icon_2.png" alt="">
+                        <p>找皮革</p>
+                    </div>
+                    <div class="find-item">
+                        <img src="../../../static/new-images/home/icon_3.png" alt="">
+                        <p>找配件</p>
+                    </div>
+                    <div class="find-item">
+                        <img src="../../../static/new-images/home/icon_4.png" alt="">
+                        <p>半成品</p>
+                    </div>
+                </div>
+                <div class="find-bottom">
+                    <div class="find-item">
+                        <img src="../../../static/new-images/home/icon_5.png" alt="">
+                        <p>找店铺</p>
+                    </div>
+                    <div class="find-item">
+                        <img src="../../../static/new-images/home/icon_6.png" alt="">
+                        <p>发快递</p>
+                    </div>
+                    <div class="find-item">
+                        <img src="../../../static/new-images/home/icon_7.png" alt="">
+                        <p>看报价</p>
+                    </div>
+                    <div class="find-item">
+                        <img src="../../../static/new-images/home/icon_8.png" alt="">
+                        <p>找更多</p>
+                    </div>
+                </div>
+                <div class="find-bottom"></div>
+            </div>
+            <div class="seek-advice">
+                <p>
+                    <i>最近浏览</i>
+                </p>
+                <div class="over">
+                    <tui-roll-news :list="list" :interval="2500">
+                    </tui-roll-news>
+                    <!-- 人的一生要想活得幸福,走得更为长远,有一项能力必不可少,那就是感恩能力。卡耐基曾说过,感恩是极有教养的产物,你不可能从一般人身上得到,因为忘记或不会感谢乃是人的天性。 -->
+                </div>
+                <!-- <span>更多</span> -->
+            </div>
+            <div class="market">
+                <div class="market-top">
+                    <div class="material">
+                        <p>材料</p>
+                        <span>行情</span>
+                    </div>
+                    <div class="percentage">
+                        <div class="item-box">
+                            <span>皮革</span>
+                            <div class="item">
+                                <span>0.03%</span>
+                                <p><tui-icon name="arrowup" size="12" color="#fff"></tui-icon></p>
+                            </div>
+                        </div>
+                        <p>5.6/m</p>
+                    </div>
+                    <div class="deal">
+                        <span>皮革成交额</span>
+                        <p>55889.6元</p>
+                    </div>
+                </div>
+                <div class="market-bottom">
+                    <div class="find-left">
+                        <div class="title">
+                            <span>找</span>
+                            <p>材料</p>
+                            <div class="img-box">
+                                快速响应
+                            </div>
+                        </div>
+                        <div class="text">具材料网这里有符合 您所有的材料需求</div>
+                        <p>立即发布</p>
+                    </div>
+                    <div class="find-right">
+                        <div class="title">材料解答</div>
+                        <p>这里有家具材料材料专家为您解答材料问题</p>
+                        <span>发表问题</span>
+                    </div>
+                </div>
+            </div>
+            <div class="hotSales">
+                <div class="title">
+                    <i>热销材料</i>
+                </div>
+                <div class="commodity-list">
+                    <div class="commodity-item" v-for="(item, index)  in productList" :key="index" @click="goDetail(item)">
+                        <div class="img-box">
+                            <tui-lazyload-img width="100%" height="100%" radius="20rpx" :src="item.image || ''"
+                                mode="aspectFill"></tui-lazyload-img>
+                        </div>
+                        <div class="text-title">
+                            <div class="text-left">
+                                <p>工厂价</p>
+                                <span>直销</span>
+                            </div>
+                            <p>品牌</p>
+                            <p>{{ item.shopName }}</p>
+                        </div>
+                        <div class="detail-text">{{ item.productName }}</div>
+                        <div class="identifying">
+                            <span>七天无理由</span>
+                            <span>官方正品</span>
+                        </div>
+                        <div class="price">
+                            <p><span>¥</span>{{ item.originalPrice }}</p>
+                            <span>优惠价¥{{ item.price }}</span>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </main>
+    </div>
+</template>
+
+<script>
+import Nav from "../components/Nav.vue"
+import { getProducts2CanvasApi } from '../../../api/anotherTFInterface'
+export default {
+    components: {
+        Nav
+    },
+    created() {
+        this.getSelect()
+    },
+    data() {
+        return {
+            //  滚动的数据
+            //list 数据
+            list: [{
+                id: 1,
+                title: '151*****121用户正在浏览'
+            }, {
+                id: 2,
+                title: '191*****057用户刚刚查看了此页面'
+            }, {
+                id: 3,
+                title: '137*****526用户刚刚搜索了布匹'
+            }],
+            hotSearch: {
+                page: 1,
+                pageSize: 10,
+            },
+            productList: [],
+            productListTotal: null
+        }
+    },
+    methods: {
+        async getSelect() {
+            // this.hotSearch.page++
+            let res = await getProducts2CanvasApi(this.hotSearch);
+            this.productListTotal = res.data.total
+            this.productList.push(...res.data.list)
+        },
+        // 去到详情
+        goDetail(item) {
+            console.log(item);
+            uni.navigateTo({
+                url: `/another-tf/another-serve/goodsDetails/index?shopId=${item.shopId}&productId=${item.productId}&skuId=${item.skuId}`,
+            });
+        },
+    },
+    // 触底加载
+    onReachBottom() {
+        if (this.productList.length >= this.productListTotal) return
+        this.hotSearch.page++
+        this.getSelect()
+    }
+}
+</script>
+
+<style lang="scss" scoped>
+@import './index.scss';
+</style>

+ 201 - 0
pages/new-tabbar/user/index.vue

@@ -0,0 +1,201 @@
+<template>
+    <view class="user">
+        <header>
+            <view class="title">个人中心</view>
+        </header>
+        <view class="card-info">
+            <view class="top-info">
+                <view class="img-box">
+                    <img src="../../../static/new-images/user/header.jpg" alt="">
+                </view>
+                <view class="text">
+                    <view class="name">韦忠林</view>
+                    <view class="label">
+                        <p>商家</p>
+                        <span>18811639900</span>
+                    </view>
+                </view>
+                <view class="bg-box">
+                    个人管理
+                </view>
+            </view>
+            <view class="perfect">
+                <view class="bg"></view>
+                <button class="btn">去完善</button>
+            </view>
+        </view>
+        <main>
+            <view class="info-list">
+                <view class="info-item" v-for="(item, index) in infoList" :key="index">
+                    <view class="left">{{ item }}</view>
+                    <p>
+                        <template v-if="index < 2">
+                            <span>去认证</span>
+                        </template>
+                        <span><tui-icon name="arrowright" size="27" color="#B9B9B9"></tui-icon></span>
+                    </p>
+                </view>
+            </view>
+            <view class="setUp">
+                <view class="left">设置</view>
+                <p>
+                    <span><tui-icon name="arrowright" size="27" color="#B9B9B9"></tui-icon></span>
+                </p>
+            </view>
+        </main>
+    </view>
+</template>
+
+<script>
+export default {
+    data() {
+        return {
+            infoList: ['个人认证信息', '企业认证信息', '商品管理', '订单管理', '会员管理', '信息管理']
+        }
+    }
+}
+</script>
+
+<style lang="scss" scoped>
+page {
+    background-color: #F5F5F5;
+}
+
+.user {
+    header {
+        padding: 30rpx 30rpx 25rpx;
+
+        .title {
+            font-size: 42rpx;
+            font-weight: 700;
+        }
+
+    }
+
+    .card-info {
+        background-color: #fff;
+        padding: 30rpx 0rpx 40rpx 40rpx;
+        box-sizing: border-box;
+
+        .top-info {
+            display: flex;
+            align-items: center;
+            position: relative;
+            margin-bottom: 40rpx;
+
+            .img-box {
+                width: 112rpx;
+                height: 112rpx;
+                margin-right: 25rpx;
+
+                img {
+                    width: 100%;
+                    height: 100%;
+                    display: block;
+                    border-radius: 50%;
+                }
+            }
+
+            .text {
+                .name {
+                    font-size: 36rpx;
+                    font-weight: 600;
+                    margin-bottom: 15rpx;
+                }
+
+                .label {
+                    display: flex;
+                    align-items: center;
+                    gap: 10rpx;
+
+                    p {
+                        padding: 5rpx 20rpx;
+                        color: #fff;
+                        background-color: #FF6619;
+                        font-size: 20rpx;
+                        border-radius: 10rpx;
+                    }
+
+                    span {
+                        color: #969699;
+                        font-size: 22rpx;
+                    }
+                }
+            }
+
+            .bg-box {
+                background: url("../../../static/new-images/user/bg.png") no-repeat center;
+                background-size: 100% 100%;
+                width: 160rpx;
+                height: 64rpx;
+                text-align: center;
+                line-height: 64rpx;
+                color: #fff;
+                position: absolute;
+                top: 0;
+                right: 0rpx;
+            }
+        }
+
+        .perfect {
+            padding-right: 30rpx;
+            width: 100%;
+            height: 130rpx;
+            box-sizing: border-box;
+            position: relative;
+
+            .bg {
+                width: 100%;
+                height: 100%;
+                background: url("../../../static/new-images/user/go.png") no-repeat center;
+                background-size: 100% 100%;
+            }
+
+            .btn {
+                position: absolute;
+                top: 32rpx;
+                right: 45rpx;
+                background: linear-gradient(to right, #DCC5AD, #CDAC8D);
+                color: #373535;
+                text-align: center;
+                width: 145rpx;
+                height: 55rpx;
+                line-height: 55rpx;
+                font-size: 24rpx;
+            }
+        }
+    }
+
+    .info-list {
+        margin-top: 30rpx;
+        padding: 0 20rpx;
+        box-sizing: border-box;
+        background-color: #fff;
+    }
+
+    .info-item,
+    .setUp {
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+        border-bottom: 1rpx solid #E8E8E9;
+        padding: 25rpx 10rpx;
+        font-size: 30rpx;
+        &:last-of-type{
+            border-bottom: 0rpx;
+        }
+
+        p {
+            display: flex;
+            align-items: center;
+            color: #B9B9B9;
+        }
+    }
+
+    .setUp {
+        background-color: #fff;
+        margin-top: 24rpx;
+        border-bottom: 0rpx;
+        padding: 25rpx 30rpx 
+    }
+}</style>

+ 245 - 0
pages/register/index.vue

@@ -0,0 +1,245 @@
+<template>
+	<div class="register">
+		<div class="back">
+			<tui-icon name="arrowleft" color="#000" :size="30" @click="handleBack"></tui-icon>
+		</div>
+		<div class="title">
+			<h2>填写注册信息</h2>
+			<p>请仔细填写以下信息,以免后期登陆异常</p>
+		</div>
+		<div class="ipt">
+			<tui-input v-model="registerQuery.phone" placeholder="请输入手机号码"></tui-input>
+			<div class="code-box">
+				<tui-input v-model="registerQuery.verificationCode" :maxlength="4" placeholder="请输入验证码"></tui-input>
+				<div class="code">
+					<tui-countdown-verify
+						ref="refRegisterVerify" color="#EF530E" border-color="transparent"
+						:size="28" :seconds="60" @send="handleSendVerify"
+					></tui-countdown-verify>
+				</div>
+			</div>
+		</div>
+		<div class="btn-list">
+			<tui-form-button
+				:disabled="!(registerQuery.phone && registerQuery.verificationCode)" color="#fff" :background="!!(registerQuery.phone && registerQuery.verificationCode) ? '#EF530E' : '#C6C7CB'"
+				@click="addAcount"
+			>
+				确定
+			</tui-form-button>
+		</div>
+		<footer>
+			<p>登录即代表你已阅读并同意</p>
+			<span>《用户服务协议》</span>
+		</footer>
+		<tui-modal :show="hasRegister" custom fade-i :button="[]" @cancel="hasRegister = false">
+			<view style="padding: 28upx 0;text-align: center;">
+				<image style="width: 240rpx;height: 144rpx;" src="../../static/images/icon/acount.png"></image>
+				<view
+					style="margin-top: 46rpx;font-size: 36rpx;font-weight: normal;line-height: 52rpx;text-align: center;color: #222229;"
+				>
+					注册完成
+				</view>
+				<view
+					style="margin-top: 18rpx;font-size: 28fpx;font-weight: 350;line-height: 40fpx;text-align: center;color: #888889;"
+				>
+					您已完成注册,请前往首页
+				</view>
+				<button
+					style="width: 484rpx;height: 80rpx;border-radius: 8rpx;background: #EF530E;color: #fff;line-height: 80rpx;margin-top: 80rpx;"
+					@click="$switchTab('/')"
+				>
+					立即跳转
+				</button>
+			</view>
+		</tui-modal>
+	</div>
+</template>
+
+<script>
+import { updatePhoneLoginRegisterApi, getVerifyCodeApi } from '../../api/anotherTFInterface'
+
+export default {
+	name: 'Register',
+	data() {
+		return {
+			registerQuery: {
+				password: '',
+				passwordAgain: '',
+				phone: '',
+				verificationCode: ''
+			},
+			hasRegister: false
+		}
+	},
+	methods: {
+		// 获取验证码
+		handleSendVerify() {
+			if (!this.registerQuery.phone) {
+				this.$refs.refRegisterVerify.reset()
+				return this.$showToast('请填写手机号')
+			}
+			if (!/^1[3-9]\d{9}$/.test(this.registerQuery.phone)) {
+				this.$refs.refRegisterVerify.reset()
+				return this.$showToast('请输入正确的手机号')
+			}
+			getVerifyCodeApi({ phone: this.registerQuery.phone })
+				.then((res) => {
+					this.$refs.refRegisterVerify.success()
+					this.$showToast('发送成功,请注意查看手机短信')
+				})
+				.catch(() => {
+					this.$refs.refRegisterVerify.reset()
+				})
+		},
+
+		addAcount() {
+			this.$refs.form.validate(this.registerQuery, [
+				{
+					name: 'phone',
+					rule: ['required', 'isMobile'],
+					msg: ['请输入手机号', '请输入正确的手机号']
+				},
+				{
+					name: 'verificationCode',
+					rule: [ 'required' ],
+					msg: [ '请输入验证码' ]
+				}
+				// {
+				// 	name: 'password',
+				// 	rule: ['required', 'isEnAndNo'],
+				// 	msg: ['请输入密码', '密码为8~20位英文和数字组合']
+				// },
+				// {
+				// 	name: 'passwordAgain',
+				// 	rule: ['required', 'isSame:password'],
+				// 	msg: ['请再次确认密码', '两次密码不一致']
+				// }
+			])
+				.then(() => {
+					updatePhoneLoginRegisterApi({
+						type: 1,
+						phone: this.registerQuery.phone,
+						verificationCode: this.registerQuery.verificationCode,
+						password: this.registerQuery.password
+					}).then((res) => {
+						this.$store.dispatch('auth/LoginAfterAction', { type: 'phone', data: res.data })
+						this.hasRegister = true
+					})
+				})
+				.catch((e) => {
+					this.$showToast(JSON.stringify(e.errorMsg))
+				})
+		}
+	}
+}
+</script>
+
+<style lang="scss" scoped>
+.register {
+    position: relative;
+    padding: 190rpx 65rpx 0;
+    box-sizing: border-box;
+
+    .back {
+        position: absolute;
+        top: 80rpx;
+        left: 35rpx;
+    }
+
+    .title {
+        padding-left: 25rpx;
+
+        h2 {
+            font-size: 64rpx;
+            font-weight: normal;
+        }
+
+        p {
+            margin-top: 10rpx;
+        }
+    }
+
+    .ipt {
+        margin-top: 80rpx;
+
+        .code-box {
+            margin-top: 40rpx;
+            position: relative;
+
+            .code {
+                position: absolute;
+                right: 0;
+                top: 50%;
+                transform: translateY(-50%);
+            }
+        }
+
+        .text {
+            margin-top: 30rpx;
+            display: flex;
+            font-size: 24rpx;
+            margin-left: 30rpx;
+
+            p {
+                color: #646466;
+            }
+
+            span {
+                color: #397BFF;
+            }
+        }
+    }
+
+    .btn-list {
+        margin-top: 80rpx;
+    }
+
+    footer {
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        width: 100%;
+        position: fixed;
+        left: 0;
+        bottom: 100rpx;
+        font-size: 24rpx;
+
+        p {
+            color: #888889;
+        }
+
+        span {
+            font-weight: 500;
+        }
+    }
+
+    .tui-modal-custom {
+        padding: 40rpx 0;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        flex-direction: column;
+        gap: 30rpx;
+        position: relative;
+
+        .img-box {
+            width: 200rpx;
+            height: 120rpx;
+
+            img {
+                width: 100%;
+                height: 100%;
+            }
+        }
+
+        .tui-modal-custom-text {
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            flex-direction: column;
+            gap: 30rpx;
+            margin-bottom: 50rpx;
+        }
+    }
+}
+</style>

BIN
static/new-images/findServices/give_1.png


BIN
static/new-images/findServices/give_2.png


BIN
static/new-images/findServices/give_3.png


BIN
static/new-images/findServices/give_4.png


BIN
static/new-images/findServices/give_5.png


BIN
static/new-images/findServices/institution_1.png


BIN
static/new-images/findServices/top_1.png


BIN
static/new-images/findServices/top_2.png


BIN
static/new-images/findServices/top_3.png


BIN
static/new-images/findStore/icon.png


BIN
static/new-images/findStore/product.png


BIN
static/new-images/hall/head.png


BIN
static/new-images/home/bg-icon.png


BIN
static/new-images/home/classification.png


BIN
static/new-images/home/icon_1.png


BIN
static/new-images/home/icon_2.png


BIN
static/new-images/home/icon_3.png


BIN
static/new-images/home/icon_4.png


BIN
static/new-images/home/icon_5.png


BIN
static/new-images/home/icon_6.png


BIN
static/new-images/home/icon_7.png


BIN
static/new-images/home/icon_8.png


BIN
static/new-images/home/swiper.png


BIN
static/new-images/tabbar/findServices.png


BIN
static/new-images/tabbar/findServices_1.png


BIN
static/new-images/tabbar/findStore.png


BIN
static/new-images/tabbar/findStore_1.png


BIN
static/new-images/tabbar/hall.png


BIN
static/new-images/tabbar/hall_1.png


BIN
static/new-images/tabbar/home.png


BIN
static/new-images/tabbar/home_1.png


BIN
static/new-images/tabbar/user.png


BIN
static/new-images/tabbar/user_1.png


BIN
static/new-images/user/bg.png


BIN
static/new-images/user/go.png


BIN
static/new-images/user/header.jpg


+ 3 - 3
store/modules/auth.js

@@ -236,7 +236,7 @@ export default {
 							})
 						} else {
 							uni.switchTab({
-								url: '/pages/another-index/index'
+								url: '/'
 							})
 						}
 					}, 2000)
@@ -284,7 +284,7 @@ export default {
 							})
 						} else {
 							uni.switchTab({
-								url: '/pages/another-index/index'
+								url: '/'
 							})
 						}
 					} else { // 第一次登录,绑定手机号
@@ -307,7 +307,7 @@ export default {
 				uni.showToast({ title: '退出成功', icon: 'none' })
 				setTimeout(() => {
 					uni.switchTab({
-						url: '/pages/another-index/index'
+						url: '/'
 					})
 				}, 2000)
 			}

+ 21 - 0
uview-ui/LICENSE

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

+ 102 - 0
uview-ui/README.md

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

+ 182 - 0
uview-ui/components/u-action-sheet/u-action-sheet.vue

@@ -0,0 +1,182 @@
+<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" 
+				:class="[index < list.length - 1 ? 'u-border-bottom' : '']"
+				:hover-stay-time="150"
+			>
+				{{item.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>
+	/**
+	 * 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>
+	 */
+	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: '取消'
+			}
+		},
+		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.list[index].disabled) style.color = '#c0c4cc';
+					return style;
+				}
+			},
+			uZIndex() {
+				// 如果用户有传递z-index值,优先使用
+				return this.zIndex ? this.zIndex : this.$u.zIndex.popup;
+			}
+		},
+		methods: {
+			// 点击取消按钮
+			close() {
+				// 发送input事件,并不会作用于父组件,而是要设置组件内部通过props传递的value参数
+				// 这是一个vue发送事件的特殊用法
+				this.popupClose();
+				this.$emit('close');
+			},
+			// 弹窗关闭
+			popupClose() {
+				this.$emit('input', false);
+			},
+			// 点击某一个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";
+
+	.u-tips {
+		font-size: 26rpx;
+		text-align: center;
+		padding: 34rpx 0;
+		line-height: 1;
+		color: $u-tips-color;
+	}
+
+	.u-action-sheet-item {
+		display: flex;
+		line-height: 1;
+		justify-content: center;
+		align-items: center;
+		font-size: 34rpx;
+		padding: 34rpx 0;
+	}
+
+	.u-gab {
+		height: 12rpx;
+		background-color: rgb(234, 234, 236);
+	}
+
+	.u-actionsheet-cancel {
+		color: $u-main-color;
+	}
+</style>

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

@@ -0,0 +1,256 @@
+<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 {
+		display: 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>

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

@@ -0,0 +1,290 @@
+<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>
+@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;
+	display: flex;
+	flex-direction: row;
+	justify-content: space-between;
+	align-items: center;
+	width: 100%;
+	background-color: #000;
+}
+
+.cropper-buttons {
+	width: 100vw;
+	display: 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>

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

@@ -0,0 +1,1265 @@
+/**
+ * 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;
+
+})));

File diff suppressed because it is too large
+ 24 - 0
uview-ui/components/u-avatar/u-avatar.vue


+ 153 - 0
uview-ui/components/u-back-top/u-back-top.vue

@@ -0,0 +1,153 @@
+<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">
+			<u-icon @click="backToTop" :name="icon" :custom-style="iconStyle"></u-icon>
+			<view class="u-back-top__content__tips">
+				{{tips}}
+			</view>
+		</view>
+		<slot v-else />
+	</view>
+</template>
+
+<script>
+	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'
+					}
+				}
+			},
+			// 整个组件的样式
+			customStyle: {
+				type: Object,
+				default() {
+					return {}
+				}
+			}
+		},
+		watch: {
+			showBackTop(nVal, oVal) {
+				// 当组件的显示与隐藏状态发生跳变时,修改组件的层级和不透明度
+				// 让组件有显示和消失的动画效果,如果用v-if控制组件状态,将无设置动画效果
+				if(nVal) {
+					this.uZIndex = this.zIndex;
+					this.opacity = 1;
+				} else {
+					this.uZIndex = -1;
+					this.opacity = 0;
+				}
+			}
+		},
+		computed: {
+			showBackTop() {
+				// 由于scrollTop为页面的滚动距离,默认为px单位,这里将用于传入的top(rpx)值
+				// 转为px用于比较,如果滚动条到顶的距离大于设定的距离,就显示返回顶部的按钮
+				return this.scrollTop > uni.upx2px(this.top);
+			},
+		},
+		data() {
+			return {
+				// 不透明度,为了让组件有一个显示和隐藏的过渡动画
+				opacity: 0,
+				// 组件的z-index值,隐藏时设置为-1,就会看不到
+				uZIndex: -1
+			}
+		},
+		methods: {
+			backToTop() {
+				uni.pageScrollTo({
+					scrollTop: 0,
+					duration: this.duration
+				});
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	@import "../../libs/css/style.components.scss";
+	
+	.u-back-top {
+		width: 80rpx;
+		height: 80rpx;
+		position: fixed;
+		z-index: 9;
+		display: flex;
+		flex-direction: column;
+		justify-content: center;
+		background-color: #E1E1E1;
+		color: $u-content-color;
+		align-items: center;
+		transition: opacity 0.4s;
+		
+		&__content {
+			display: flex;
+			flex-direction: column;
+			align-items: center;
+			
+			&__tips {
+				font-size: 24rpx;
+				transform: scale(0.8);
+				line-height: 1;
+			}
+		}
+	}
+</style>

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

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

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

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

+ 641 - 0
uview-ui/components/u-calendar/u-calendar.vue

@@ -0,0 +1,641 @@
+<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">
+		<view class="u-calendar">
+			<view class="u-calendar__header">
+				<view class="u-calendar__header__text" v-if="!$slots['tooltip']">
+					{{toolTip}}
+				</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>
+		</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: '选择日期'
+			}
+		},
+		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;
+			}
+		},
+		watch: {
+			dataChange(val) {
+				this.init()
+			}
+		},
+		created() {
+			this.init()
+		},
+		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 //是否是切换年月操作
+					});
+				} 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
+					});
+				}
+			}
+		}
+	};
+</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;
+				display: flex;
+				justify-content: center;
+				align-items: center;
+			}
+		}
+		
+		&__action {
+			padding: 40rpx 0 40rpx 0;
+			
+			&__icon {
+				margin: 0 16rpx;
+			}
+			
+			&__text {
+				padding: 0 16rpx;
+				color: $u-main-color;
+				font-size: 32rpx;
+				line-height: 32rpx;
+				font-weight: bold;
+			}
+		}
+	
+		&__week-day {
+			display: flex;
+			align-items: center;
+			justify-content: center;
+			padding: 6px 0;
+			overflow: hidden;
+			
+			&__text {
+				flex: 1;
+				text-align: center;
+			}
+		}
+	
+		&__content {
+			width: 100%;
+			display: 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;
+			}
+			
+			&--start-date {
+				border-top-left-radius: 8rpx;
+				border-bottom-left-radius: 8rpx;
+			}
+			
+			&__item {
+				width: 14.2857%;
+				display: flex;
+				align-items: center;
+				justify-content: center;
+				padding: 6px 0;
+				overflow: hidden;
+				position: relative;
+				z-index: 2;
+				
+				&__inner {
+					height: 84rpx;
+					display: -webkit-box;
+					display: -webkit-flex;
+					display: 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;
+				}
+			}
+			
+			&__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%;
+			display: 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;
+			}
+			
+			&__btn {
+				width: 100%;
+			}
+		}
+	}
+</style>

+ 254 - 0
uview-ui/components/u-car-keyboard/u-car-keyboard.vue

@@ -0,0 +1,254 @@
+<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>
+				<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 :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>
+			</block>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		name: "u-keyboard",
+		props: {
+			// 是否打乱键盘按键的顺序
+			random: {
+				type: Boolean,
+				default: false
+			}
+		},
+		data() {
+			return {
+				// 车牌输入时,abc=true为输入车牌号码,bac=false为输入省份中文简称
+				abc: false
+			};
+		},
+		computed: {
+			areaList() {
+				let data = [
+					'京',
+					'沪',
+					'粤',
+					'津',
+					'冀',
+					'豫',
+					'云',
+					'辽',
+					'黑',
+					'湘',
+					'皖',
+					'鲁',
+					'苏',
+					'浙',
+					'赣',
+					'鄂',
+					'桂',
+					'甘',
+					'晋',
+					'陕',
+					'蒙',
+					'吉',
+					'闽',
+					'贵',
+					'渝',
+					'川',
+					'青',
+					'琼',
+					'宁',
+					'挂',
+					'藏',
+					'港',
+					'澳',
+					'新',
+					'使',
+					'学'
+				];
+				let tmp = [];
+				// 打乱顺序
+				if (this.random) data = this.$u.randomArray(data);
+				// 切割成二维数组
+				tmp[0] = data.slice(0, 10);
+				tmp[1] = data.slice(10, 20);
+				tmp[2] = data.slice(20, 30);
+				tmp[3] = data.slice(30, 36);
+				return tmp;
+			},
+			EngKeyBoardList() {
+				let data = [
+					1,
+					2,
+					3,
+					4,
+					5,
+					6,
+					7,
+					8,
+					9,
+					0,
+					'Q',
+					'W',
+					'E',
+					'R',
+					'T',
+					'Y',
+					'U',
+					'I',
+					'O',
+					'P',
+					'A',
+					'S',
+					'D',
+					'F',
+					'G',
+					'H',
+					'J',
+					'K',
+					'L',
+					'Z',
+					'X',
+					'C',
+					'V',
+					'B',
+					'N',
+					'M'
+				];
+				let tmp = [];
+				if (this.random) data = this.$u.randomArray(data);
+				tmp[0] = data.slice(0, 10);
+				tmp[1] = data.slice(10, 20);
+				tmp[2] = data.slice(20, 30);
+				tmp[3] = data.slice(30, 36);
+				return tmp;
+			}
+		},
+		methods: {
+			// 点击键盘按钮
+			carInputClick(i, j) {
+				let value = '';
+				// 不同模式,获取不同数组的值
+				if (this.abc) value = this.EngKeyBoardList[i][j];
+				else value = this.areaList[i][j];
+				this.$emit('change', value);
+			},
+			// 修改汽车牌键盘的输入模式,中文|英文
+			changeCarInputMode() {
+				this.abc = !this.abc;
+			},
+			// 点击退格键
+			backspaceClick() {
+				this.$emit('backspace');
+				clearInterval(this.timer); //再次清空定时器,防止重复注册定时器
+				this.timer = null;
+				this.timer = setInterval(() => {
+					this.$emit('backspace');
+				}, 250);
+			},
+			clearTimer() {
+				clearInterval(this.timer);
+				this.timer = null;
+			},
+		}
+	};
+</script>
+
+<style lang="scss" scoped>
+	@import "../../libs/css/style.components.scss";
+
+	.u-keyboard-grids {
+		background: rgb(215, 215, 217);
+		padding: 24rpx 0;
+		position: relative;
+	}
+
+	.u-keyboard-grids-item {
+		display: flex;
+		align-items: center;
+		justify-content: center;
+	}
+
+	.u-keyboard-grids-btn {
+		text-decoration: none;
+		width: 62rpx;
+		flex: 0 0 64rpx;
+		height: 80rpx;
+		display: inline-block;
+		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;
+	}
+
+	.u-carinput-hover {
+		background-color: rgb(185, 188, 195) !important;
+	}
+
+	.u-keyboard-back {
+		position: absolute;
+		width: 96rpx;
+		right: 22rpx;
+		bottom: 32rpx;
+		height: 80rpx;
+		background-color: rgb(185, 188, 195);
+		display: flex;
+		align-items: center;
+		border-radius: 8rpx;
+		justify-content: center;
+		box-shadow: 0 2rpx 0rpx #888992;
+	}
+
+	.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;
+		display: flex;
+		align-items: center;
+		border-radius: 8rpx;
+		justify-content: center;
+	}
+
+	.u-keyboard-change .inactive.zh {
+		transform: scale(0.85) translateY(-10rpx);
+	}
+
+	.u-keyboard-change .inactive.en {
+		transform: scale(0.85) translateY(10rpx);
+	}
+
+	.u-keyboard-change .active {
+		color: rgb(237, 112, 64);
+		font-size: 30rpx;
+	}
+
+	.u-keyboard-change .zh {
+		transform: translateY(-10rpx);
+	}
+
+	.u-keyboard-change .en {
+		transform: translateY(10rpx);
+	}
+</style>

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

@@ -0,0 +1,298 @@
+<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/line.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: Boolean,
+			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;
+	}
+	
+	&--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>

+ 69 - 0
uview-ui/components/u-cell-group/u-cell-group.vue

@@ -0,0 +1,69 @@
+<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>
+</template>
+
+<script>
+	/**
+	 * 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'}
+	 * @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,
+			}
+		},
+	}
+</script>
+
+<style lang="scss" scoped>
+	@import "../../libs/css/style.components.scss";
+	
+	.u-cell-box {
+		width: 100%;
+	}
+
+	.u-cell-title {
+		padding: 30rpx 32rpx 10rpx 32rpx;
+		font-size: 30rpx;
+		text-align: left;
+		color: $u-tips-color;
+	}
+
+	.u-cell-item-box {
+		background-color: #FFFFFF;
+	}
+</style>

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

@@ -0,0 +1,300 @@
+<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" 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 {
+	position: relative;
+	display: flex;
+	box-sizing: border-box;
+	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 {
+	display: flex;
+	align-items: center;
+	height: 48rpx;
+}
+
+.u-cell-border:after {
+	position: absolute;
+	box-sizing: border-box;
+	content: ' ';
+	pointer-events: none;
+	right: 0;
+	left: 0;
+	top: 0;
+	border-bottom: 1px solid $u-border-color;
+	-webkit-transform: scaleY(0.5);
+	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;
+	word-wrap: break-word;
+}
+
+.u-cell__value {
+	overflow: hidden;
+	text-align: right;
+	vertical-align: middle;
+	color: $u-tips-color;
+	font-size: 26rpx;
+}
+
+.u-cell__title,
+.u-cell__value {
+	flex: 1;
+}
+
+.u-cell--required {
+	overflow: visible;
+	display: flex;
+	align-items: center;
+}
+
+.u-cell--required:before {
+	position: absolute;
+	content: '*';
+	left: 8px;
+	margin-top: 4rpx;
+	font-size: 14px;
+	color: $u-type-error;
+}
+</style>

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

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

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

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

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

@@ -0,0 +1,218 @@
+<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>
+</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;
+			}
+		},
+		// 底部圆环的颜色(灰色的圆环)
+		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
+		},
+		// 主题类型
+		type: {
+			type: String,
+			default: ''
+		},
+		// 整个圆环进度区域的背景色
+		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;
+			}
+			setTimeout(() => {
+				// 定时器,每次操作间隔为time值,为了让进度条有动画效果
+				this.drawCircleByProgress(progress);
+			}, time);
+		}
+	}
+};
+</script>
+
+<style lang="scss" scoped>
+@import "../../libs/css/style.components.scss";
+.u-circle-progress {
+	position: relative;
+	display: inline-flex;
+	align-items: center;
+	justify-content: center;
+}
+
+.u-canvas-bg {
+	position: absolute;
+}
+
+.u-canvas {
+	position: absolute;
+}
+</style>

+ 135 - 0
uview-ui/components/u-col/u-col.vue

@@ -0,0 +1,135 @@
+<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.stop.prevent="click">
+		<slot></slot>
+	</view>
+</template>
+
+<script>
+	/**
+	 * 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>
+	 */
+	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'
+			}
+		},
+		inject: ['gutter'],
+		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;
+			},
+			uAlignItem() {
+				if (this.align == 'top') return 'flex-start';
+				if (this.align == 'bottom') return 'flex-end';
+				else return this.align;
+			}
+		},
+		methods: {
+			click() {
+				this.$emit('click');
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	@import "../../libs/css/style.components.scss";
+	.u-col {
+		/* #ifdef MP-WEIXIN || MP-QQ */
+		float: left;
+		/* #endif */
+	}
+
+	.u-col-0 {
+		width: 0;
+	}
+
+	.u-col-1 {
+		width: calc(100%/12);
+	}
+
+	.u-col-2 {
+		width: calc(100%/12 * 2);
+	}
+
+	.u-col-3 {
+		width: calc(100%/12 * 3);
+	}
+
+	.u-col-4 {
+		width: calc(100%/12 * 4);
+	}
+
+	.u-col-5 {
+		width: calc(100%/12 * 5);
+	}
+
+	.u-col-6 {
+		width: calc(100%/12 * 6);
+	}
+
+	.u-col-7 {
+		width: calc(100%/12 * 7);
+	}
+
+	.u-col-8 {
+		width: calc(100%/12 * 8);
+	}
+
+	.u-col-9 {
+		width: calc(100%/12 * 9);
+	}
+
+	.u-col-10 {
+		width: calc(100%/12 * 10);
+	}
+
+	.u-col-11 {
+		width: calc(100%/12 * 11);
+	}
+
+	.u-col-12 {
+		width: calc(100%/12 * 12);
+	}
+</style>

+ 206 - 0
uview-ui/components/u-collapse-item/u-collapse-item.vue

@@ -0,0 +1,206 @@
+<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>
+	</view>
+</template>
+
+<script>
+	/**
+	 * 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被打开或者收起时触发
+	 * @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: ''
+			}
+		},
+		inject: ['uCollapse'],
+		data() {
+			return {
+				isShow: false,
+				elId: this.$u.guid(),
+				height: 0, // body内容的高度
+				headStyle: {}, // 头部样式,对象形式
+				bodyStyle: {}, // 主体部分样式
+				//itemStyle: {}, // 每个item的整体样式
+				arrowColor: '', // 箭头的颜色
+				hoverClass: '', // 头部按下时的效果样式类
+			};
+		},
+		mounted() {
+			this.init();
+		},
+		watch: {
+			open(val) {
+				this.isShow = val;
+			}
+		},
+		computed: {
+			arrow() {
+				return this.uCollapse.arrow;
+			},
+			itemStyle() {
+				return this.uCollapse.itemStyle;
+			}
+		},
+		created() {
+			// 获取u-collapse的信息,放在u-collapse是为了方便,不用每个u-collapse-item写一遍
+			this.isShow = this.open;
+			this.nameSync = this.name ? this.name : this.uCollapse.childrens.length;
+			this.uCollapse.childrens.push(this);
+			//this.itemStyle = this.uCollapse.itemStyle;
+			this.headStyle = this.uCollapse.headStyle;
+			this.bodyStyle = this.uCollapse.bodyStyle;
+			this.arrowColor = this.uCollapse.arrowColor;
+			this.hoverClass = this.uCollapse.hoverClass;
+		},
+		methods: {
+			// 异步获取内容,或者动态修改了内容时,需要重新初始化
+			init() {
+				this.$nextTick(() => {
+					this.queryRect();
+				});
+			},
+			// 点击collapsehead头部
+			headClick() {
+				if (this.disabled) return;
+				if (this.uCollapse.accordion == true) {
+					this.uCollapse.childrens.map(val => {
+						// 自身不设置为false,因为后面有this.isShow = !this.isShow;处理了
+						if (this != val) {
+							val.isShow = false;
+						}
+					});
+				}
+
+				this.isShow = !this.isShow;
+				// 触发本组件的事件
+				this.$emit('change', {
+					index: this.index,
+					show: this.isShow
+				})
+				// 只有在打开时才发出事件
+				if (this.isShow) this.uCollapse.onChange();
+				this.$forceUpdate();
+			},
+			// 查询内容高度
+			queryRect() {
+				// $uGetRect为uView自带的节点查询简化方法,详见文档介绍:https://www.uviewui.com/js/getRect.html
+				// 组件内部一般用this.$uGetRect,对外的为this.$u.getRect,二者功能一致,名称不同
+				this.$uGetRect('#' + this.elId).then(res => {
+					this.height = res.height;
+				})
+			}
+		}
+	};
+</script>
+
+<style lang="scss" scoped>
+	@import "../../libs/css/style.components.scss";
+	
+	.u-collapse-head {
+		position: relative;
+		display: flex;
+		justify-content: space-between;
+		align-items: center;
+		color: $u-main-color;
+		font-size: 30rpx;
+		line-height: 1;
+		padding: 24rpx 0;
+		text-align: left;
+	}
+
+	.u-collapse-title {
+		flex: 1;
+		overflow: hidden;
+	}
+
+	.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;
+	}
+
+	.u-collapse-body {
+		overflow: hidden;
+		transition: all 0.3s;
+	}
+
+	.u-collapse-content {
+		overflow: hidden;
+		font-size: 28rpx;
+		color: $u-tips-color;
+		text-align: left;
+	}
+</style>

+ 104 - 0
uview-ui/components/u-collapse/u-collapse.vue

@@ -0,0 +1,104 @@
+<template>
+	<view class="u-collapse">
+		<slot />
+	</view>
+</template>
+
+<script>
+	/**
+	 * 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)
+	 * @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'
+			}
+		},
+		provide() {
+			return {
+				uCollapse: this
+			}
+		},
+		created() {
+			this.childrens = []
+		},
+		data() {
+			return {
+
+			}
+		},
+		methods: {
+			// 重新初始化一次内部的所有子元素的高度计算,用于异步获取数据渲染的情况
+			init() {
+				this.childrens.forEach((vm, index) => {
+					vm.init();
+				})
+			},
+			// collapse item被点击,由collapse item调用父组件方法
+			onChange() {
+				let activeItem = [];
+				this.childrens.forEach((vm, index) => {
+					if (vm.isShow) {
+						activeItem.push(vm.nameSync);
+					}
+				})
+				// 如果是手风琴模式,只有一个匹配结果,也即activeItem长度为1,将其转为字符串
+				if (this.accordion) activeItem = activeItem.join('');
+				this.$emit('change', activeItem);
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	@import "../../libs/css/style.components.scss";
+</style>

+ 233 - 0
uview-ui/components/u-column-notice/u-column-notice.vue

@@ -0,0 +1,233 @@
+<template>
+	<view
+		class="u-notice-bar"
+		:style="{
+			background: computeBgColor,
+			padding: padding
+		}"
+		:class="[
+			type ? `u-type-${type}-light-bg` : ''
+		]"
+	>
+		<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"
+					:style="[textStyle]"
+					@tap="click(index)"
+					:class="['u-type-' + type]"
+				>
+					{{ item }}
+				</view>
+			</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>
+	</view>
+</template>
+
+<script>
+export default {
+	props: {
+		// 显示的内容,数组
+		list: {
+			type: Array,
+			default() {
+				return [];
+			}
+		},
+		// 显示的主题,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');
+		},
+		change(e) {
+			let index = e.detail.current;
+			if(index == this.list.length - 1) {
+				this.$emit('end');
+			}
+		}
+	}
+};
+</script>
+
+<style lang="scss" scoped>
+@import "../../libs/css/style.components.scss";
+
+.u-notice-bar {
+	width: 100%;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	flex-wrap: nowrap;
+	padding: 18rpx 24rpx;
+	overflow: hidden;
+}
+
+.u-swiper {
+	font-size: 26rpx;
+	height: 32rpx;
+	display: flex;
+	align-items: center;
+	flex: 1;
+	margin-left: 12rpx;
+}
+
+.u-swiper-item {
+	display: flex;
+	align-items: center;
+	overflow: hidden;
+}
+
+.u-news-item {
+	overflow: hidden;
+}
+
+.u-right-icon {
+	margin-left: 12rpx;
+	display: inline-flex;
+	align-items: center;
+}
+
+.u-left-icon {
+	display: inline-flex;
+	align-items: center;
+}
+</style>

+ 315 - 0
uview-ui/components/u-count-down/u-count-down.vue

@@ -0,0 +1,315 @@
+<template>
+	<view class="u-countdown">
+		<view class="u-countdown-item" :style="[itemStyle]" v-if="showDays && (hideZeroDay || (!hideZeroDay && d != '0'))">
+			<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 != '0'))"
+		>
+			{{ 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>
+</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';
+			}
+			if(this.showBorder) {
+				style.borderStyle = 'solid';
+				style.borderColor = this.borderColor;
+				style.borderWidth = '1px';
+			}
+			if(this.bgColor) {
+				style.backgroundColor = this.bgColor;
+			}
+			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();
+				}
+				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));
+			}
+			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;
+			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() {
+		clearInterval(this.timer);
+		this.timer = null;
+	}
+};
+</script>
+
+<style scoped lang="scss">
+	@import "../../libs/css/style.components.scss";
+
+	.u-countdown {
+		display: inline-flex;
+		align-items: center;
+	}
+
+	.u-countdown-item {
+		display: 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 {
+		display: 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;
+	}
+</style>

+ 239 - 0
uview-ui/components/u-count-to/u-count-to.vue

@@ -0,0 +1,239 @@
+<template>
+	<view
+		class="u-count-num"
+		:style="{
+			fontSize: fontSize + 'rpx',
+			fontWeight: bold ? 'bold' : 'normal',
+			color: color
+		}"
+	>
+		{{ displayValue }}
+	</view>
+</template>
+
+<script>
+/**
+ * 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)
+ * @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,
+			displayValue: this.formatNumber(this.startVal),
+			printVal: null,
+			paused: false, // 是否暂停
+			localDuration: Number(this.duration),
+			startTime: null, // 开始的时间
+			timestamp: null, // 时间戳
+			remaining: null, // 停留的时间
+			rAF: null,
+			lastTime: 0 // 上一次的时间
+		};
+	},
+	computed: {
+		countDown() {
+			return this.startVal > this.endVal;
+		}
+	},
+	watch: {
+		startVal() {
+			this.autoplay && this.start();
+		},
+		endVal() {
+			this.autoplay && this.start();
+		}
+	},
+	mounted() {
+		this.autoplay && this.start();
+	},
+	methods: {
+		easingFn(t, b, c, d) {
+			return (c * (-Math.pow(2, (-10 * t) / d) + 1) * 1024) / 1023 + b;
+		},
+		requestAnimationFrame(callback) {
+			const currTime = new Date().getTime();
+			// 为了使setTimteout的尽可能的接近每秒60帧的效果
+			const timeToCall = Math.max(0, 16 - (currTime - this.lastTime));
+			const id = setTimeout(() => {
+				callback(currTime + timeToCall);
+			}, timeToCall);
+			this.lastTime = currTime + timeToCall;
+			return id;
+		},
+
+		cancelAnimationFrame(id) {
+			clearTimeout(id);
+		},
+		// 开始滚动数字
+		start() {
+			this.localStartVal = this.startVal;
+			this.startTime = null;
+			this.localDuration = this.duration;
+			this.paused = false;
+			this.rAF = this.requestAnimationFrame(this.count);
+		},
+		// 暂定状态,重新再开始滚动;或者滚动状态下,暂停
+		reStart() {
+			if (this.paused) {
+				this.resume();
+				this.paused = false;
+			} else {
+				this.stop();
+				this.paused = true;
+			}
+		},
+		// 暂停
+		stop() {
+			this.cancelAnimationFrame(this.rAF);
+		},
+		// 重新开始(暂停的情况下)
+		resume() {
+			this.startTime = null;
+			this.localDuration = this.remaining;
+			this.localStartVal = this.printVal;
+			this.requestAnimationFrame(this.count);
+		},
+		// 重置
+		reset() {
+			this.startTime = null;
+			this.cancelAnimationFrame(this.rAF);
+			this.displayValue = this.formatNumber(this.startVal);
+		},
+		count(timestamp) {
+			if (!this.startTime) this.startTime = timestamp;
+			this.timestamp = timestamp;
+			const progress = timestamp - this.startTime;
+			this.remaining = this.localDuration - progress;
+			if (this.useEasing) {
+				if (this.countDown) {
+					this.printVal = this.localStartVal - this.easingFn(progress, 0, this.localStartVal - this.endVal, this.localDuration);
+				} else {
+					this.printVal = this.easingFn(progress, this.localStartVal, this.endVal - this.localStartVal, this.localDuration);
+				}
+			} else {
+				if (this.countDown) {
+					this.printVal = this.localStartVal - (this.localStartVal - this.endVal) * (progress / this.localDuration);
+				} else {
+					this.printVal = this.localStartVal + (this.endVal - this.localStartVal) * (progress / this.localDuration);
+				}
+			}
+			if (this.countDown) {
+				this.printVal = this.printVal < this.endVal ? this.endVal : this.printVal;
+			} else {
+				this.printVal = this.printVal > this.endVal ? this.endVal : this.printVal;
+			}
+			this.displayValue = this.formatNumber(this.printVal);
+			if (progress < this.localDuration) {
+				this.rAF = this.requestAnimationFrame(this.count);
+			} else {
+				this.$emit('end');
+			}
+		},
+		// 判断是否数字
+		isNumber(val) {
+			return !isNaN(parseFloat(val));
+		},
+		formatNumber(num) {
+			// 将num转为Number类型,因为其值可能为字符串数值,调用toFixed会报错
+			num = Number(num);
+			num = num.toFixed(Number(this.decimals));
+			num += '';
+			const x = num.split('.');
+			let x1 = x[0];
+			const x2 = x.length > 1 ? this.decimal + x[1] : '';
+			const rgx = /(\d+)(\d{3})/;
+			if (this.separator && !this.isNumber(this.separator)) {
+				while (rgx.test(x1)) {
+					x1 = x1.replace(rgx, '$1' + this.separator + '$2');
+				}
+			}
+			return x1 + x2;
+		},
+		destroyed() {
+			this.cancelAnimationFrame(this.rAF);
+		}
+	}
+};
+</script>
+
+<style lang="scss" scoped>
+@import "../../libs/css/style.components.scss";
+
+.u-count-num {
+	display: inline-block;
+	text-align: center;
+}
+</style>

+ 151 - 0
uview-ui/components/u-divider/u-divider.vue

@@ -0,0 +1,151 @@
+<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>
+</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
+		},
+		// 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');
+		}
+	}
+};
+</script>
+
+<style lang="scss" scoped>
+@import "../../libs/css/style.components.scss";
+.u-divider {
+	width: 100%;
+	position: relative;
+	text-align: center;
+	display: flex;
+	justify-content: center;
+	align-items: center;
+	overflow: hidden;
+	flex-direction: row;
+}
+
+.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-text {
+	white-space: nowrap;
+	padding: 0 16rpx;
+	display: inline-flex;
+}
+</style>

+ 242 - 0
uview-ui/components/u-dropdown-list/u-dropdown-list.vue

@@ -0,0 +1,242 @@
+<template>
+  <view class="dropdown-list-wapper u-flex u-flex-1">
+    <view
+      v-for="(drop, index) in dropdownListFromFather"
+      :key="drop.name"
+      :show="drop.show"
+      class="u-selected-class u-dropdown-list"
+      :style="{ zIndex: zIndex + 1 }"
+    >
+      <slot name="selectionbox">
+        <view
+          :style="{ height: top + 'rpx' }"
+          class="drop-item u-flex u-justify-center"
+          @click="handleDropClick(drop)"
+        >
+          <text :style="{ color: drop.show ? activeColor : '#999' }">
+            {{ getTitle(drop.options) }}
+          </text>
+          <view
+            class="u-animation"
+            :class="[drop.show ? 'u-animation-show' : '']"
+          >
+            <u-icon
+              v-if="drop.show"
+              name="arrow-up-fill"
+              :size="18"
+              :color="activeColor"
+            ></u-icon>
+            <u-icon v-else name="arrow-down-fill" :size="18"></u-icon>
+          </view>
+        </view>
+      </slot>
+      <view
+        class="u-dropdown-view"
+        :class="[drop.show ? 'u-dropdownlist-show' : '']"
+        :style="{
+          background: bgcolor,
+          height: drop.show ? 'auto' : 0,
+          top: top + 'rpx'
+        }"
+      >
+        <slot name="dropdownbox">
+          <view class="u-selected-list">
+            <view
+              class="select-item u-flex u-align-center u-border-bottom u-align-between"
+              :style="{ color: select.select ? activeColor : '#666666' }"
+              @tap="handleSelected(select, drop.options)"
+              v-for="(select, n) in drop.options"
+              :key="n"
+            >
+              <text>{{ select.text }}</text>
+              <u-icon
+                v-if="select.select"
+                class="select-icon"
+                :color="activeColor"
+                size="35"
+                name="checkmark"
+              ></u-icon>
+            </view>
+          </view>
+        </slot>
+      </view>
+    </view>
+    <u-mask
+      duration="100"
+      :show="dropdownShow"
+      @click="closeMask"
+      :z-index="zIndex"
+    ></u-mask>
+  </view>
+</template>
+
+<script>
+const dropdownOption1 = [
+  { id: 0, text: '类型', value: '', select: false },
+  { id: 1, text: '全场券', value: 1, select: false },
+  { id: 2, text: '品类券', value: 2, select: false },
+  { id: 3, text: '单品券', value: 3, select: false },
+  { id: 4, text: '业务券', value: 4, select: false }
+]
+const dropdownOption2 = [
+  { id: 5, text: '状态', value: '', select: false },
+  { id: 6, text: '可使用', value: 1, select: false },
+  { id: 7, text: '已过期', value: 2, select: false }
+]
+const dropdownOption3 = [
+  { id: 8, text: '优惠力度', value: '', select: false },
+  { id: 9, text: '满100-20', value: 1, select: false },
+  { id: 10, text: '满100-50', value: 2, select: false }
+]
+
+export default {
+  props: {
+    // 下拉框数据
+    dropdownList: {
+      type: Array,
+      default: () => [
+        { show: false, options: dropdownOption1 },
+        { show: false, options: dropdownOption2 },
+        { show: false, options: dropdownOption3 }
+      ],
+      required: true,
+      validator: value =>
+        value.every(item => Array.isArray(item.options) && item.options.length)
+    },
+    //背景颜色
+    bgcolor: {
+      type: String,
+      default: 'none'
+    },
+    //rpx 选择框高度也用这个值
+    top: {
+      type: Number,
+      default: 90
+    },
+    // 菜单标题和选项的选中态颜色
+    activeColor: {
+      type: String,
+      default: '#e7141a'
+    },
+    // mask和下拉列表的z-index
+    zIndex: {
+      type: [String, Number],
+      default: 21
+    }
+  },
+  data() {
+    return {
+      dropdownShow: false,
+      dropdownListFromFather: this.dropdownList
+    }
+  },
+  computed: {},
+  methods: {
+    getTitle(item = []) {
+      const obj = item.find(v => v.select) || {}
+      if (obj.select) {
+        return obj.text
+      } else {
+        if (item[0]) {
+          item[0].select = true
+          return item[0].text
+        }
+      }
+      return ''
+    },
+    handleDropClick(item) {
+      if (item.show) {
+        item.show = false
+        this.dropdownShow = false
+        return
+      }
+      this.dropdownListFromFather.map(item => {
+        item.show = false
+      })
+      const t = setTimeout(() => {
+        item.show = true
+        this.dropdownShow = true
+        clearTimeout(t)
+      }, 100)
+    },
+    closeMask() {
+      this.dropdownShow = false
+      this.dropdownListFromFather.map(item => {
+        item.show = false
+      })
+    },
+    handleSelected(select, options) {
+      options.map(item => {
+        item.select = false
+      })
+      select.select = true
+      this.closeMask()
+      // 返回选中对象和下拉列表数组
+      this.$emit('change', select, options)
+    }
+  },
+  watch: {
+    dropdownList: {
+      handler(v) {
+        this.dropdownListFromFather = v
+      },
+      deep: true
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+@import "../../libs/css/style.components.scss";
+.dropdown-list-wapper {
+  position: relative;
+}
+.u-dropdown-view {
+  width: 100%;
+  overflow: hidden;
+  position: absolute;
+  z-index: 9999;
+  left: 0;
+  right: 0;
+  /* opacity: 0; */
+  visibility: hidden;
+  transition: height 0.5s ease-in-out;
+  .u-selected-list {
+    background-color: #fff;
+    .select-item {
+      color: #666666;
+      font-size: 28rpx;
+      padding: 30rpx 54rpx 30rpx 30rpx;
+      margin-left: 30rpx;
+    }
+    .select-item.selectActive {
+      color: #e7141a;
+    }
+  }
+}
+
+.u-dropdownlist-show {
+  /* opacity: 1; */
+  visibility: visible;
+}
+.u-dropdown-list {
+  flex: 1;
+  // z-index: 22;
+  background: #fff;
+  position: static;
+}
+.drop-item {
+  justify-content: center;
+  color: #999999;
+  font-size: 30rpx;
+  > text {
+    margin-right: 10rpx;
+  }
+  /deep/ {
+    .uicon {
+      position: relative;
+      top: -2rpx;
+    }
+  }
+}
+</style>

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

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

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

@@ -0,0 +1,378 @@
+<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" @touchstart="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) {
+			this.focused = false;
+			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 {
+	display: flex;
+	align-items: center;
+}
+
+.u-textarea-inner {
+	align-items: flex-start;
+}
+
+.u-textarea-class {
+	min-height: 96rpx;
+	width: auto;
+	font-size: 28rpx;
+}
+
+.fild-body {
+	display: flex;
+	flex: 1;
+	align-items: center;
+}
+
+.u-arror-right {
+	margin-left: 8rpx;
+}
+
+.u-label-text {
+	display: inline-block;
+}
+
+.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;
+	display: 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 {
+	display: 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>

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