Просмотр исходного кода

促销活动,新增thorui 组件库

zhengjinyi 4 лет назад
Родитель
Сommit
a0bfb34f85
71 измененных файлов с 16322 добавлено и 221 удалено
  1. 2 0
      App.vue
  2. 0 28
      api/cart.js
  3. 1 1
      common/config/config.js
  4. 2 0
      common/css/style/icon.css
  5. 2480 0
      common/css/style/thorui.css
  6. 143 0
      components/cm-module/productDetails/cm-activipopu.vue
  7. 8 4
      components/cm-module/productDetails/cm-attributes.vue
  8. 263 60
      components/cm-module/productDetails/cm-price.vue
  9. 185 0
      components/thorui/tui-actionsheet/tui-actionsheet.vue
  10. 134 0
      components/thorui/tui-alert/tui-alert.vue
  11. 155 0
      components/thorui/tui-badge/tui-badge.vue
  12. 387 0
      components/thorui/tui-bottom-navigation/tui-bottom-navigation.vue
  13. 96 0
      components/thorui/tui-bottom-popup/tui-bottom-popup.vue
  14. 203 0
      components/thorui/tui-bubble-popup/tui-bubble-popup.vue
  15. 401 0
      components/thorui/tui-button/tui-button.vue
  16. 562 0
      components/thorui/tui-calendar/tui-calendar.js
  17. 814 0
      components/thorui/tui-calendar/tui-calendar.vue
  18. 211 0
      components/thorui/tui-card/tui-card.vue
  19. 515 0
      components/thorui/tui-cascade-selection/tui-cascade-selection.vue
  20. 251 0
      components/thorui/tui-circular-progress/tui-circular-progress.vue
  21. 166 0
      components/thorui/tui-collapse/tui-collapse.vue
  22. 257 0
      components/thorui/tui-countdown/tui-countdown.vue
  23. 537 0
      components/thorui/tui-datetime/tui-datetime.vue
  24. 103 0
      components/thorui/tui-divider/tui-divider.vue
  25. 98 0
      components/thorui/tui-drawer/tui-drawer.vue
  26. 69 0
      components/thorui/tui-dropdown-list/tui-dropdown-list.vue
  27. 255 0
      components/thorui/tui-fab/tui-fab.vue
  28. 118 0
      components/thorui/tui-footer/tui-footer.vue
  29. 137 0
      components/thorui/tui-grid-item/tui-grid-item.vue
  30. 44 0
      components/thorui/tui-grid/tui-grid.vue
  31. 55 0
      components/thorui/tui-icon/tui-icon.vue
  32. 1034 0
      components/thorui/tui-image-cropper/tui-image-cropper.vue
  33. 149 0
      components/thorui/tui-image-group/tui-image-group.vue
  34. 73 0
      components/thorui/tui-keyboard-input/tui-keyboard-input.vue
  35. 240 0
      components/thorui/tui-keyboard/tui-keyboard.vue
  36. 171 0
      components/thorui/tui-list-cell/tui-list-cell.vue
  37. 87 0
      components/thorui/tui-list-view/tui-list-view.vue
  38. 78 0
      components/thorui/tui-loading/tui-loading.vue
  39. 161 0
      components/thorui/tui-loadmore/tui-loadmore.vue
  40. 374 0
      components/thorui/tui-modal/tui-modal.vue
  41. 174 0
      components/thorui/tui-navigation-bar/tui-navigation-bar.vue
  42. 103 0
      components/thorui/tui-no-data/tui-no-data.vue
  43. 115 0
      components/thorui/tui-nomore/tui-nomore.vue
  44. 205 0
      components/thorui/tui-numberbox/tui-numberbox.vue
  45. 679 0
      components/thorui/tui-picture-cropper/tui-picture-cropper.vue
  46. 560 0
      components/thorui/tui-picture-cropper/tui-picture-cropper.wxs
  47. 154 0
      components/thorui/tui-rate/tui-rate.vue
  48. 307 0
      components/thorui/tui-round-progress/tui-round-progress.vue
  49. 178 0
      components/thorui/tui-scroll-top/tui-scroll-top.vue
  50. 230 0
      components/thorui/tui-skeleton/tui-skeleton.vue
  51. 187 0
      components/thorui/tui-steps/tui-steps.vue
  52. 124 0
      components/thorui/tui-sticky-wxs/tui-sticky-wxs.vue
  53. 44 0
      components/thorui/tui-sticky-wxs/tui-sticky.wxs
  54. 152 0
      components/thorui/tui-sticky/tui-sticky.vue
  55. 275 0
      components/thorui/tui-swipe-action/tui-swipe-action.vue
  56. 262 0
      components/thorui/tui-tabbar/tui-tabbar.vue
  57. 232 0
      components/thorui/tui-tabs/tui-tabs.vue
  58. 358 0
      components/thorui/tui-tag/tui-tag.vue
  59. 38 0
      components/thorui/tui-time-axis/tui-time-axis.vue
  60. 50 0
      components/thorui/tui-timeaxis-item/tui-timeaxis-item.vue
  61. 161 0
      components/thorui/tui-tips/tui-tips.vue
  62. 118 0
      components/thorui/tui-toast/tui-toast.vue
  63. 104 0
      components/thorui/tui-top-dropdown/tui-top-dropdown.vue
  64. 341 0
      components/thorui/tui-upload/tui-upload.vue
  65. 6 0
      pages.json
  66. 3 4
      pages/goods/cart.vue
  67. 86 81
      pages/goods/product.vue
  68. 39 41
      pages/tabBar/cart/cart.vue
  69. 1 1
      services/ajax.env.js
  70. 17 0
      services/product.service.js
  71. 0 1
      supplier/pages/deliver/logistics-list.vue

+ 2 - 0
App.vue

@@ -89,6 +89,8 @@
 	/*每个页面公共css */
 	@import "@/common/css/common.scss";
 	@import "@/common/css/iconfont.scss";
+	@import "@/common/css/style/thorui.css";
+	@import "@/common/css/style/icon.css";
 
 view,
 scroll-view,

+ 0 - 28
api/cart.js

@@ -22,34 +22,6 @@ export function queryShoppingCartList(params) {
 		})
 	})
 }
-/**
- *更新购物车商品增减
- */
-export function shoppingCartUpdate(params) {
-	return new Promise(function(resolve,reject) {
-		request.post('/shoppingCart/update',params, true, res => {
-			if(res.code == 0){
-				resolve(res)
-			}else{
-				reject(res)
-			}
-		})
-	})
-}
-/**
- *购物车删除商品
- */
-export function shoppingCartDelete(params) {
-	return new Promise(function(resolve,reject) {
-		request.post('/shoppingCart/delete',params, true, res => {
-			if(res.code == 0){
-				resolve(res)
-			}else{
-				reject(res)
-			}
-		})
-	})
-}
 /**
  *查询地址列表
  */

+ 1 - 1
common/config/config.js

@@ -1,7 +1,7 @@
 let URL_CONFIG = ""
 if(process.env.NODE_ENV === 'development'){
     // 开发环境
-	// URL_CONFIG = 'http://192.168.2.56:8008'	 //本地IP联调地址
+	// URL_CONFIG = 'http://192.168.2.68:8008'	 //本地IP联调地址
 	// URL_CONFIG = 'http://192.168.2.67:8008'	 //裴裴联调地址
 	URL_CONFIG = 'http://192.168.2.75:8008'	 //超超联调地址
     // URL_CONFIG = 'https://spi-b.caimei365.com'	 //采美测试地址

Разница между файлами не показана из-за своего большого размера
+ 2 - 0
common/css/style/icon.css


+ 2480 - 0
common/css/style/thorui.css

@@ -0,0 +1,2480 @@
+/* Thor UI 基础组件 样式*/
+
+/*!
+ * =====================================================
+ * Thor UI v1.5.0 (https://www.thorui.cn/)
+ * =====================================================
+ */
+.tui-mask {
+	width: 100%;
+	height: 100%;
+	position: fixed;
+	top: 0;
+	left: 0;
+	background: rgba(0, 0, 0, 0.4);
+	z-index: 999;
+}
+
+.tui-ellipsis {
+	overflow: hidden;
+	white-space: nowrap;
+	text-overflow: ellipsis;
+}
+
+.tui-ellipsis-2 {
+	display: -webkit-box;
+	overflow: hidden;
+	white-space: normal !important;
+	text-overflow: ellipsis;
+	word-wrap: break-word;
+	-webkit-line-clamp: 2;
+	-webkit-box-orient: vertical;
+}
+
+.tui-opcity {
+	opacity: 0.5;
+}
+
+.tui-scale-small {
+	transform: scale(0.9);
+	transform-origin: center center;
+}
+
+.tui-height-full {
+	height: 100%;
+}
+
+.tui-width-full {
+	width: 100%;
+}
+
+.tui-ptop-zero {
+	padding-top: 0;
+}
+
+.tui-pbottom-zero {
+	padding-bottom: 0;
+}
+
+.tui-pleft-zero {
+	padding-left: 0;
+}
+
+.tui-pright-zero {
+	padding-right: 0;
+}
+
+
+/* color start*/
+
+.tui-color-primary {
+	color: #5677fc;
+}
+
+.tui-color-success {
+	color: #19be6b;
+}
+
+.tui-color-warning {
+	color: #ff7900;
+}
+
+.tui-color-error {
+	color: #EB0909;
+}
+
+.tui-color-blue {
+	color: #007AFF;
+}
+
+.tui-color-blue {
+	color: #007AFF;
+}
+
+.tui-color-white {
+	color: #ffffff;
+}
+
+.tui-color-pink {
+	color: #f74d54;
+}
+
+.tui-primary {
+	background-color: #5677fc !important;
+	color: #fff;
+}
+
+.tui-light-primary {
+	background-color: #5c8dff !important;
+	color: #fff;
+}
+
+.tui-dark-primary {
+	background-color: #4a67d6 !important;
+	color: #fff;
+}
+
+.tui-dLight-primary {
+	background-color: #4e77d9 !important;
+	color: #fff;
+}
+
+.tui-danger {
+	background-color: #ed3f14 !important;
+	color: #fff;
+}
+
+.tui-warning {
+	background-color: #ff7900 !important;
+	color: #fff;
+}
+
+.tui-blue {
+	background-color: #007AFF !important;
+	color: #fff;
+}
+
+.tui-green {
+	background-color: #19be6b !important;
+	color: #fff;
+}
+
+.tui-black {
+	background-color: #000 !important;
+	color: #fff;
+}
+
+.tui-white {
+	background-color: #fff !important;
+	color: #333 !important;
+}
+
+.tui-translucent {
+	background-color: rgba(0, 0, 0, 0.7);
+}
+
+.tui-light-black {
+	background-color: #333 !important;
+}
+
+.tui-gray {
+	background-color: #80848f;
+}
+
+.tui-phcolor-gray {
+	background-color: #ccc !important;
+}
+
+.tui-divider-gray {
+	background-color: #eaeef1 !important;
+}
+
+.tui-btn-gray {
+	background-color: #ededed !important;
+	color: #999 !important;
+}
+
+.tui-hover-gray {
+	background-color: #f7f7f9 !important;
+}
+
+.tui-bg-gray {
+	background-color: #fafafa !important;
+}
+
+.tui-light-blue {
+	background-color: #ecf6fd;
+	color: #4dabeb !important;
+}
+
+.tui-light-brownish {
+	background-color: #fcebef;
+	color: #8a5966 !important;
+}
+
+.tui-light-orange {
+	background-color: #fef5eb;
+	color: #faa851 !important;
+}
+
+.tui-light-green {
+	background-color: #e8f6e8;
+	color: #44cf85 !important;
+}
+
+/* color end*/
+
+
+/* flex start */
+
+.tui-flex {
+	display: -webkit-flex;
+	display: flex;
+}
+
+.tui-flex-1 {
+	flex: 1;
+}
+
+.tui-align-center {
+	justify-content: center;
+}
+
+.tui-align-left {
+	justify-content: flex-start !important;
+}
+
+.tui-align-right {
+	justify-content: flex-end !important;
+}
+
+.tui-align-between {
+	justify-content: space-between !important;
+}
+
+.tui-align-around {
+	justify-content: space-around !important;
+}
+
+.tui-vertical-center {
+	align-items: center;
+}
+
+.tui-vertical-top {
+	align-items: flex-start;
+}
+
+.tui-vertical-top {
+	align-items: flex-end;
+}
+
+.tui-center {
+	display: flex;
+	align-items: center;
+	justify-content: center;
+}
+
+.tui-line-feed {
+	flex-direction: row;
+	flex-wrap: wrap;
+}
+
+.tui-col-12 {
+	width: 100%;
+}
+
+.tui-col-11 {
+	width: 91.66666667%;
+}
+
+.tui-col-10 {
+	width: 83.33333333%;
+}
+
+.tui-col-9 {
+	width: 75%;
+}
+
+.tui-col-8 {
+	width: 66.66666667%;
+}
+
+.tui-col-7 {
+	width: 58.33333333%;
+}
+
+.tui-col-6 {
+	width: 50%;
+}
+
+.tui-col-5 {
+	width: 41.66666667%;
+}
+
+.tui-col-4 {
+	width: 33.33333333%;
+}
+
+.tui-col-3 {
+	width: 25%;
+}
+
+.tui-col-2 {
+	width: 16.66666667%;
+}
+
+.tui-col-1 {
+	width: 8.33333333%;
+}
+
+/* flex end */
+
+
+/* icon start */
+.tui-icon {
+	font-family: 'thoruiFont' !important;
+	font-style: normal;
+	-webkit-font-smoothing: antialiased;
+	text-align: center;
+	text-decoration: none;
+	font-size: 32px;
+	color: #999;
+}
+
+.tui-icon-about:before {
+	content: '\e772';
+}
+
+.tui-icon-about-fill:before {
+	content: '\e771';
+}
+
+.tui-icon-add:before {
+	content: '\e770';
+}
+
+.tui-icon-add-fill:before {
+	content: '\e76f';
+}
+
+.tui-icon-addmessage:before {
+	content: '\e76e';
+}
+
+.tui-icon-addressbook:before {
+	content: '\e76d';
+}
+
+.tui-icon-agree:before {
+	content: '\e76c';
+}
+
+.tui-icon-agree-fill:before {
+	content: '\e76b';
+}
+
+.tui-icon-alarm:before {
+	content: '\e76a';
+}
+
+.tui-icon-alarm-fill:before {
+	content: '\e769';
+}
+
+.tui-icon-alipay:before {
+	content: '\e768';
+}
+
+.tui-icon-android:before {
+	content: '\e767';
+}
+
+.tui-icon-applets:before {
+	content: '\e766';
+}
+
+.tui-icon-arrowdown:before {
+	content: '\e765';
+}
+
+.tui-icon-arrowleft:before {
+	content: '\e764';
+}
+
+.tui-icon-arrowright:before {
+	content: '\e763';
+}
+
+.tui-icon-arrowup:before {
+	content: '\e762';
+}
+
+.tui-icon-attestation:before {
+	content: '\e761';
+}
+
+.tui-icon-back:before {
+	content: '\e760';
+}
+
+.tui-icon-bag:before {
+	content: '\e75f';
+}
+
+.tui-icon-bag-fill:before {
+	content: '\e75e';
+}
+
+.tui-icon-balloon:before {
+	content: '\e75d';
+}
+
+.tui-icon-bankcard:before {
+	content: '\e75c';
+}
+
+.tui-icon-bankcard-fill:before {
+	content: '\e75b';
+}
+
+.tui-icon-bottom:before {
+	content: '\e75a';
+}
+
+.tui-icon-calendar:before {
+	content: '\e759';
+}
+
+.tui-icon-camera:before {
+	content: '\e758';
+}
+
+.tui-icon-camera-fill:before {
+	content: '\e757';
+}
+
+.tui-icon-camera-add:before {
+	content: '\e756';
+}
+
+.tui-icon-card:before {
+	content: '\e755';
+}
+
+.tui-icon-card-fill:before {
+	content: '\e754';
+}
+
+.tui-icon-cart:before {
+	content: '\e753';
+}
+
+.tui-icon-cart-fill:before {
+	content: '\e752';
+}
+
+.tui-icon-category:before {
+	content: '\e751';
+}
+
+.tui-icon-category-fill:before {
+	content: '\e750';
+}
+
+.tui-icon-check:before {
+	content: '\e74f';
+}
+
+.tui-icon-circle:before {
+	content: '\e74e';
+}
+
+.tui-icon-circle-fill:before {
+	content: '\e74d';
+}
+
+.tui-icon-circle-selected:before {
+	content: '\e74c';
+}
+
+.tui-icon-clock:before {
+	content: '\e74b';
+}
+
+.tui-icon-clock-fill:before {
+	content: '\e74a';
+}
+
+.tui-icon-close:before {
+	content: '\e749';
+}
+
+.tui-icon-close-fill:before {
+	content: '\e748';
+}
+
+.tui-icon-community:before {
+	content: '\e747';
+}
+
+.tui-icon-community-fill:before {
+	content: '\e746';
+}
+
+.tui-icon-computer:before {
+	content: '\e745';
+}
+
+.tui-icon-computer-fill:before {
+	content: '\e744';
+}
+
+.tui-icon-coupon:before {
+	content: '\e743';
+}
+
+.tui-icon-delete:before {
+	content: '\e742';
+}
+
+.tui-icon-deletekey:before {
+	content: '\e741';
+}
+
+.tui-icon-dingtalk:before {
+	content: '\e740';
+}
+
+.tui-icon-dissatisfied:before {
+	content: '\e73f';
+}
+
+.tui-icon-down:before {
+	content: '\e73e';
+}
+
+.tui-icon-download:before {
+	content: '\e73d';
+}
+
+.tui-icon-edit:before {
+	content: '\e73c';
+}
+
+.tui-icon-ellipsis:before {
+	content: '\e73b';
+}
+
+.tui-icon-enlarge:before {
+	content: '\e73a';
+}
+
+.tui-icon-evaluate:before {
+	content: '\e739';
+}
+
+.tui-icon-exchange:before {
+	content: '\e738';
+}
+
+.tui-icon-explain:before {
+	content: '\e737';
+}
+
+.tui-icon-explain-fill:before {
+	content: '\e736';
+}
+
+.tui-icon-explore:before {
+	content: '\e735';
+}
+
+.tui-icon-explore-fill:before {
+	content: '\e734';
+}
+
+.tui-icon-eye:before {
+	content: '\e733';
+}
+
+.tui-icon-feedback:before {
+	content: '\e732';
+}
+
+.tui-icon-fingerprint:before {
+	content: '\e730';
+}
+
+.tui-icon-friendadd:before {
+	content: '\e72f';
+}
+
+.tui-icon-friendadd-fill:before {
+	content: '\e72e';
+}
+
+.tui-icon-gps:before {
+	content: '\e72d';
+}
+
+.tui-icon-histogram:before {
+	content: '\e72c';
+}
+
+.tui-icon-home:before {
+	content: '\e72b';
+}
+
+.tui-icon-home-fill:before {
+	content: '\e72a';
+}
+
+.tui-icon-house:before {
+	content: '\e729';
+}
+
+.tui-icon-imface:before {
+	content: '\e728';
+}
+
+.tui-icon-imkeyboard:before {
+	content: '\e727';
+}
+
+.tui-icon-immore:before {
+	content: '\e726';
+}
+
+.tui-icon-imvoice:before {
+	content: '\e725';
+}
+
+.tui-icon-ios:before {
+	content: '\e724';
+}
+
+.tui-icon-kefu:before {
+	content: '\e723';
+}
+
+.tui-icon-label:before {
+	content: '\e722';
+}
+
+.tui-icon-label-fill:before {
+	content: '\e721';
+}
+
+.tui-icon-like:before {
+	content: '\e720';
+}
+
+.tui-icon-like-fill:before {
+	content: '\e71f';
+}
+
+.tui-icon-link:before {
+	content: '\e71e';
+}
+
+.tui-icon-listview:before {
+	content: '\e71d';
+}
+
+.tui-icon-loading:before {
+	content: '\e71c';
+}
+
+.tui-icon-location:before {
+	content: '\e71b';
+}
+
+.tui-icon-mail:before {
+	content: '\e71a';
+}
+
+.tui-icon-mail-fill:before {
+	content: '\e719';
+}
+
+.tui-icon-manage:before {
+	content: '\e718';
+}
+
+.tui-icon-manage-fill:before {
+	content: '\e717';
+}
+
+.tui-icon-member:before {
+	content: '\e716';
+}
+
+.tui-icon-member-fill:before {
+	content: '\e715';
+}
+
+.tui-icon-message:before {
+	content: '\e714';
+}
+
+.tui-icon-message-fill:before {
+	content: '\e713';
+}
+
+.tui-icon-mobile:before {
+	content: '\e712';
+}
+
+.tui-icon-moments:before {
+	content: '\e711';
+}
+
+.tui-icon-more:before {
+	content: '\e710';
+}
+
+.tui-icon-more-fill:before {
+	content: '\e70f';
+}
+
+.tui-icon-narrow:before {
+	content: '\e70e';
+}
+
+.tui-icon-news:before {
+	content: '\e70d';
+}
+
+.tui-icon-news-fill:before {
+	content: '\e70c';
+}
+
+.tui-icon-nodata:before {
+	content: '\e70b';
+}
+
+.tui-icon-notice:before {
+	content: '\e699';
+}
+
+.tui-icon-notice-fill:before {
+	content: '\e698';
+}
+
+.tui-icon-offline:before {
+	content: '\e697';
+}
+
+.tui-icon-offline-fill:before {
+	content: '\e696';
+}
+
+.tui-icon-oppose:before {
+	content: '\e695';
+}
+
+.tui-icon-oppose-fill:before {
+	content: '\e694';
+}
+
+.tui-icon-order:before {
+	content: '\e693';
+}
+
+.tui-icon-partake:before {
+	content: '\e692';
+}
+
+.tui-icon-people:before {
+	content: '\e691';
+}
+
+.tui-icon-people-fill:before {
+	content: '\e690';
+}
+
+.tui-icon-pic:before {
+	content: '\e68f';
+}
+
+.tui-icon-pic-fill:before {
+	content: '\e68e';
+}
+
+.tui-icon-picture:before {
+	content: '\e68d';
+}
+
+.tui-icon-pie:before {
+	content: '\e68c';
+}
+
+.tui-icon-play:before {
+	content: '\e68b';
+}
+
+.tui-icon-plus:before {
+	content: '\e689';
+}
+
+.tui-icon-polygonal:before {
+	content: '\e688';
+}
+
+.tui-icon-position:before {
+	content: '\e687';
+}
+
+.tui-icon-position-fill:before {
+	content: '\e686';
+}
+
+.tui-icon-pwd:before {
+	content: '\e685';
+}
+
+.tui-icon-qq:before {
+	content: '\e684';
+}
+
+.tui-icon-qrcode:before {
+	content: '\e682';
+}
+
+.tui-icon-redpacket:before {
+	content: '\e681';
+}
+
+.tui-icon-redpacket-fill:before {
+	content: '\e680';
+}
+
+.tui-icon-reduce:before {
+	content: '\e67f';
+}
+
+.tui-icon-refresh:before {
+	content: '\e67e';
+}
+
+.tui-icon-revoke:before {
+	content: '\e67d';
+}
+
+.tui-icon-satisfied:before {
+	content: '\e67c';
+}
+
+.tui-icon-screen:before {
+	content: '\e67b';
+}
+
+.tui-icon-search:before {
+	content: '\e67a';
+}
+
+.tui-icon-search-2:before {
+	content: '\e679';
+}
+
+.tui-icon-send:before {
+	content: '\e678';
+}
+
+.tui-icon-service:before {
+	content: '\e677';
+}
+
+.tui-icon-service-fill:before {
+	content: '\e676';
+}
+
+.tui-icon-setup:before {
+	content: '\e675';
+}
+
+.tui-icon-setup-fill:before {
+	content: '\e674';
+}
+
+.tui-icon-share:before {
+	content: '\e673';
+}
+
+.tui-icon-share-fill:before {
+	content: '\e672';
+}
+
+.tui-icon-shield:before {
+	content: '\e671';
+}
+
+.tui-icon-shop:before {
+	content: '\e670';
+}
+
+.tui-icon-shop-fill:before {
+	content: '\e66f';
+}
+
+.tui-icon-shut:before {
+	content: '\e66e';
+}
+
+.tui-icon-signin:before {
+	content: '\e66d';
+}
+
+.tui-icon-sina:before {
+	content: '\e66c';
+}
+
+.tui-icon-skin:before {
+	content: '\e66b';
+}
+
+.tui-icon-soso:before {
+	content: '\e669';
+}
+
+.tui-icon-square:before {
+	content: '\e668';
+}
+
+.tui-icon-square-fill:before {
+	content: '\e667';
+}
+
+.tui-icon-square-selected:before {
+	content: '\e666';
+}
+
+.tui-icon-star:before {
+	content: '\e665';
+}
+
+.tui-icon-star-fill:before {
+	content: '\e664';
+}
+
+.tui-icon-strategy:before {
+	content: '\e663';
+}
+
+.tui-icon-sweep:before {
+	content: '\e662';
+}
+
+.tui-icon-time:before {
+	content: '\e661';
+}
+
+.tui-icon-time-fill:before {
+	content: '\e660';
+}
+
+.tui-icon-todown:before {
+	content: '\e65f';
+}
+
+.tui-icon-toleft:before {
+	content: '\e65e';
+}
+
+.tui-icon-tool:before {
+	content: '\e65d';
+}
+
+.tui-icon-top:before {
+	content: '\e65c';
+}
+
+.tui-icon-toright:before {
+	content: '\e65b';
+}
+
+.tui-icon-towardsleft:before {
+	content: '\e65a';
+}
+
+.tui-icon-towardsright:before {
+	content: '\e659';
+}
+
+.tui-icon-towardsright-fill:before {
+	content: '\e658';
+}
+
+.tui-icon-transport:before {
+	content: '\e657';
+}
+
+.tui-icon-transport-fill:before {
+	content: '\e656';
+}
+
+.tui-icon-turningdown:before {
+	content: '\e654';
+}
+
+.tui-icon-turningleft:before {
+	content: '\e653';
+}
+
+.tui-icon-turningright:before {
+	content: '\e652';
+}
+
+.tui-icon-turningup:before {
+	content: '\e651';
+}
+
+.tui-icon-unreceive:before {
+	content: '\e650';
+}
+
+.tui-icon-unseen:before {
+	content: '\e64f';
+}
+
+.tui-icon-up:before {
+	content: '\e64e';
+}
+
+.tui-icon-upload:before {
+	content: '\e64c';
+}
+
+.tui-icon-video:before {
+	content: '\e64b';
+}
+
+.tui-icon-voice:before {
+	content: '\e649';
+}
+
+.tui-icon-voice-fill:before {
+	content: '\e648';
+}
+
+.tui-icon-voipphone:before {
+	content: '\e647';
+}
+
+.tui-icon-wallet:before {
+	content: '\e646';
+}
+
+.tui-icon-warning:before {
+	content: '\e645';
+}
+
+.tui-icon-wealth:before {
+	content: '\e644';
+}
+
+.tui-icon-wealth-fill:before {
+	content: '\e643';
+}
+
+.tui-icon-weather:before {
+	content: '\e642';
+}
+
+.tui-icon-wechat:before {
+	content: '\e641';
+}
+
+.tui-icon-wifi:before {
+	content: '\e640';
+}
+
+/* icon end */
+
+/*Button start*/
+.tui-btn-primary {
+	background: #5677fc !important;
+	color: #fff;
+}
+
+.tui-shadow-primary {
+	box-shadow: 0 10rpx 14rpx 0 rgba(86, 119, 252, 0.2);
+}
+
+.tui-btn-danger {
+	background: #eb0909 !important;
+	color: #fff;
+}
+
+.tui-shadow-danger {
+	box-shadow: 0 10rpx 14rpx 0 rgba(235, 9, 9, 0.2);
+}
+
+.tui-btn-warning {
+	background: #fc872d !important;
+	color: #fff;
+}
+
+.tui-shadow-warning {
+	box-shadow: 0 10rpx 14rpx 0 rgba(252, 135, 45, 0.2);
+}
+
+.tui-btn-green {
+	background: #35b06a !important;
+	color: #fff;
+}
+
+.tui-shadow-green {
+	box-shadow: 0 10rpx 14rpx 0 rgba(53, 176, 106, 0.2);
+}
+
+.tui-btn-blue {
+	background: #007AFF !important;
+	color: #fff;
+}
+
+.tui-shadow-blue {
+	box-shadow: 0 10rpx 14rpx 0 rgba(0, 122, 255, 0.2);
+}
+
+.tui-btn-white {
+	background: #fff !important;
+	color: #333 !important;
+}
+
+.tui-btn-gray {
+	background: #bfbfbf !important;
+	color: #fff !important;
+}
+
+.tui-btn-black {
+	background: #333 !important;
+	color: #fff !important;
+}
+
+.tui-shadow-gray {
+	box-shadow: 0 10rpx 14rpx 0 rgba(191, 191, 191, 0.2);
+}
+
+.tui-hover-gray {
+	background: #f7f7f9 !important;
+}
+
+.tui-black-hover {
+	background: #555 !important;
+	color: #e5e5e5 !important;
+}
+
+/* button start 
+ * 宽高通过覆盖样式改变
+**/
+
+.tui-btn {
+	width: 100%;
+	height: 96rpx;
+	line-height: 96rpx;
+	font-size: 32rpx;
+	position: relative;
+	border: 0 !important;
+	border-radius: 6rpx;
+	padding-left: 0;
+	padding-right: 0;
+	overflow: visible;
+}
+
+.tui-btn-base {
+	width: auto;
+	/* #ifdef MP-ALIPAY */
+	min-width: 200rpx;
+	/* #endif */
+	font-size: 30rpx;
+	height: 70rpx;
+	line-height: 70rpx;
+}
+
+.tui-btn-small {
+	width: auto;
+	/* #ifdef MP-ALIPAY */
+	min-width: 140rpx;
+	/* #endif */
+	font-size: 30rpx;
+	height: 60rpx;
+	line-height: 60rpx;
+}
+
+.tui-btn::after {
+	content: '';
+	position: absolute;
+	width: 200%;
+	height: 200%;
+	transform-origin: 0 0;
+	transform: scale(0.5, 0.5) translateZ(0);
+	box-sizing: border-box;
+	left: 0;
+	top: 0;
+	border-radius: 12rpx;
+	border: 0;
+}
+
+.tui-btn-white::after {
+	border: 1rpx solid #bfbfbf;
+}
+
+.tui-white-hover {
+	background: #e5e5e5 !important;
+	color: #2e2e2e !important;
+}
+
+.tui-dark-disabled {
+	opacity: 0.6 !important;
+	color: #fafbfc !important;
+}
+
+.tui-dark-disabled-outline {
+	opacity: 0.5 !important;
+}
+
+.tui-gray-disabled {
+	background: #f3f3f3 !important;
+	color: #919191 !important;
+	box-shadow: none;
+}
+
+.tui-outline-hover {
+	opacity: 0.5;
+}
+
+.tui-primary-hover {
+	background: #4a67d6 !important;
+	color: #e5e5e5 !important;
+}
+
+.tui-primary-outline::after {
+	border: 1rpx solid #5677fc !important;
+}
+
+.tui-primary-outline {
+	color: #5677fc !important;
+	background: transparent;
+}
+
+.tui-danger-hover {
+	background: #c80808 !important;
+	color: #e5e5e5 !important;
+}
+
+.tui-danger-outline {
+	color: #eb0909 !important;
+	background: transparent;
+}
+
+.tui-danger-outline::after {
+	border: 1rpx solid #eb0909 !important;
+}
+
+.tui-warning-hover {
+	background: #d67326 !important;
+	color: #e5e5e5 !important;
+}
+
+.tui-warning-outline {
+	color: #fc872d !important;
+	background: transparent;
+}
+
+.tui-warning-outline::after {
+	border: 1px solid #fc872d !important;
+}
+
+.tui-green-hover {
+	background: #2d965a !important;
+	color: #e5e5e5 !important;
+}
+
+.tui-green-outline {
+	color: #35b06a !important;
+	background: transparent;
+}
+
+.tui-green-outline::after {
+	border: 1rpx solid #35b06a !important;
+}
+
+.tui-blue-hover {
+	background: #0062CC !important;
+	color: #e5e5e5 !important;
+}
+
+.tui-blue-outline {
+	color: #007AFF !important;
+	background: transparent;
+}
+
+.tui-blue-outline::after {
+	border: 1rpx solid #007AFF !important;
+}
+
+/* #ifndef APP-NVUE */
+.tui-btn-gradual {
+	background: linear-gradient(90deg, rgb(255, 89, 38), rgb(240, 14, 44)) !important;
+	color: #fff !important;
+}
+
+.tui-shadow-gradual {
+	box-shadow: 0 10rpx 14rpx 0 rgba(235, 9, 9, 0.15);
+}
+
+/* #endif */
+
+.tui-gray-hover {
+	background: #a3a3a3 !important;
+	color: #898989;
+}
+
+/* #ifndef APP-NVUE */
+.tui-gradual-hover {
+	background: linear-gradient(90deg, #d74620, #cd1225) !important;
+	color: #fff !important;
+}
+
+/* #endif */
+
+.tui-gray-outline {
+	color: #999 !important;
+	background: transparent !important;
+}
+
+.tui-white-outline {
+	color: #fff !important;
+	background: transparent !important;
+}
+
+.tui-black-outline {
+	background: transparent !important;
+	color: #333 !important;
+}
+
+.tui-gray-outline::after {
+	border: 1rpx solid #ccc !important;
+}
+
+.tui-white-outline::after {
+	border: 1px solid #fff !important;
+}
+
+.tui-black-outline::after {
+	border: 1px solid #333 !important;
+}
+
+/*圆角 */
+
+.tui-fillet {
+	border-radius: 50rpx;
+}
+
+.tui-btn-white.tui-fillet::after {
+	border-radius: 98rpx;
+}
+
+.tui-outline-fillet::after {
+	border-radius: 98rpx;
+}
+
+/*平角*/
+.tui-rightAngle {
+	border-radius: 0;
+}
+
+.tui-btn-white.tui-rightAngle::after {
+	border-radius: 0;
+}
+
+.tui-outline-rightAngle::after {
+	border-radius: 0;
+}
+
+/*Button end*/
+
+/*Tag start*/
+
+.tui-tag {
+	padding: 16rpx 26rpx;
+	font-size: 28rpx;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	border-radius: 6rpx;
+}
+
+.tui-tag-outline {
+	position: relative;
+	background-color: none;
+	color: #5677fc;
+}
+
+.tui-tag-outline::after {
+	content: '';
+	position: absolute;
+	width: 200%;
+	height: 200%;
+	-webkit-transform-origin: 0 0;
+	transform-origin: 0 0;
+	-webkit-transform: scale(0.5, 0.5);
+	transform: scale(0.5, 0.5);
+	-webkit-box-sizing: border-box;
+	box-sizing: border-box;
+	left: 0;
+	top: 0;
+	border-radius: 12rpx;
+}
+
+.tui-tag-fillet {
+	border-radius: 50rpx;
+}
+
+.tui-white.tui-tag-fillet::after {
+	border-radius: 80rpx;
+}
+
+.tui-tag-outline-fillet::after {
+	border-radius: 80rpx;
+}
+
+.tui-tag-fillet-left {
+	border-radius: 50rpx 0 0 50rpx;
+}
+
+.tui-tag-fillet-right {
+	border-radius: 0 50rpx 50rpx 0;
+}
+
+.tui-tag-fillet-left.tui-tag-outline::after {
+	border-radius: 100rpx 0 0 100rpx;
+}
+
+.tui-tag-fillet-right.tui-tag-outline::after {
+	border-radius: 0 100rpx 100rpx 0;
+}
+
+/*Tag end*/
+
+/*Badge start*/
+.tui-badge-dot {
+	height: 16rpx;
+	width: 16rpx;
+	border-radius: 50%;
+}
+
+.tui-badge {
+	font-size: 24rpx;
+	height: 36rpx;
+	min-width: 12rpx;
+	padding: 0 12rpx;
+	border-radius: 36rpx;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	z-index: 10;
+}
+
+.tui-badge-scale {
+	transform-origin: center center;
+}
+
+/*Badge end*/
+
+/*List start*/
+.tui-list-cell {
+	position: relative;
+	width: 100%;
+	padding: 26rpx 30rpx;
+	background-color: #fff;
+	box-sizing: border-box;
+}
+
+.tui-list-radius {
+	border-radius: 6rpx;
+	overflow: hidden;
+}
+
+.tui-cell-hover {
+	background-color: #f1f1f1 !important;
+}
+
+.tui-list-cell::after {
+	content: '';
+	position: absolute;
+	border-bottom: 1rpx solid #eaeef1;
+	-webkit-transform: scaleY(0.5) translateZ(0);
+	transform: scaleY(0.5) translateZ(0);
+	transform-origin: 0 100%;
+	bottom: 0;
+	right: 0;
+	left: 0;
+}
+
+.tui-line-left::after {
+	left: 30rpx !important;
+}
+
+.tui-line-right::after {
+	right: 30rpx !important;
+}
+
+.tui-cell-unlined::after {
+	border-bottom: 0 !important;
+}
+
+.tui-cell-arrow::before {
+	content: ' ';
+	height: 10px;
+	width: 10px;
+	border-width: 2px 2px 0 0;
+	border-color: #c0c0c0;
+	border-style: solid;
+	-webkit-transform: matrix(0.5, 0.5, -0.5, 0.5, 0, 0);
+	transform: matrix(0.5, 0.5, -0.5, 0.5, 0, 0);
+	position: absolute;
+	top: 50%;
+	margin-top: -6px;
+	right: 30rpx;
+}
+
+.tui-arrow-right::before {
+	right: 0 !important;
+}
+
+.tui-arrow-gray::before {
+	border-color: #666666 !important;
+}
+
+.tui-arrow-white::before {
+	border-color: #ffffff !important;
+}
+
+.tui-arrow-warning::before {
+	border-color: #ff7900 !important;
+}
+
+.tui-arrow-success::before {
+	border-color: #19be6b !important;
+}
+
+.tui-arrow-danger::before {
+	border-color: #eb0909 !important;
+}
+
+/*List end*/
+
+/*Card start*/
+.tui-card {
+	margin: 0 30rpx;
+	font-size: 28rpx;
+	background-color: #fff;
+	border-radius: 10rpx;
+	box-shadow: 0 0 10rpx #eee;
+	box-sizing: border-box;
+	overflow: hidden;
+}
+
+.tui-card-full {
+	margin: 0 !important;
+	border-radius: 0 !important;
+}
+
+.tui-card-full::after {
+	border-radius: 0 !important;
+}
+
+.tui-card-border {
+	position: relative;
+	box-shadow: none !important
+}
+
+.tui-card-border::after {
+	content: ' ';
+	position: absolute;
+	height: 200%;
+	width: 200%;
+	border: 1px solid #ddd;
+	transform-origin: 0 0;
+	-webkit-transform-origin: 0 0;
+	-webkit-transform: scale(0.5);
+	transform: scale(0.5);
+	left: 0;
+	top: 0;
+	border-radius: 20rpx;
+	box-sizing: border-box;
+	pointer-events: none;
+}
+
+.tui-card-header {
+	width: 100%;
+	padding: 20rpx;
+	display: flex;
+	align-items: center;
+	justify-content: space-between;
+	position: relative;
+	box-sizing: border-box;
+	overflow: hidden;
+	border-top-left-radius: 10rpx;
+	border-top-right-radius: 10rpx;
+}
+
+.tui-card-header::after {
+	content: '';
+	position: absolute;
+	border-bottom: 1rpx solid #eaeef1;
+	-webkit-transform: scaleY(0.5);
+	transform: scaleY(0.5);
+	bottom: 0;
+	right: 0;
+	left: 0;
+	pointer-events: none;
+}
+
+.tui-header-line::after {
+	border-bottom: 0 !important;
+}
+
+.tui-header-thumb {
+	height: 60rpx;
+	width: 60rpx;
+	vertical-align: middle;
+	margin-right: 20rpx;
+	border-radius: 6rpx;
+}
+
+.tui-thumb-circle {
+	border-radius: 50% !important;
+}
+
+.tui-header-title {
+	display: inline-block;
+	font-size: 30rpx;
+	color: #7a7a7a;
+	vertical-align: middle;
+	max-width: 460rpx;
+	overflow: hidden;
+	white-space: nowrap;
+	text-overflow: ellipsis;
+}
+
+.tui-header-right {
+	font-size: 24rpx;
+	color: #b2b2b2;
+}
+
+.tui-card-body {
+	font-size: 32rpx;
+	color: #262b3a;
+	box-sizing: border-box;
+}
+
+.tui-card-footer {
+	font-size: 28rpx;
+	color: #596d96;
+	border-bottom-left-radius: 10rpx;
+	border-bottom-right-radius: 10rpx;
+	box-sizing: border-box;
+}
+
+/*Card end*/
+
+/*Grid start*/
+.tui-grids {
+	width: 100%;
+	position: relative;
+	overflow: hidden;
+}
+
+.tui-grids::after {
+	content: " ";
+	position: absolute;
+	left: 0;
+	top: 0;
+	width: 100%;
+	height: 1px;
+	border-top: 1px solid #eaeef1;
+	-webkit-transform-origin: 0 0;
+	transform-origin: 0 0;
+	-webkit-transform: scaleY(0.5);
+	transform: scaleY(0.5);
+}
+
+.tui-border-top::after {
+	border-top: 0 !important;
+}
+
+.tui-grid {
+	position: relative;
+	padding: 40rpx 20rpx;
+	box-sizing: border-box;
+	background: #fff;
+	float: left;
+}
+
+.tui-grid-2 {
+	width: 50%;
+}
+
+.tui-grid-3 {
+	width: 33.333333333%;
+}
+
+.tui-grid-4 {
+	width: 25%;
+	padding: 30rpx 20rpx !important;
+}
+
+.tui-grid-5 {
+	width: 20%;
+	padding: 20rpx !important;
+}
+
+.tui-grid-2:nth-of-type(2n)::before {
+	width: 0;
+	border-right: 0;
+}
+
+.tui-grid-3:nth-of-type(3n)::before {
+	width: 0;
+	border-right: 0;
+}
+
+.tui-grid-4:nth-of-type(4n)::before {
+	width: 0;
+	border-right: 0;
+}
+
+.tui-grid-5:nth-of-type(5n)::before {
+	width: 0;
+	border-right: 0;
+}
+
+.tui-grid::before {
+	content: " ";
+	position: absolute;
+	right: 0;
+	top: 0;
+	width: 1px;
+	bottom: 0;
+	border-right: 1px solid #eaeef1;
+	-webkit-transform-origin: 100% 0;
+	transform-origin: 100% 0;
+	-webkit-transform: scaleX(0.5);
+	transform: scaleX(0.5);
+}
+
+.tui-grid::after {
+	content: " ";
+	position: absolute;
+	left: 0;
+	bottom: 0;
+	right: 0;
+	height: 1px;
+	border-bottom: 1px solid #eaeef1;
+	-webkit-transform-origin: 0 100%;
+	transform-origin: 0 100%;
+	-webkit-transform: scaleY(0.5);
+	transform: scaleY(0.5);
+}
+
+.tui-grid-bottom::after {
+	height: 0 !important;
+	border-bottom: 0 !important
+}
+
+.tui-grid-bg {
+	position: relative;
+	padding: 0;
+	width: 100%;
+	box-sizing: border-box;
+}
+
+.tui-item-hover {
+	background-color: #f7f7f9 !important;
+}
+
+/*Grid end*/
+
+/*Loading start*/
+.tui-loading-init {
+	min-width: 200rpx;
+	min-height: 200rpx;
+	max-width: 500rpx;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	flex-direction: column;
+	position: fixed;
+	top: 50%;
+	left: 50%;
+	transform: translate(-50%, -50%);
+	z-index: 9999;
+	font-size: 26rpx;
+	color: #fff;
+	background-color: rgba(0, 0, 0, 0.7);
+	border-radius: 10rpx;
+}
+
+.tui-loading-center {
+	width: 50rpx;
+	height: 50rpx;
+	border: 3px solid #fff;
+	border-radius: 50%;
+	margin: 0 6px;
+	display: inline-block;
+	vertical-align: middle;
+	clip-path: polygon(0% 0%, 100% 0%, 100% 40%, 0% 40%);
+	animation: rotate 1s linear infinite;
+	margin-bottom: 36rpx;
+}
+
+.tui-loadmore-tips {
+	text-align: center;
+	padding: 0 20rpx;
+	box-sizing: border-box;
+}
+
+@-webkit-keyframes rotate {
+	from {
+		transform: rotatez(0deg);
+	}
+
+	to {
+		transform: rotatez(360deg);
+	}
+}
+
+@keyframes rotate {
+	from {
+		transform: rotatez(0deg);
+	}
+
+	to {
+		transform: rotatez(360deg);
+	}
+}
+
+.tui-loadmore {
+	width: 48%;
+	margin: 1.5em auto;
+	line-height: 1.5em;
+	font-size: 24rpx;
+	text-align: center;
+}
+
+.tui-loading-1 {
+	margin: 0 5px;
+	width: 20px;
+	height: 20px;
+	display: inline-block;
+	vertical-align: middle;
+	-webkit-animation: a 1s steps(12) infinite;
+	animation: a 1s steps(12) infinite;
+	background: transparent url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAiIGhlaWdodD0iMTIwIiB2aWV3Qm94PSIwIDAgMTAwIDEwMCI+PHBhdGggZmlsbD0ibm9uZSIgZD0iTTAgMGgxMDB2MTAwSDB6Ii8+PHJlY3Qgd2lkdGg9IjciIGhlaWdodD0iMjAiIHg9IjQ2LjUiIHk9IjQwIiBmaWxsPSIjRTlFOUU5IiByeD0iNSIgcnk9IjUiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAgLTMwKSIvPjxyZWN0IHdpZHRoPSI3IiBoZWlnaHQ9IjIwIiB4PSI0Ni41IiB5PSI0MCIgZmlsbD0iIzk4OTY5NyIgcng9IjUiIHJ5PSI1IiB0cmFuc2Zvcm09InJvdGF0ZSgzMCAxMDUuOTggNjUpIi8+PHJlY3Qgd2lkdGg9IjciIGhlaWdodD0iMjAiIHg9IjQ2LjUiIHk9IjQwIiBmaWxsPSIjOUI5OTlBIiByeD0iNSIgcnk9IjUiIHRyYW5zZm9ybT0icm90YXRlKDYwIDc1Ljk4IDY1KSIvPjxyZWN0IHdpZHRoPSI3IiBoZWlnaHQ9IjIwIiB4PSI0Ni41IiB5PSI0MCIgZmlsbD0iI0EzQTFBMiIgcng9IjUiIHJ5PSI1IiB0cmFuc2Zvcm09InJvdGF0ZSg5MCA2NSA2NSkiLz48cmVjdCB3aWR0aD0iNyIgaGVpZ2h0PSIyMCIgeD0iNDYuNSIgeT0iNDAiIGZpbGw9IiNBQkE5QUEiIHJ4PSI1IiByeT0iNSIgdHJhbnNmb3JtPSJyb3RhdGUoMTIwIDU4LjY2IDY1KSIvPjxyZWN0IHdpZHRoPSI3IiBoZWlnaHQ9IjIwIiB4PSI0Ni41IiB5PSI0MCIgZmlsbD0iI0IyQjJCMiIgcng9IjUiIHJ5PSI1IiB0cmFuc2Zvcm09InJvdGF0ZSgxNTAgNTQuMDIgNjUpIi8+PHJlY3Qgd2lkdGg9IjciIGhlaWdodD0iMjAiIHg9IjQ2LjUiIHk9IjQwIiBmaWxsPSIjQkFCOEI5IiByeD0iNSIgcnk9IjUiIHRyYW5zZm9ybT0icm90YXRlKDE4MCA1MCA2NSkiLz48cmVjdCB3aWR0aD0iNyIgaGVpZ2h0PSIyMCIgeD0iNDYuNSIgeT0iNDAiIGZpbGw9IiNDMkMwQzEiIHJ4PSI1IiByeT0iNSIgdHJhbnNmb3JtPSJyb3RhdGUoLTE1MCA0NS45OCA2NSkiLz48cmVjdCB3aWR0aD0iNyIgaGVpZ2h0PSIyMCIgeD0iNDYuNSIgeT0iNDAiIGZpbGw9IiNDQkNCQ0IiIHJ4PSI1IiByeT0iNSIgdHJhbnNmb3JtPSJyb3RhdGUoLTEyMCA0MS4zNCA2NSkiLz48cmVjdCB3aWR0aD0iNyIgaGVpZ2h0PSIyMCIgeD0iNDYuNSIgeT0iNDAiIGZpbGw9IiNEMkQyRDIiIHJ4PSI1IiByeT0iNSIgdHJhbnNmb3JtPSJyb3RhdGUoLTkwIDM1IDY1KSIvPjxyZWN0IHdpZHRoPSI3IiBoZWlnaHQ9IjIwIiB4PSI0Ni41IiB5PSI0MCIgZmlsbD0iI0RBREFEQSIgcng9IjUiIHJ5PSI1IiB0cmFuc2Zvcm09InJvdGF0ZSgtNjAgMjQuMDIgNjUpIi8+PHJlY3Qgd2lkdGg9IjciIGhlaWdodD0iMjAiIHg9IjQ2LjUiIHk9IjQwIiBmaWxsPSIjRTJFMkUyIiByeD0iNSIgcnk9IjUiIHRyYW5zZm9ybT0icm90YXRlKC0zMCAtNS45OCA2NSkiLz48L3N2Zz4=) no-repeat;
+	background-size: 100%;
+}
+
+@-webkit-keyframes a {
+	0% {
+		-webkit-transform: rotate(0deg);
+		transform: rotate(0deg);
+	}
+
+	to {
+		-webkit-transform: rotate(1turn);
+		transform: rotate(1turn);
+	}
+}
+
+@keyframes a {
+	0% {
+		-webkit-transform: rotate(0deg);
+		transform: rotate(0deg);
+	}
+
+	to {
+		-webkit-transform: rotate(1turn);
+		transform: rotate(1turn);
+	}
+}
+
+.tui-loadmore-tips {
+	display: inline-block;
+	vertical-align: middle;
+}
+
+.tui-loading-2 {
+	width: 28rpx;
+	height: 28rpx;
+	border: 1px solid #8f8d8e;
+	border-radius: 50%;
+	margin: 0 6px;
+	display: inline-block;
+	vertical-align: middle;
+	clip-path: polygon(0% 0%, 100% 0%, 100% 30%, 0% 30%);
+	animation: rotate 1s linear infinite;
+}
+
+@-webkit-keyframes rotate {
+	from {
+		transform: rotatez(0deg);
+	}
+
+	to {
+		transform: rotatez(360deg);
+	}
+}
+
+@keyframes rotate {
+	from {
+		transform: rotatez(0deg);
+	}
+
+	to {
+		transform: rotatez(360deg);
+	}
+}
+
+.tui-loading-3 {
+	display: inline-block;
+	margin: 0 6px;
+	vertical-align: middle;
+	width: 28rpx;
+	height: 28rpx;
+	background: 0 0;
+	border-radius: 50%;
+	border: 2px solid;
+	border-color: #e5e5e5 #e5e5e5 #e5e5e5 #8f8d8e;
+	animation: tui-rotate 0.7s linear infinite;
+}
+
+.tui-loading-3.tui-loading-primary {
+	border-color: #e5e5e5 #e5e5e5 #e5e5e5 #5677fc;
+}
+
+.tui-loading-3.tui-loading-green {
+	border-color: #e5e5e5 #e5e5e5 #e5e5e5 #19be6b;
+}
+
+.tui-loading-3.tui-loading-orange {
+	border-color: #e5e5e5 #e5e5e5 #e5e5e5 #ff7900;
+}
+
+.tui-loading-3.tui-loading-red {
+	border-color: #ededed #ededed #ededed #ed3f14;
+}
+
+@-webkit-keyframes tui-rotate {
+	0% {
+		transform: rotate(0);
+	}
+
+	100% {
+		transform: rotate(360deg);
+	}
+}
+
+@keyframes tui-rotate {
+	0% {
+		transform: rotate(0);
+	}
+
+	100% {
+		transform: rotate(360deg);
+	}
+}
+
+.tui-loadmore-none {
+	width: 50%;
+	margin: 1.5em auto;
+	line-height: 1.5em;
+	font-size: 24rpx;
+	display: flex;
+	justify-content: center;
+}
+
+.tui-nomore {
+	width: 100%;
+	height: 100%;
+	position: relative;
+	display: flex;
+	justify-content: center;
+	margin-top: 10rpx;
+	padding-bottom: 6rpx;
+}
+
+.tui-nomore::before {
+	content: ' ';
+	position: absolute;
+	border-bottom: 1rpx solid #e5e5e5;
+	-webkit-transform: scaleY(0.5);
+	transform: scaleY(0.5);
+	width: 100%;
+	top: 18rpx;
+	left: 0;
+}
+
+.tui-nomore-text {
+	color: #999;
+	font-size: 24rpx;
+	text-align: center;
+	padding: 0 18rpx;
+	height: 36rpx;
+	line-height: 36rpx;
+	position: relative;
+	z-index: 1;
+}
+
+.tui-nomore-dot {
+	position: relative;
+	text-align: center;
+	-webkit-display: flex;
+	display: flex;
+	-webkit-justify-content: center;
+	justify-content: center;
+	margin-top: 10rpx;
+	padding-bottom: 6rpx;
+}
+
+.tui-nomore-dot::before {
+	content: '';
+	position: absolute;
+	border-bottom: 1rpx solid #e5e5e5;
+	-webkit-transform: scaleY(0.5);
+	transform: scaleY(0.5);
+	width: 360rpx;
+	top: 18rpx;
+}
+
+.tui-dot-text {
+	position: relative;
+	color: #e5e5e5;
+	font-size: 10px;
+	text-align: center;
+	width: 50rpx;
+	height: 36rpx;
+	line-height: 36rpx;
+	-webkit-transform: scale(0.8);
+	transform: scale(0.8);
+	-webkit-transform-origin: center center;
+	transform-origin: center center;
+	z-index: 1;
+}
+
+/*Loading end*/
+
+/*Footer start*/
+.tui-footer {
+	width: 100%;
+	overflow: hidden;
+	padding: 30rpx 24rpx;
+	box-sizing: border-box;
+	font-size: 24rpx;
+	color: #A7A7A7;
+}
+
+.tui-fixed {
+	position: fixed;
+	z-index: 9999;
+	/* #ifdef H5 */
+	bottom: 0;
+	/* #endif */
+	/* #ifndef H5 */
+	bottom: env(safe-area-inset-bottom);
+	/* #endif */
+}
+
+.tui-footer-link {
+	color: #596d96;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	font-size: 28rpx;
+}
+
+.tui-link {
+	position: relative;
+	padding: 0 18rpx;
+	line-height: 1;
+}
+
+.tui-link::before {
+	content: " ";
+	position: absolute;
+	right: 0;
+	top: 0;
+	width: 1px;
+	bottom: 0;
+	border-right: 1px solid #d3d3d3;
+	-webkit-transform-origin: 100% 0;
+	transform-origin: 100% 0;
+	-webkit-transform: scaleX(0.5);
+	transform: scaleX(0.5);
+}
+
+.tui-link:last-child::before {
+	border-right: 0 !important
+}
+
+.tui-link-hover {
+	opacity: 0.5
+}
+
+.tui-footer-copyright {
+	font-size: 24rpx;
+	color: #A7A7A7;
+	line-height: 1;
+	text-align: center;
+	padding-top: 16rpx
+}
+
+/*Footer end*/
+
+
+/*layout start*/
+
+/*形状*/
+.tui-triangle {
+	border: 16rpx solid;
+	width: 0;
+	height: 0;
+}
+
+.tui-triangle-left {
+	border-color: transparent #5c8dff transparent transparent;
+}
+
+.tui-triangle-right {
+	border-color: transparent transparent transparent #5c8dff;
+}
+
+.tui-triangle-top {
+	border-color: transparent transparent #5c8dff transparent;
+}
+
+.tui-triangle-bottom {
+	border-color: #5c8dff transparent transparent transparent;
+}
+
+
+.tui-parallelogram {
+	width: 100rpx;
+	height: 50rpx;
+	transform: skew(-10deg);
+	background: #19be6b;
+	margin-left: 10rpx;
+}
+
+.tui-crescent {
+	width: 60rpx;
+	height: 60rpx;
+	border-radius: 50%;
+	box-shadow: 12rpx 12rpx 0 0 yellowgreen;
+}
+
+/*聊天框*/
+.tui-chatbox {
+	max-width: 60%;
+	border-radius: 10rpx;
+	position: relative;
+	padding: 20rpx 26rpx;
+	font-size: 28rpx;
+	color: #fff;
+	/* word-break: break-all;
+  word-wrap: break-word; */
+}
+
+.tui-chatbox-left {
+	background: #5c8dff;
+	border: 1rpx solid #5c8dff;
+	display: inline-block;
+}
+
+.tui-chatbox-right {
+	background: #19be6b;
+	border: 1rpx solid #19be6b;
+}
+
+.tui-chatbox::before {
+	content: "";
+	position: absolute;
+	width: 0;
+	height: 0;
+	top: 20rpx;
+	border: 16rpx solid;
+}
+
+.tui-chatbox-left::before {
+	right: 100%;
+	border-color: transparent #5c8dff transparent transparent;
+}
+
+.tui-chatbox-right::before {
+	left: 100%;
+	border-color: transparent transparent transparent #19be6b;
+}
+
+/*checkbox 整体大小  */
+
+/* #ifdef MP-WEIXIN */
+.tui-checkbox .wx-checkbox-input {
+	width: 36rpx;
+	height: 36rpx;
+	border-radius: 50%;
+	margin: 0;
+}
+
+.tui-checkbox .wx-checkbox-input.wx-checkbox-input-checked {
+	background: #F82400;
+	width: 40rpx;
+	height: 40rpx;
+	border: none;
+}
+
+/* #endif */
+
+/* #ifndef MP-WEIXIN */
+>>>.tui-checkbox .uni-checkbox-input {
+	width: 36rpx;
+	height: 36rpx;
+	border-radius: 50% !important;
+	margin: 0;
+	border-color: #d1d1d1 !important;
+}
+
+>>>.tui-checkbox .uni-checkbox-input.uni-checkbox-input-checked {
+	background: #F82400;
+	width: 40rpx;
+	height: 40rpx;
+	border: none;
+}
+
+/* #endif */
+
+/*textarea*/
+
+.tui-textarea-box {
+	border-radius: 4rpx;
+	height: 280rpx;
+	box-sizing: border-box;
+	padding: 20rpx 20rpx 0 20rpx;
+	position: relative;
+}
+
+.tui-textarea-box::after {
+	content: '';
+	position: absolute;
+	height: 200%;
+	width: 200%;
+	border: 1px solid #e6e6e6;
+	transform-origin: 0 0;
+	-webkit-transform-origin: 0 0;
+	-webkit-transform: scale(0.5);
+	transform: scale(0.5);
+	left: 0;
+	top: 0;
+	border-radius: 8rpx;
+	pointer-events: none;
+}
+
+.tui-textarea {
+	height: 210rpx;
+	width: 100%;
+	color: #666;
+	font-size: 28rpx;
+	z-index: 2;
+}
+
+.tui-phcolor-color {
+	color: #ccc !important;
+}
+
+.tui-textarea-counter {
+	font-size: 24rpx;
+	color: #999;
+	text-align: right;
+	height: 40rpx;
+	line-height: 40rpx;
+	padding-top: 4rpx;
+}
+
+/*Input输入框*/
+.tui-input-item {
+	width: 100%;
+	display: flex;
+	align-items: center;
+	font-size: 32rpx;
+
+}
+
+.tui-input {
+	flex: 1;
+	font-size: 32rpx;
+	overflow: visible;
+}
+
+.tui-input-title {
+	min-width: 140rpx;
+	padding-right: 12rpx;
+	flex-shrink: 0;
+}
+
+.tui-phcolor {
+	color: #ccc;
+	font-size: 32rpx;
+	overflow: visible;
+}
+
+.tui-input-border {
+	/* border: 1rpx solid #eaeef1; */
+	padding: 20rpx 30rpx;
+	border-radius: 4rpx;
+	position: relative;
+	font-size: 32rpx;
+}
+
+.tui-input-border::after {
+	content: '';
+	position: absolute;
+	height: 200%;
+	width: 200%;
+	border: 1px solid #e6e6e6;
+	transform-origin: 0 0;
+	-webkit-transform-origin: 0 0;
+	-webkit-transform: scale(0.5);
+	transform: scale(0.5);
+	left: 0;
+	top: 0;
+	border-radius: 8rpx;
+	pointer-events: none;
+}
+
+.tui-text-right {
+	text-align: right;
+}
+
+/*上传*/
+.tui-upload-box {
+	width: 100%;
+	display: flex;
+	flex-wrap: wrap;
+}
+
+.tui-upload-item {
+	width: 220rpx;
+	height: 220rpx;
+	position: relative;
+	margin-right: 20rpx;
+	margin-bottom: 20rpx;
+}
+
+.tui-upload-item:nth-of-type(3n) {
+	margin-right: 0;
+}
+
+.tui-upload-img {
+	width: 220rpx;
+	height: 220rpx;
+	display: block;
+}
+
+.tui-upload-del {
+	position: absolute;
+	right: -18rpx;
+	top: -18rpx;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+}
+
+.tui-upload-add {
+	width: 220rpx;
+	height: 220rpx;
+	font-size: 68rpx;
+	font-weight: 100;
+	color: #888;
+	background-color: #F7F7F7;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	padding: 0;
+}
+
+/* 操作条*/
+.tui-operation {
+	width: 100%;
+	height: 100rpx;
+	box-sizing: border-box;
+	overflow: hidden;
+	background: rgba(255, 255, 255, 0.9);
+	position: relative;
+	display: flex;
+	align-items: center;
+	justify-content: space-between;
+}
+
+.tui-operation::before {
+	content: '';
+	position: absolute;
+	top: 0px;
+	right: 0;
+	left: 0;
+	border-top: 1rpx solid #eaeef1;
+	-webkit-transform: scaleY(0.5);
+	transform: scaleY(0.5);
+}
+
+.tui-operation-left {
+	display: flex;
+	align-items: center;
+}
+
+.tui-operation-item {
+	flex: 1;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	flex-direction: column;
+	position: relative;
+}
+
+.tui-operation-text {
+	font-size: 22rpx;
+	color: #333;
+}
+
+.tui-operation-right {
+	flex: 1;
+	height: 100rpx;
+	box-sizing: border-box;
+	padding-top: 0;
+}
+
+.tui-relative {
+	position: relative;
+}
+
+.tui-btn-comment {
+	height: 64rpx;
+	width: 84%;
+	background: #ededed;
+	color: #999;
+	border-radius: 8rpx;
+	font-size: 28rpx;
+	display: flex;
+	align-items: center;
+	padding-left: 20rpx;
+	box-sizing: border-box;
+	padding-top: 0;
+	margin-left: 30rpx;
+}
+
+.tui-chat-operation {
+	background: #F6F6F6 !important;
+	padding-right: 18rpx;
+	box-sizing: border-box;
+}
+
+.tui-input-box {
+	width: 78%;
+	justify-content: flex-start;
+}
+
+.tui-chat-input {
+	background: #fff;
+	height: 72rpx;
+	border-radius: 6rpx;
+	padding-left: 20rpx;
+	padding-right: 20rpx;
+	flex: 1;
+}
+
+.tui-voice-icon {
+	margin-left: 20rpx;
+	margin-right: 20rpx
+}
+
+/*layout end*/

+ 143 - 0
components/cm-module/productDetails/cm-activipopu.vue

@@ -0,0 +1,143 @@
+<template name="cm-price">
+	<!-- 商品促销活动弹窗提示判断 -->
+	<view class="wrap-main">
+	
+	</view>
+</template>
+
+<script>
+	import { mapState,mapMutations } from 'vuex'
+	export default{
+		name:'cm-price',
+		props:{
+			product:{
+				type:Object,
+			},
+			userIdentity: {
+				type: Number,
+				default: 2
+			},
+			ladderPriceList:{
+				type: Array,
+			},
+			retailPrice:{
+				type:String,
+			},
+			minBuyNumber:{
+				type: Number,
+				default: 1
+			}
+		},
+		data() {
+			return{
+				popupShow:false
+			}
+		},
+		filters: {
+			NumFormat:function(text) {//处理金额
+				return Number(text).toFixed(2);
+			},
+		},
+		created() {
+			
+		},
+		computed: {
+			...mapState(['hasLogin','isWxAuthorize'])
+		},
+		methods:{
+			clickPopupShow(){
+				this.popupShow = true
+			},
+			hidePopup(){
+				this.popupShow = false
+			},
+			goUpgradeApply(){
+				this.$api.navigateTo('/pages/login/apply')
+			},
+			loginClick(){
+				this.$api.navigateTo('/pages/login/login')
+			}
+		},
+		
+	}
+</script>
+
+<style lang="scss">	
+	.tui-flex-1 {
+		flex: 1;
+		padding: 16rpx;
+	}
+	.tui-popup-box {
+		position: relative;
+		padding: 30rpx 0 100rpx 0;
+	}
+	.tui-popup-title{
+		line-height: 60rpx;
+		text-align: center;
+		color: #333;
+		font-size: 28rpx;
+		border-bottom: 1px solid #EBEBEB;
+	}
+	.tui-popup-attr{
+		width: 100%;
+		padding: 0 20rpx;
+		.tui-popup-attr-cell{
+			width: 100%;
+			height: auto;
+			border-bottom: 1px solid #EFEFEF;
+			font-size: 24rpx;
+			line-height: 24rpx;
+			display: flex;
+			flex-wrap: wrap;
+			.attr-cell-td{
+				width: 238rpx;
+				float: left;
+				color: #999999;
+				padding:24rpx 20rpx;
+				display: flex;
+				flex: 3;
+				flex-direction: column;
+			}
+			.attr-cell-tr{
+				width: 462rpx;
+				float: left;
+				color: #333333;
+				padding:20rpx;
+				display: flex;
+				flex: 7;
+				flex-direction: column;
+			}
+			&:last-child{
+				border-bottom: none;
+			}
+		}
+	}
+	.tui-popup-btn {
+		width: 100%;
+		position: absolute;
+		left: 0;
+		bottom: 0;
+		z-index: 9999;
+	}
+</style>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+ 8 - 4
components/cm-module/productDetails/cm-attributes.vue

@@ -6,12 +6,16 @@
 		</view>
 		<view class="wrap-info">
 			<view class="info-viewT" :class="goodsData.isNoneDisabled ? 'none' : ''">
-				<view class="info-viewL tui-skeleton-fillet">品牌:<text>{{product.brandName == null? '其他' : product.brandName}}</text></view>
-				<view class="info-viewR tui-skeleton-fillet">包装规格:<text>{{product.unit}}</text></view>		
+				<view class="info-viewL tui-skeleton-fillet">起订量:<text>{{product.minBuyNumber}}</text></view>
+				<view class="info-viewR tui-skeleton-fillet">品牌:<text>{{product.brandName == null? '其他' : product.brandName}}</text></view>
+			</view>
+			<view class="info-viewT" :class="goodsData.isNoneDisabled ? 'none' : ''">
+				<view class="info-viewL tui-skeleton-fillet">包装规格:<text>{{product.unit}}</text></view>
+				<view class="info-viewR tui-skeleton-fillet">商品编码:<text>{{product.productCode}}</text></view>		
 			</view>
 			<view class="info-viewT" :class="goodsData.isNoneDisabled ? 'none' : ''" v-if="product.productCode!=''&&product.productCode!=null">
-				<view class="info-viewL tui-skeleton-fillet">商品编码:<text>{{product.productCode}}</text></view>
-				<view class="info-viewR tui-skeleton-fillet">库存:<text>{{product.stock}}</text></view>
+				<view class="info-viewL tui-skeleton-fillet">库存:<text>{{product.stock}}</text></view>
+				<view class="info-viewR tui-skeleton-fillet"></view>
 			</view>
 		</view>
 		<view class="wrap-seve" :class="goodsData.isNoneDisabled ? 'none' : ''">

+ 263 - 60
components/cm-module/productDetails/cm-price.vue

@@ -5,17 +5,14 @@
 			<view class="p-login grade tui-skeleton-fillet">
 				<text class="p-no">¥</text>
 				<uni-stars :stars="parseInt(product.price1Grade)" :font-size='36' :width-info="180"></uni-stars>
-				<view class="p-minBuy">起订量:<text class="min-text">{{minBuyNumber}}</text></view>
-			</view>
-			<view class="wrap-main-act">
 				<view v-if="product.actStatus==1" class="floor-item-act">
-					<view v-if="product.promotions.type == 1 && product.promotions.mode==1"  class="floor-tags" @click.stop="click">
+					<view v-if="product.promotions.type == 1 && product.promotions.mode==1"  class="floor-tags" @click.stop="clickPopupShow()">
 						{{product.promotions.name}}<text v-if="hasLogin">:¥{{ product.price | NumFormat }}</text>
 					</view>
-					<view  v-else  class="floor-tags" @click.stop="click">{{product.promotions.name}}</view>	
+					<view v-else class="floor-tags" @click.stop="clickPopupShow()">{{product.promotions.name}}</view>	
 				</view>
-				<view v-if="product.actStatus ==0  &&  product.ladderPriceFlag==1" class="floor-item-act">
-					<view class="floor-tags" @click.stop="click">阶梯价格</view>	
+				<view v-if="product.actStatus ==0  &&  product.ladderPriceFlag === 1" class="floor-item-act">
+					<view class="floor-tags" @click.stop="clickPopupShow()">阶梯价格</view>	
 				</view>
 				<view class="floor-item-btn">
 					<view class="btn" @click.stop="loginClick">登录查看价格</view>
@@ -23,60 +20,124 @@
 			</view>
 		</template>
 		<template v-else>
-			<view   v-if="userIdentity == 1" class="wrap-main-price">
-				<view class="p-main">
-					<view class="p-price tui-skeleton-fillet" :class="product.promotions.type == 1 && product.promotions.mode==1 ? 'none' : ''">
+			<!-- 协销  -->
+			<template  v-if="userIdentity == 1">
+				<view class="wrap-main-item">
+					<view class="p-price tui-skeleton-fillet" :class="product.promotions.type == 1 && product.promotions.mode == 1 ? 'none' : ''">
 						<text class="txt sm">¥</text>
-						<text class="txt big">{{retailPrice | NumFormat}}</text>
+						<text class="txt big">{{product.retailPrice | NumFormat}}</text>
 					</view>
-					<view class="p-minBuy">起订量:<text class="min-text">{{minBuyNumber}}</text></view>
-				</view>				
-			</view>	
-			<view v-if="userIdentity == 4">
-				<view v-if="product.price1TextFlag == '1'" class="wrap-main-pricenone">
-					<text class="wrap-main-text">¥未公开价格</text>
-				</view>
-				<view v-if="product.price1TextFlag == '2'" class="wrap-main-pricenone">
-					<text class="wrap-main-text">¥价格仅会员可见</text>
-				</view>
-				<view  v-if="product.price1TextFlag == '0'" class="wrap-main-price">
-					<view class="p-main">
-						<view class="p-price tui-skeleton-fillet" :class="product.promotions.type == 1 && product.promotions.mode==1 ? 'none' : ''">
-							<text class="txt sm">¥</text>
-							<text class="txt big">{{retailPrice | NumFormat}}</text>
+					<view v-if="product.actStatus==1" class="floor-item-act">
+						<view v-if="product.promotions.type == 1 && product.promotions.mode == 1"  class="floor-tags" @click.stop="clickPopupShow(1)">
+							{{product.promotions.name}}<text v-if="hasLogin">:¥{{ product.price | NumFormat }}</text>
 						</view>
+						<view v-else class="floor-tags" @click.stop="clickPopupShow()">{{product.promotions.name}}</view>	
+					</view>
+					<view  v-if="product.actStatus == 0  &&  product.ladderPriceFlag == 1" class="floor-item-act">
+						<view class="floor-tags" @click.stop="clickPopupShow()">阶梯价格</view>	
 					</view>
 				</view>		
-				<view class="p-minBuy">起订量:<text class="min-text">{{minBuyNumber}}</text></view>
-			</view>
-			<view v-if="userIdentity == 2">
+			</template>	
+			<!-- 会员机构  -->
+			<template v-if="userIdentity == 2">
 				<view v-if="product.price1TextFlag == '1'" class="wrap-main-pricenone">
-					<text class="wrap-main-text">¥未公开价格</text>
+					<view class="wrap-main-text">¥未公开价格</view>
 				</view>
-				<view v-else class="wrap-main-price">
-					<view class="p-main">
-						<view class="p-price tui-skeleton-fillet" :class="product.promotions.type == 1 && product.promotions.mode==1 ? 'none' : ''">
-							<text class="txt sm">¥</text>
-							<text class="txt big">{{retailPrice | NumFormat}}</text>
+				<view v-else class="wrap-main-item">
+					<view class="p-price tui-skeleton-fillet" :class="product.promotions.type == 1 && product.promotions.mode==1 ? 'none' : ''">
+						<text class="txt sm">¥</text>
+						<text class="txt big">{{product.retailPrice | NumFormat}}</text>
+					</view>
+					<view v-if="product.actStatus==1" class="floor-item-act">
+						<view v-if="product.promotions.type == 1 && product.promotions.mode == 1"  class="floor-tags" @click.stop="clickPopupShow(1)">
+							{{product.promotions.name}}<text v-if="hasLogin">:¥{{ product.price | NumFormat }}</text>
 						</view>
+						<view v-else class="floor-tags" @click.stop="clickPopupShow()">{{product.promotions.name}}</view>	
 					</view>
-				</view>	
-				<view class="p-minBuy">起订量:<text class="min-text">{{minBuyNumber}}</text></view>
-			</view>
-			<view class="wrap-main-act">
-				<view v-if="product.actStatus==1" class="floor-item-act">
-					<view v-if="product.promotions.type == 1 && product.promotions.mode==1"  class="floor-tags" @click.stop="click">
-						{{product.promotions.name}}<text v-if="hasLogin">:¥{{ product.price | NumFormat }}</text>
+					<view  v-if="product.actStatus ==0  &&  product.ladderPriceFlag == 1" class="floor-item-act">
+						<view class="floor-tags" @click.stop="clickPopupShow()">阶梯价格</view>	
 					</view>
-					<view v-else class="floor-tags" @click.stop="click">{{product.promotions.name}}</view>	
 				</view>
-				<view  v-if="product.actStatus ==0  &&  product.ladderPriceFlag==1" class="floor-item-act">
-					<view class="floor-tags" @click.stop="click">阶梯价格</view>	
-				</view>
-				<view v-if="userIdentity == 4" class="floor-item-btn">
-					<view class="btn" @click.stop="goUpgradeApply">去升级会员查看价格</view>
-				</view>
-			</view>
+			</template>
+			<!-- 普通机构  -->
+			<template v-if="userIdentity == 4">
+				<template v-if="product.price1TextFlag == '1'">
+					<text class="wrap-main-text">¥未公开价格</text>
+				</template>
+				<template v-if="product.price1TextFlag == '2'">
+					<text class="wrap-main-text">¥价格仅会员可见</text>
+					<view class="floor-item-btn">
+						<view class="btn" @click.stop="goUpgradeApply">升级会员查看价格</view>
+					</view>
+				</template>
+				<template v-if="product.price1TextFlag == '0'">
+					<view class="wrap-main-item">
+						<view class="p-price tui-skeleton-fillet" :class="product.promotions.type == 1 && product.promotions.mode == 1 ? 'none' : ''">
+							<text class="txt sm">¥</text>
+							<text class="txt big">{{product.retailPrice | NumFormat}}</text>
+						</view>
+						<view v-if="product.actStatus==1" class="floor-item-act">
+							<view v-if="product.promotions.type == 1 && product.promotions.mode == 1"  class="floor-tags" @click.stop="clickPopupShow(1)">
+								{{product.promotions.name}}<text v-if="hasLogin">:¥{{ product.price | NumFormat }}</text>
+							</view>
+							<view v-else class="floor-tags" @click.stop="clickPopupShow()">{{product.promotions.name}}</view>	
+						</view>
+						<view  v-if="product.actStatus ==0  &&  product.ladderPriceFlag == 1" class="floor-item-act">
+							<view class="floor-tags" @click.stop="clickPopupShow()">阶梯价格</view>	
+						</view>
+					</view>
+				</template>		
+			</template>
+			<!--促销活动弹窗提示-->
+			<tui-bottom-popup :radius="true" :show="popupShow" @close="hidePopup()">
+				<view class="tui-popup-box clearfix">
+					<template v-if="product.actStatus == 0  &&  product.ladderPriceFlag == 1">
+						<view class="tui-scrollview-box">
+							<view class="ladder-main">
+								<view class="ladder-item">
+									<view class="ladder-item-td">起订量</view>
+									<view class="ladder-item-td">价格</view>
+								</view>
+								<view class="ladder-item" v-for="(ladd, index) in 3" :key="index">
+									<view class="ladder-item-td">2~3</view>
+									<view class="ladder-item-td">200.00</view>
+								</view>
+							</view>
+						</view>	
+					</template>	
+					<template v-else>
+						<view class="tui-scrollview-box">
+							<view class="box-text">
+								<text>促销时间:</text>
+								<text v-if="product.promotions.status==1">不限时</text>
+								<text v-else>{{ product.promotions.beginTime }} ~ {{ product.promotions.endTime }}</text>
+							</view>
+							<view class="box-title" v-if="product.promotions.mode==2">
+								{{product.promotions.name+',满 ¥'+ product.promotions.touchPrice  +' 减 ¥'+ product.promotions.reducedPrice }}
+							</view>
+							<view class="box-title" v-if="product.promotions.mode==3">
+								{{product.promotions.name+',满 ¥'+ product.promotions.touchPrice +' 赠送商品'}}
+							</view>
+							<view class="box-product" v-if="product.promotions.mode==3">
+								<view class="title">赠送商品:</view>
+								<view class="box-product-main"  v-for="(item, index) in product.promotions.giftList" :key="index">
+									<view class="image"><image :src="product.mainImage" mode="widthFix"></image></view>
+									<view class="info">
+										<view class="name">{{item.name}}}</view>
+										<view class="num">X{{item.number}}</view>
+									</view>
+								</view>
+							</view>
+						</view>
+					</template>
+					<view class="tui-right-flex tui-popup-btn" :style="{paddingBottom :isIphoneX ? '68rpx' : '0rpx'}">
+						<view class="tui-flex-1">
+							<view class="tui-button" v-if="product.promotions.type == 2" @click="hidePopup()">更多凑单商品</view>
+							<view class="tui-button" v-else @click="hidePopup()">了解</view>
+						</view>
+					</view>
+				</view>	
+			</tui-bottom-popup>
 		</template>
 	</view>
 </template>
@@ -95,18 +156,13 @@
 			},
 			ladderPriceList:{
 				type: Array,
-			},
-			retailPrice:{
-				type:String,
-			},
-			minBuyNumber:{
-				type: Number,
-				default: 1
 			}
 		},
 		data() {
 			return{
-				
+				popupShow:false,
+				promotionsType:0,
+				isIphoneX:this.$store.state.isIphoneX,
 			}
 		},
 		filters: {
@@ -121,19 +177,166 @@
 			...mapState(['hasLogin','isWxAuthorize'])
 		},
 		methods:{
+			clickPopupShow(type){
+				this.popupShow = true
+				this.promotionsType = type
+			},
+			hidePopup(){
+				this.popupShow = false
+			},
 			goUpgradeApply(){
 				this.$api.navigateTo('/pages/login/apply')
 			},
 			loginClick(){
 				this.$api.navigateTo('/pages/login/login')
-			}
+			},
+			toFixed:function(text) {//处理金额
+				if(text == null || text === undefined){
+					return Number(0).toFixed(2);
+				}else{
+					return Number(text).toFixed(2);
+				}
+			},
 		},
 		
 	}
 </script>
 
 <style lang="scss">	
-
+	.tui-flex-1 {
+		flex: 1;
+	}
+	.tui-popup-box {
+		position: relative;
+		box-sizing: border-box;
+		min-height: 220rpx;
+		padding:24rpx 24rpx 0 24rpx;
+	}
+	.tui-scrollview-box{
+		width: 100%;
+		height: auto;
+		float: left;
+		box-sizing: border-box;
+		.ladder-main{
+			width: 100%;
+			height: 320rpx;
+			border: 1px solid rgba(225,86,22,0.3);
+			border-radius: 10rpx;
+			.ladder-item{
+				width: 100%;
+				height: 80rpx;
+				float: left;
+				border-bottom: 1px solid rgba(225,86,22,0.3);
+				&:nth-child(1){
+					.ladder-item-td{
+						color: #333333;
+					}
+				}
+				&:last-child{
+					border-bottom: none;
+				}
+				.ladder-item-td{
+					width:50%;
+					text-align: center;
+					line-height: 80rpx;
+					font-size: $font-size-24;
+					color: $color-system;
+					box-sizing: border-box;
+					float: left;
+					&:nth-child(1){
+						border-right: 1px solid rgba(225,86,22,0.3);
+					}
+				}
+			}
+		}
+		.box-title{
+			font-size: $font-size-26;
+			color: $color-system;
+			text-align: left;
+			line-height: 56rpx;
+		}
+		.box-text{
+			font-size: $font-size-26;
+			color: rgba(225,86,22,0.6);
+			text-align: left;
+			line-height: 56rpx;
+		}
+		.box-product{
+			width: 100%;
+			height: auto;
+			margin-top: 20rpx;
+			.title{
+				font-size: $font-size-24;
+				color: $color-system;
+				text-align: left;
+				line-height: 54rpx;
+			}
+			.box-product-main{
+				width: 100%;
+				height: 136rpx;
+				.image{
+					width: 134rpx;
+					height: 134rpx;
+					border: 1px solid #EBEBEB;
+					float: left;
+					image{
+						width: 100%;
+						height: 100%;
+						display: block;
+					}
+				}
+				.info{
+					width: 540rpx;
+					height: 134rpx;
+					float: left;
+					margin-left: 16rpx;
+					position: relative;
+					.name{
+						width: 100%;
+						float: left;
+						line-height: 40rpx;
+						font-size: $font-size-28;
+						color: $text-color;
+						-o-text-overflow: ellipsis;
+						text-overflow: ellipsis;
+						display: -webkit-box;
+						word-break: break-all;
+						-webkit-box-orient: vertical;
+						-webkit-line-clamp: 2;
+						overflow: hidden;
+					}
+					.num{
+						width: 100%;
+						height: 44rpx;
+						font-size: $font-size-24;
+						color: $text-color;
+						text-align: left;
+						line-height: 44rpx;
+						position: absolute;
+						bottom: 0;
+						left: 0;
+					}
+				}
+			}
+		}
+	}
+	
+	.tui-popup-btn {
+		width: 100%;
+		height: auto;
+		float: left;
+		margin-top: 24rpx;
+		.tui-button{
+			width: 100%;
+			height: 88rpx;
+			background: $btn-confirm;
+			line-height: 88rpx;
+			text-align: center;
+			color: #FFFFFF;
+			font-size: $font-size-28;
+			border-radius: 14rpx;
+		}
+	}
 </style>
 
 

+ 185 - 0
components/thorui/tui-actionsheet/tui-actionsheet.vue

@@ -0,0 +1,185 @@
+<template>
+	<view @touchmove.stop.prevent>
+		<view class="tui-actionsheet" :class="{'tui-actionsheet-show':show,'tui-actionsheet-radius':radius}">
+			<view class="tui-actionsheet-tips" :style="{fontSize:size+'rpx',color:color}" v-if="tips">
+				{{tips}}
+			</view>
+			<view :class="[isCancel?'tui-operate-box':'']">
+				<block v-for="(item,index) in itemList" :key="index">
+					<view class="tui-actionsheet-btn tui-actionsheet-divider" :class="{'tui-btn-last':!isCancel && index==itemList.length-1}"
+					 hover-class="tui-actionsheet-hover" :hover-stay-time="150" :data-index="index" :style="{color:item.color || '#2B2B2B'}"
+					 @tap="handleClickItem">{{item.text}}</view>
+				</block>
+			</view>
+			<view class="tui-actionsheet-btn tui-actionsheet-cancel" hover-class="tui-actionsheet-hover" :hover-stay-time="150"
+			 v-if="isCancel" @tap="handleClickCancel">取消</view>
+		</view>
+		<view class="tui-actionsheet-mask" :class="{'tui-mask-show':show}" @tap="handleClickMask"></view>
+	</view>
+</template>
+
+<script>
+	export default {
+		name: "tuiActionsheet",
+		props: {
+			//点击遮罩 是否可关闭
+			maskClosable: {
+				type: Boolean,
+				default: true
+			},
+			//显示操作菜单
+			show: {
+				type: Boolean,
+				default: false
+			},
+			//菜单按钮数组,自定义文本颜色,红色参考色:#e53a37
+			itemList: {
+				type: Array,
+				default: function() {
+					return [{
+						text: "确定",
+						color: "#2B2B2B"
+					}]
+				}
+			},
+			//提示文字
+			tips: {
+				type: String,
+				default: ""
+			},
+			//提示文字颜色
+			color: {
+				type: String,
+				default: "#808080"
+			},
+			//提示文字大小 rpx
+			size: {
+				type: Number,
+				default: 26
+			},
+			//是否需要圆角
+			radius: {
+				type: Boolean,
+				default: true
+			},
+			//是否需要取消按钮
+			isCancel: {
+				type: Boolean,
+				default: true
+			}
+		},
+		methods: {
+			handleClickMask() {
+				if (!this.maskClosable) return;
+				this.handleClickCancel();
+			},
+			handleClickItem(e) {
+				if (!this.show) return;
+				const dataset = e.currentTarget.dataset;
+				this.$emit('click', {
+					index: Number(dataset.index)
+				});
+			},
+			handleClickCancel() {
+				this.$emit('cancel');
+			}
+		}
+	}
+</script>
+
+<style scoped>
+	.tui-actionsheet {
+		width: 100%;
+		position: fixed;
+		left: 0;
+		right: 0;
+		bottom: 0;
+		z-index: 9999;
+		visibility: hidden;
+		transform: translate3d(0, 100%, 0);
+		transform-origin: center;
+		transition: all 0.25s ease-in-out;
+		background-color: #F7F7F7;
+		min-height: 100rpx;
+	}
+
+	.tui-actionsheet-radius {
+		border-top-left-radius: 20rpx;
+		border-top-right-radius: 20rpx;
+		overflow: hidden;
+	}
+
+	.tui-actionsheet-show {
+		transform: translate3d(0, 0, 0);
+		visibility: visible;
+	}
+
+	.tui-actionsheet-tips {
+		width: 100%;
+		padding: 40rpx 60rpx;
+		box-sizing: border-box;
+		text-align: center;
+		background-color: #fff;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+	}
+
+	.tui-operate-box {
+		padding-bottom: 12rpx;
+	}
+
+	.tui-actionsheet-btn {
+		width: 100%;
+		height: 100rpx;
+		background-color: #fff;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		text-align: center;
+		font-size: 34rpx;
+		position: relative;
+	}
+
+	.tui-btn-last {
+		padding-bottom: env(safe-area-inset-bottom);
+	}
+
+	.tui-actionsheet-divider::before {
+		content: '';
+		width: 100%;
+		border-top: 1rpx solid #E7E7E7;
+		position: absolute;
+		top: 0;
+		left: 0;
+		-webkit-transform: scaleY(0.5);
+		transform: scaleY(0.5);
+	}
+
+	.tui-actionsheet-cancel {
+		color: #1a1a1a;
+		padding-bottom: env(safe-area-inset-bottom);
+	}
+
+	.tui-actionsheet-hover {
+		background-color: #f7f7f9;
+	}
+
+	.tui-actionsheet-mask {
+		position: fixed;
+		top: 0;
+		left: 0;
+		right: 0;
+		bottom: 0;
+		background-color: rgba(0, 0, 0, 0.6);
+		z-index: 9996;
+		transition: all 0.3s ease-in-out;
+		opacity: 0;
+		visibility: hidden;
+	}
+
+	.tui-mask-show {
+		opacity: 1;
+		visibility: visible;
+	}
+</style>

+ 134 - 0
components/thorui/tui-alert/tui-alert.vue

@@ -0,0 +1,134 @@
+<template>
+	<view>
+		<view class="tui-alert-class tui-alert-box" :class="[show?'tui-alert-show':'']">
+			<view class="tui-alert-content" :style="{fontSize:size+'rpx',color:color}">
+				<slot></slot>
+			</view>
+			<view class="tui-alert-btn" :style="{color:btnColor}" hover-class="tui-alert-btn-hover" :hover-stay-time="150"
+			 @tap.stop="handleClick">{{btnText}}</view>
+		</view>
+		<view class="tui-alert-mask" :class="[show?'tui-alert-mask-show':'']" @tap.stop="handleClickCancel"></view>
+	</view>
+</template>
+
+<script>
+	export default {
+		name:"tuiAlert",
+		props: {
+			//控制显示
+			show: {
+				type: Boolean,
+				default: false
+			},
+			//提示信息字体大小
+			size: {
+				type: Number,
+				default: 30
+			},
+			//提示信息字体颜色
+			color: {
+				type: String,
+				default: "#333"
+			},
+			//按钮字体颜色
+			btnColor: {
+				type: String,
+				default: "#EB0909"
+			},
+			btnText:{
+				type: String,
+				default: "确定"
+			},
+			//点击遮罩 是否可关闭
+			maskClosable: {
+				type: Boolean,
+				default: false
+			}
+		},
+		methods: {
+			handleClick(e) {
+				if (!this.show) return;
+				this.$emit('click', {});
+			},
+			handleClickCancel() {
+				if (!this.maskClosable) return;
+				this.$emit('cancel');
+			}
+		}
+	}
+</script>
+
+<style scoped>
+	.tui-alert-box {
+		position: fixed;
+		width: 560rpx;
+		left: 50%;
+		top: 50%;
+		background-color: #fff;
+		transition: all 0.3s ease-in-out;
+		transform: translate(-50%, -50%) scale(0);
+		opacity: 0;
+		border-radius: 6rpx;
+		overflow: hidden;
+		z-index: 99998;
+	}
+
+	.tui-alert-show {
+		transform: translate(-50%, -50%) scale(1);
+		opacity: 1;
+	}
+
+	.tui-alert-mask {
+		position: fixed;
+		top: 0;
+		left: 0;
+		right: 0;
+		bottom: 0;
+		background-color: rgba(0, 0, 0, 0.5);
+		z-index: 99996;
+		transition: all 0.3s ease-in-out;
+		opacity: 0;
+		visibility: hidden;
+	}
+
+	.tui-alert-mask-show {
+		visibility: visible;
+		opacity: 1;
+	}
+
+	.tui-alert-content {
+		text-align: center;
+		color: #333333;
+		padding: 98rpx 48rpx 92rpx 48rpx;
+		box-sizing: border-box;
+		word-break: break-all;
+	}
+
+	.tui-alert-btn {
+		width: 100%;
+		height: 90rpx;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		background-color: #fff;
+		box-sizing: border-box;
+		position: relative;
+		font-size: 32rpx;
+		line-height: 32rpx;
+	}
+
+	.tui-alert-btn-hover {
+		background-color: #f7f7f7;
+	}
+
+	.tui-alert-btn::before {
+		width: 100%;
+		content: "";
+		position: absolute;
+		border-top: 1rpx solid #E0E0E0;
+		-webkit-transform: scaleY(0.5);
+		transform: scaleY(0.5);
+		left: 0;
+		top: 0;
+	}
+</style>

+ 155 - 0
components/thorui/tui-badge/tui-badge.vue

@@ -0,0 +1,155 @@
+<template>
+	<view :class="[dot ? 'tui-badge-dot' : 'tui-badge', 'tui-' + type, !dot ? 'tui-badge-scale' : '']" :style="{ top: top, right: right, position: absolute ? 'absolute' : 'static', transform: getStyle, margin: margin }"
+	 @tap="handleClick">
+		<slot></slot>
+	</view>
+</template>
+
+<script>
+	export default {
+		name: 'tuiBadge',
+		props: {
+			//primary,warning,green,danger,white,black,gray,white_red
+			type: {
+				type: String,
+				default: 'primary'
+			},
+			//是否是圆点
+			dot: {
+				type: Boolean,
+				default: false
+			},
+			margin: {
+				type: String,
+				default: '0'
+			},
+			//是否绝对定位
+			absolute: {
+				type: Boolean,
+				default: false
+			},
+			top: {
+				type: String,
+				default: '-8rpx'
+			},
+			right: {
+				type: String,
+				default: '0'
+			},
+			//缩放比例
+			scaleRatio: {
+				type: Number,
+				default: 1
+			},
+			//水平方向移动距离
+			translateX: {
+				type: String,
+				default: '0'
+			}
+		},
+		computed: {
+			getStyle() {
+				return `scale(${this.scaleRatio}) translateX(${this.translateX})`;
+			}
+		},
+		methods: {
+			handleClick() {
+				this.$emit('click', {});
+			}
+		}
+	};
+</script>
+
+<style scoped>
+	/* color start*/
+
+	.tui-primary {
+		background-color: #5677fc;
+		color: #fff;
+	}
+
+	.tui-danger {
+		background-color: #ed3f14;
+		color: #fff;
+	}
+
+	.tui-red {
+		background-color: #F74D54;
+		color: #fff;
+	}
+
+	.tui-warning {
+		background-color: #ff7900;
+		color: #fff;
+	}
+
+	.tui-green {
+		background-color: #19be6b;
+		color: #fff;
+	}
+
+	.tui-white {
+		background-color: #fff;
+		color: #333;
+	}
+
+	.tui-white_red {
+		background-color: #fff;
+		color: #F74D54;
+	}
+
+	.tui-white_primary {
+		background-color: #fff;
+		color: #5677fc;
+	}
+
+	.tui-white_green {
+		background-color: #fff;
+		color: #19be6b;
+	}
+
+	.tui-white_warning {
+		background-color: #fff;
+		color: #ff7900;
+	}
+
+	.tui-black {
+		background-color: #000;
+		color: #fff;
+	}
+
+	.tui-gray {
+		background-color: #ededed;
+		color: #999;
+	}
+
+	/* color end*/
+
+	/* badge start*/
+
+	.tui-badge-dot {
+		height: 8px;
+		width: 8px;
+		border-radius: 50%;
+	}
+
+	.tui-badge {
+		font-size: 24rpx;
+		line-height: 24rpx;
+		height: 36rpx;
+		min-width: 36rpx;
+		padding: 0 10rpx;
+		box-sizing: border-box;
+		border-radius: 100rpx;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		z-index: 10;
+	}
+
+	.tui-badge-scale {
+		transform-origin: center center;
+	}
+
+	/* badge end*/
+</style>

+ 387 - 0
components/thorui/tui-bottom-navigation/tui-bottom-navigation.vue

@@ -0,0 +1,387 @@
+<template>
+	<view @touchmove.stop.prevent="stop">
+		<view class="tui-bottom-navigation" :class="{ 'tui-navigation-fixed': isFixed, 'tui-remove-splitLine': unlined }">
+			<view
+				class="tui-navigation-item"
+				:class="{ 'tui-item-after_height': splitLineScale, 'tui-last-item': index == itemList.length - 1 }"
+				:style="{ backgroundColor: isDarkMode ? '#202020' : backgroundColor }"
+				v-for="(item, index) in itemList"
+				:key="index"
+			>
+				<view class="tui-item-inner" @tap="menuClick(index, item.parameter, item.type)">
+					<image
+						:src="current | getIcon(index, item)"
+						class="tui-navigation-img"
+						v-if="item.iconPath || (current == index && item.selectedIconPath && item.type == 1)"
+					></image>
+					<text
+						class="tui-navigation-text"
+						:style="{
+							color: isDarkMode ? '#fff' : current == index && item.type == 1 ? selectedColor : item.color || color,
+							fontWeight: current == index && bold && item.type == 1 ? 'bold' : 'normal',
+							fontSize: fontSize
+						}"
+					>
+						{{ item.text }}
+					</text>
+				</view>
+				<view
+					class="tui-navigation-popup"
+					:class="{ 'tui-navigation-popup_show': showMenuIndex == index }"
+					:style="{ backgroundColor: isDarkMode ? '#4c4c4c' : subMenuBgColor, left: item.popupLeft || '50%' }"
+					v-if="item.itemList"
+				>
+					<view
+						class="tui-popup-cell"
+						:class="{ 'tui-first-cell': subIndex === 0, 'tui-last-cell': subIndex === item.itemList.length - 1 }"
+						:hover-class="subMenuHover ? (isDarkMode ? 'tui-item-dark_hover' : 'tui-item-hover') : ''"
+						:hover-stay-time="150"
+						v-for="(subItem, subIndex) in item.itemList || []"
+						:key="subIndex"
+						@tap="subMenuClick(index, item.type, subIndex, subItem.parameter)"
+					>
+						<text class="tui-ellipsis" :style="{ color: isDarkMode ? '#fff' : subMenuColor, fontSize: subMenufontSize, lineHeight: subMenufontSize }">
+							{{ subItem.text }}
+						</text>
+					</view>
+					<view class="tui-popup-triangle" :style="{ borderTopColor: isDarkMode ? '#4c4c4c' : subMenuBgColor }"></view>
+				</view>
+			</view>
+		</view>
+		<view class="tui-navigation-mask" :class="{ 'tui-navigation-mask_show': showMenuIndex != -1 }" @tap="handleClose"></view>
+	</view>
+</template>
+
+<script>
+export default {
+	name: 'tuiBottomNavigation',
+	props: {
+		//当前索引
+		current: {
+			type: Number,
+			default: 0
+		},
+		/**
+		 * {
+				text: 'ThorUI',
+				iconPath: '/static/images/common/icon_menu_gray.png',
+				selectedIconPath: '/static/images/common/icon_menu_gray.png',
+				color: '#666',
+				//1-选中切换,2-跳转、请求、其他操作,3-菜单
+				type: 3,
+				//自定义参数,类型自定义
+				parameter: null,
+				//子菜单left值,不传默认50%,当菜单贴近左右两边可用此参数调整
+				popupLeft: '',
+				itemList: [
+					{
+						//不建议超过6个字,请自行控制
+						text: '自定义参',
+						//自定义参数,类型自定义
+						parameter: null
+					},
+					{
+						text: '自定义参数',
+						//自定义参数,类型自定义
+						parameter: null
+					}
+				]
+			}
+		 * 
+		 * */
+		itemList: {
+			type: Array,
+			default: () => {
+				return [];
+			}
+		},
+		//颜色
+		color: {
+			type: String,
+			default: '#666'
+		},
+		//选中颜色
+		selectedColor: {
+			type: String,
+			default: '#5677fc'
+		},
+		fontSize: {
+			type: String,
+			default: '28rpx'
+		},
+		//选中后字体是否加粗
+		bold: {
+			type: Boolean,
+			default: true
+		},
+		//导航条背景颜色
+		backgroundColor: {
+			type: String,
+			default: '#F8F8F8'
+		},
+		//item分割线高度是否缩小
+		splitLineScale: {
+			type: Boolean,
+			default: true
+		},
+		//二级菜单字体颜色
+		subMenuColor: {
+			type: String,
+			default: '#333'
+		},
+		//二级菜单字体大小
+		subMenufontSize: {
+			type: String,
+			default: '28rpx'
+		},
+		//二级菜单背景色  深色:#4c4c4c
+		subMenuBgColor: {
+			type: String,
+			default: '#fff'
+		},
+		//二级菜单是否有点击效果
+		subMenuHover: {
+			type: Boolean,
+			default: true
+		},
+		//是否固定在底部
+		isFixed: {
+			type: Boolean,
+			default: true
+		},
+		//去除导航栏顶部的线条
+		unlined: {
+			type: Boolean,
+			default: false
+		},
+		//是否暗黑模式 (true:所有设置颜色失效)
+		isDarkMode: {
+			type: Boolean,
+			default: false
+		}
+	},
+	filters: {
+		getIcon: function(current, index, item) {
+			let url = item.iconPath;
+			if (item.type == 1) {
+				url = current == index ? item.selectedIconPath || item.iconPath : item.iconPath;
+			}
+			return url;
+		}
+	},
+	data() {
+		return {
+			showMenuIndex: -1 //显示的菜单index
+		};
+	},
+	methods: {
+		stop() {
+			return false;
+		},
+		handleClose() {
+			this.showMenuIndex = -1;
+		},
+		menuClick(index, parameter, type) {
+			//type:1-选中切换,2-跳转、请求、其他操作,3-菜单
+			if (type == 3) {
+				this.showMenuIndex = this.showMenuIndex == index ? -1 : index;
+			} else {
+				this.showMenuIndex = -1;
+				this.$emit('click', {
+					menu: 'main', //main,sub 主菜单,子菜单
+					type: type,
+					index: index,
+					parameter: parameter || ''
+				});
+			}
+		},
+		subMenuClick(index, type, subIndex, parameter) {
+			this.showMenuIndex = -1;
+			this.$emit('click', {
+				menu: 'sub', //main,sub 主菜单,子菜单
+				type: type,
+				index: index,
+				subIndex: subIndex,
+				parameter: parameter || ''
+			});
+		}
+	}
+};
+</script>
+
+<style scoped>
+.tui-bottom-navigation {
+	width: 100%;
+	height: 100rpx;
+	display: flex;
+	align-items: center;
+	justify-content: space-between;
+	position: relative;
+	z-index: 999;
+}
+
+.tui-navigation-fixed {
+	position: fixed !important;
+	left: 0;
+	bottom: 0;
+	padding-bottom: env(safe-area-inset-bottom);
+}
+
+.tui-bottom-navigation::after {
+	content: '';
+	width: 100%;
+	border-top: 1rpx solid #bfbfbf;
+	position: absolute;
+	top: 0;
+	left: 0;
+	transform: scaleY(0.5) translateZ(0);
+	transform-origin: 0 0;
+	z-index: 1000;
+}
+.tui-remove-splitLine::before {
+	border-top: 0 !important;
+}
+
+.tui-navigation-item {
+	flex: 1;
+	height: 100rpx;
+	position: relative;
+	box-sizing: border-box;
+}
+
+.tui-item-inner {
+	width: 100%;
+	height: 100rpx;
+	display: flex;
+	text-align: center;
+	align-items: center;
+	justify-content: center;
+}
+
+.tui-navigation-item::after {
+	height: 100%;
+	content: '';
+	position: absolute;
+	border-right: 1rpx solid #bfbfbf;
+	transform: scaleX(0.5) translateZ(0);
+	right: 0;
+	top: 0;
+}
+
+.tui-item-after_height::after {
+	height: 40% !important;
+	top: 30% !important;
+}
+
+.tui-last-item::after {
+	border-right: 0 !important;
+}
+
+.tui-navigation-img {
+	width: 32rpx;
+	height: 32rpx;
+	margin-right: 8rpx;
+}
+
+.tui-navigation-popup {
+	max-width: 160%;
+	width: auto;
+	position: absolute;
+	border-radius: 8rpx;
+	visibility: hidden;
+	opacity: 0;
+	transform: translate3d(-50%, 0, 0);
+	transform-origin: center;
+	transition: all 0.12s ease-in-out;
+	bottom: 0;
+	z-index: -1;
+}
+
+.tui-navigation-popup_show {
+	transform: translate3d(-50%, -124rpx, 0);
+	visibility: visible;
+	opacity: 1;
+}
+
+.tui-popup-triangle {
+	position: absolute;
+	width: 0;
+	height: 0;
+	border-left: 9rpx solid transparent;
+	border-right: 9rpx solid transparent;
+	border-top: 18rpx solid;
+	left: 50%;
+	bottom: -18rpx;
+	-webkit-transform: translateX(-50%);
+	transform: translateX(-50%);
+	z-index: 997;
+}
+
+.tui-popup-cell {
+	width: 100%;
+	padding: 32rpx 20rpx;
+	box-sizing: border-box;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	flex: 1;
+	position: relative;
+}
+
+.tui-ellipsis {
+	white-space: nowrap;
+	overflow: hidden;
+	text-overflow: ellipsis;
+}
+
+.tui-popup-cell::after {
+	content: '';
+	position: absolute;
+	border-bottom: 1rpx solid #eaeef1;
+	-webkit-transform: scaleY(0.5);
+	transform: scaleY(0.5);
+	bottom: 0;
+	right: 24rpx;
+	left: 24rpx;
+}
+
+.tui-item-hover {
+	background-color: #f1f1f1;
+}
+
+.tui-item-dark_hover {
+	background-color: #555;
+}
+
+.tui-first-cell {
+	border-top-left-radius: 8rpx;
+	border-top-right-radius: 8rpx;
+}
+
+.tui-last-cell {
+	border-bottom-left-radius: 8rpx;
+	border-bottom-right-radius: 8rpx;
+}
+
+.tui-last-cell::after {
+	border-bottom: 0 !important;
+}
+
+.tui-navigation-mask {
+	position: fixed;
+	top: 0;
+	left: 0;
+	right: 0;
+	bottom: 0;
+	z-index: 995;
+	transition: all 0.3s ease-in-out;
+	opacity: 0;
+	visibility: hidden;
+	background-color: rgba(0, 0, 0, 0);
+}
+
+.tui-navigation-mask_show {
+	opacity: 1;
+	visibility: visible;
+}
+</style>

+ 96 - 0
components/thorui/tui-bottom-popup/tui-bottom-popup.vue

@@ -0,0 +1,96 @@
+<template>
+	<view @touchmove.stop.prevent>
+		<view class="tui-popup-class tui-bottom-popup" :class="{'tui-popup-show':show,'tui-popup-radius':radius}" :style="{backgroundColor:backgroundColor,height:height?height+'rpx':'auto'}">
+			<slot></slot>
+		</view>
+		<view class="tui-popup-mask" :class="[show?'tui-mask-show':'']" v-if="mask" @tap="handleClose"></view>
+	</view>
+</template>
+
+<script>
+	export default {
+		name: "tuiBottomPopup",
+		props: {
+			//是否需要mask
+			mask: {
+				type: Boolean,
+				default: true
+			},
+			//控制显示
+			show: {
+				type: Boolean,
+				default: false
+			},
+			//背景颜色
+			backgroundColor: {
+				type: String,
+				default: "#fff"
+			},
+			//高度 rpx
+			height: {
+				type: Number,
+				default: 0
+			},
+			//设置圆角
+			radius:{
+				type:Boolean,
+				default:true
+			}
+		},
+		methods: {
+			handleClose() {
+				if (!this.show) {
+					return;
+				}
+				this.$emit('close', {});
+			}
+		}
+	}
+</script>
+
+<style scoped>
+	.tui-bottom-popup {
+		width: 100%;
+		position: fixed;
+		left: 0;
+		right: 0;
+		bottom: 0;
+		z-index: 997;
+		/* visibility: hidden; */
+		opacity: 0;
+		transform: translate3d(0, 100%, 0);
+		transform-origin: center;
+		transition: all 0.3s ease-in-out;
+		min-height: 20rpx;
+	}
+	.tui-popup-radius {
+		border-top-left-radius: 24rpx;
+		border-top-right-radius: 24rpx;
+		padding-bottom: env(safe-area-inset-bottom);
+		overflow: hidden;
+	}
+
+	.tui-popup-show {
+		transform: translate3d(0, 0, 0);
+		opacity: 1;
+		/* visibility: visible; */
+	}
+
+	.tui-popup-mask {
+		position: fixed;
+		top: 0;
+		left: 0;
+		right: 0;
+		bottom: 0;
+		background-color: rgba(0, 0, 0, 0.6);
+		z-index: 996;
+		transition: all 0.3s ease-in-out;
+		opacity: 0;
+		visibility: hidden;
+	}
+
+	.tui-mask-show {
+		opacity: 1;
+		visibility: visible;
+	}
+</style>

+ 203 - 0
components/thorui/tui-bubble-popup/tui-bubble-popup.vue

@@ -0,0 +1,203 @@
+<template>
+	<view :class="{ 'tui-flex-end': flexEnd }">
+		<view class="tui-popup-list" :class="{ 'tui-popup-show': show,'tui-z_index':show && position!='relative' }" :style="{ width: width, backgroundColor: backgroundColor, borderRadius: radius, color: color, position: position, left: left, right: right, bottom: bottom, top: top,transform:`translate(${translateX},${translateY})` }">
+			<view class="tui-triangle" :style="{
+					borderWidth: borderWidth,
+					borderColor: `transparent transparent ${backgroundColor} transparent`,
+					left: triangleLeft,
+					right: triangleRight,
+					top: triangleTop,
+					bottom: triangleBottom
+				}"
+			 v-if="direction == 'top'"></view>
+			<view class="tui-triangle" :style="{
+					borderWidth: borderWidth,
+					borderColor: `${backgroundColor}  transparent transparent transparent`,
+					left: triangleLeft,
+					right: triangleRight,
+					top: triangleTop,
+					bottom: triangleBottom
+				}"
+			 v-if="direction == 'bottom'"></view>
+			<view class="tui-triangle" :style="{
+					borderWidth: borderWidth,
+					borderColor: `transparent  ${backgroundColor} transparent transparent`,
+					left: triangleLeft,
+					right: triangleRight,
+					top: triangleTop,
+					bottom: triangleBottom
+				}"
+			 v-if="direction == 'left'"></view>
+			<view class="tui-triangle" :style="{
+					borderWidth: borderWidth,
+					borderColor: `transparent transparent  transparent ${backgroundColor}`,
+					left: triangleLeft,
+					right: triangleRight,
+					top: triangleTop,
+					bottom: triangleBottom
+				}"
+			 v-if="direction == 'right'"></view>
+			<slot />
+		</view>
+		<view @touchmove.stop.prevent="stop" class="tui-popup-mask" :class="{ 'tui-popup-show': show }" :style="{ backgroundColor: maskBgColor }"
+		 v-if="mask" @tap="handleClose"></view>
+	</view>
+</template>
+<script>
+	export default {
+		name: 'tuiBubblePopup',
+		props: {
+			//宽度
+			width: {
+				type: String,
+				default: '300rpx'
+			},
+			//popup圆角
+			radius: {
+				type: String,
+				default: '8rpx'
+			},
+			//popup 定位 left right top bottom值
+			left: {
+				type: String,
+				default: 'auto'
+			},
+			right: {
+				type: String,
+				default: 'auto'
+			},
+			top: {
+				type: String,
+				default: 'auto'
+			},
+			bottom: {
+				type: String,
+				default: 'auto'
+			},
+			translateX:{
+				type: String,
+				default: '0'
+			},
+			translateY:{
+				type: String,
+				default: '0'
+			},
+			//背景颜色
+			backgroundColor: {
+				type: String,
+				default: '#4c4c4c'
+			},
+			//字体颜色
+			color: {
+				type: String,
+				default: '#fff'
+			},
+			//三角border-width
+			borderWidth: {
+				type: String,
+				default: '12rpx'
+			},
+			//三角形方向 top left right bottom
+			direction: {
+				type: String,
+				default: 'top'
+			},
+			//定位 left right top bottom值
+			triangleLeft: {
+				type: String,
+				default: 'auto'
+			},
+			triangleRight: {
+				type: String,
+				default: 'auto'
+			},
+			triangleTop: {
+				type: String,
+				default: 'auto'
+			},
+			triangleBottom: {
+				type: String,
+				default: 'auto'
+			},
+			//定位 relative absolute  fixed
+			position: {
+				type: String,
+				default: 'fixed'
+			},
+			//flex-end
+			flexEnd: {
+				type: Boolean,
+				default: false
+			},
+			//是否需要mask
+			mask: {
+				type: Boolean,
+				default: true
+			},
+			maskBgColor: {
+				type: String,
+				default: 'rgba(0, 0, 0, 0.4)'
+			},
+			//控制显示
+			show: {
+				type: Boolean,
+				default: false
+			}
+		},
+		methods: {
+			handleClose() {
+				if (!this.show) {
+					return;
+				}
+				this.$emit('close', {});
+			},
+			stop() {
+				return false;
+			}
+		}
+	};
+</script>
+
+<style scoped>
+	.tui-popup-list {
+		z-index: 1;
+		transition: all 0.3s ease-in-out;
+		opacity: 0;
+		visibility: hidden;
+	}
+
+	.tui-flex-end {
+		width: 100%;
+		display: flex;
+		justify-content: flex-end;
+	}
+
+	.tui-triangle {
+		position: absolute;
+		width: 0;
+		height: 0;
+		border-style: solid;
+		z-index: 997;
+	}
+
+	.tui-popup-mask {
+		position: fixed;
+		top: 0;
+		left: 0;
+		right: 0;
+		bottom: 0;
+		z-index: 995;
+		transition: all 0.3s ease-in-out;
+		opacity: 0;
+		visibility: hidden;
+	}
+
+	.tui-popup-show {
+		opacity: 1;
+		visibility: visible;
+	}
+
+	.tui-z_index {
+		z-index: 996;
+	}
+</style>

+ 401 - 0
components/thorui/tui-button/tui-button.vue

@@ -0,0 +1,401 @@
+<template>
+	<button class="tui-btn" :class="[
+			plain ? 'tui-' + type + '-outline' : 'tui-btn-' + (type || 'primary'),
+			getDisabledClass(disabled, type, plain),
+			getShapeClass(shape, plain),
+			getShadowClass(type, shadow, plain)
+		]"
+	 :hover-class="getHoverClass(disabled, type, plain)" :style="{ width: width, height: height, lineHeight: height, fontSize: size + 'rpx',margin:margin }"
+	 :loading="loading" :open-type="openType" @getuserinfo="bindgetuserinfo" :disabled="disabled" @tap="handleClick">
+		<slot></slot>
+	</button>
+</template>
+
+<script>
+	export default {
+		name: 'tuiButton',
+		props: {
+			//样式类型 primary, white, danger, warning, green,blue, gray,black
+			type: {
+				type: String,
+				default: 'primary'
+			},
+			//是否加阴影
+			shadow: {
+				type: Boolean,
+				default: false
+			},
+			// 宽度 rpx或 %
+			width: {
+				type: String,
+				default: '100%'
+			},
+			//高度 rpx
+			height: {
+				type: String,
+				default: '96rpx'
+			},
+			//字体大小 rpx
+			size: {
+				type: Number,
+				default: 32
+			},
+			margin: {
+				type: String,
+				default: "0"
+			},
+			//形状 circle(圆角), square(默认方形),rightAngle(平角)
+			shape: {
+				type: String,
+				default: 'square'
+			},
+			plain: {
+				type: Boolean,
+				default: false
+			},
+			disabled: {
+				type: Boolean,
+				default: false
+			},
+			//禁用后背景是否为灰色 (非空心button生效)
+			disabledGray: {
+				type: Boolean,
+				default: false
+			},
+			loading: {
+				type: Boolean,
+				default: false
+			},
+			openType: {
+				type: String,
+				default: ''
+			},
+			index: {
+				type: [Number, String],
+				default: 0
+			}
+		},
+		methods: {
+			handleClick() {
+				if (this.disabled) return false;
+				this.$emit('click', {
+					index: Number(this.index)
+				});
+			},
+			bindgetuserinfo({
+				detail = {}
+			} = {}) {
+				this.$emit('getuserinfo', detail);
+			},
+			getShadowClass: function(type, shadow, plain) {
+				let className = '';
+				if (shadow && type != 'white' && !plain) {
+					className = 'tui-shadow-' + type;
+				}
+				return className;
+			},
+			getDisabledClass: function(disabled, type, plain) {
+				let className = '';
+				if (disabled && type != 'white') {
+					let classVal = this.disabledGray ? 'tui-gray-disabled' : 'tui-dark-disabled';
+					className = plain ? 'tui-dark-disabled-outline' : classVal;
+				}
+				return className;
+			},
+			getShapeClass: function(shape, plain) {
+				let className = '';
+				if (shape == 'circle') {
+					className = plain ? 'tui-outline-fillet' : 'tui-fillet';
+				} else if (shape == 'rightAngle') {
+					className = plain ? 'tui-outline-rightAngle' : 'tui-rightAngle';
+				}
+				return className;
+			},
+			getHoverClass: function(disabled, type, plain) {
+				let className = '';
+				if (!disabled) {
+					className = plain ? 'tui-outline-hover' : 'tui-' + (type || 'primary') + '-hover';
+				}
+				return className;
+			}
+		}
+	};
+</script>
+
+<style scoped>
+	.tui-btn-primary {
+		background: #5677fc !important;
+		color: #fff;
+	}
+
+	.tui-shadow-primary {
+		box-shadow: 0 10rpx 14rpx 0 rgba(86, 119, 252, 0.2);
+	}
+
+	.tui-btn-danger {
+		background: #eb0909 !important;
+		color: #fff;
+	}
+
+	.tui-shadow-danger {
+		box-shadow: 0 10rpx 14rpx 0 rgba(235, 9, 9, 0.2);
+	}
+
+	.tui-btn-warning {
+		background: #fc872d !important;
+		color: #fff;
+	}
+
+	.tui-shadow-warning {
+		box-shadow: 0 10rpx 14rpx 0 rgba(252, 135, 45, 0.2);
+	}
+
+	.tui-btn-green {
+		background: #35b06a !important;
+		color: #fff;
+	}
+
+	.tui-shadow-green {
+		box-shadow: 0 10rpx 14rpx 0 rgba(53, 176, 106, 0.2);
+	}
+
+	.tui-btn-blue {
+		background: #007AFF !important;
+		color: #fff;
+	}
+
+	.tui-shadow-blue {
+		box-shadow: 0 10rpx 14rpx 0 rgba(0, 122, 255, 0.2);
+	}
+
+	.tui-btn-white {
+		background: #fff !important;
+		color: #333 !important;
+	}
+
+	.tui-btn-gray {
+		background: #bfbfbf !important;
+		color: #fff !important;
+	}
+
+	.tui-btn-black {
+		background: #333 !important;
+		color: #fff !important;
+	}
+
+	.tui-shadow-gray {
+		box-shadow: 0 10rpx 14rpx 0 rgba(191, 191, 191, 0.2);
+	}
+
+	.tui-hover-gray {
+		background: #f7f7f9 !important;
+	}
+
+	.tui-black-hover {
+		background: #555 !important;
+		color: #e5e5e5 !important;
+	}
+
+	/* button start*/
+
+	.tui-btn {
+		width: 100%;
+		position: relative;
+		border: 0 !important;
+		border-radius: 6rpx;
+		padding-left: 0;
+		padding-right: 0;
+		overflow: visible;
+	}
+
+	.tui-btn::after {
+		content: '';
+		position: absolute;
+		width: 200%;
+		height: 200%;
+		transform-origin: 0 0;
+		transform: scale(0.5, 0.5) translateZ(0);
+		box-sizing: border-box;
+		left: 0;
+		top: 0;
+		border-radius: 12rpx;
+		border: 0;
+	}
+
+	.tui-btn-white::after {
+		border: 1rpx solid #bfbfbf;
+	}
+
+	.tui-white-hover {
+		background: #e5e5e5 !important;
+		color: #2e2e2e !important;
+	}
+
+	.tui-dark-disabled {
+		opacity: 0.6 !important;
+		color: #fafbfc !important;
+	}
+
+	.tui-dark-disabled-outline {
+		opacity: 0.5 !important;
+	}
+
+	.tui-gray-disabled {
+		background: #f3f3f3 !important;
+		color: #919191 !important;
+		box-shadow: none;
+	}
+
+	.tui-outline-hover {
+		opacity: 0.5;
+	}
+
+	.tui-primary-hover {
+		background: #4a67d6 !important;
+		color: #e5e5e5 !important;
+	}
+
+	.tui-primary-outline::after {
+		border: 1rpx solid #5677fc !important;
+	}
+
+	.tui-primary-outline {
+		color: #5677fc !important;
+		background: transparent;
+	}
+
+	.tui-danger-hover {
+		background: #c80808 !important;
+		color: #e5e5e5 !important;
+	}
+
+	.tui-danger-outline {
+		color: #eb0909 !important;
+		background: transparent;
+	}
+
+	.tui-danger-outline::after {
+		border: 1rpx solid #eb0909 !important;
+	}
+
+	.tui-warning-hover {
+		background: #d67326 !important;
+		color: #e5e5e5 !important;
+	}
+
+	.tui-warning-outline {
+		color: #fc872d !important;
+		background: transparent;
+	}
+
+	.tui-warning-outline::after {
+		border: 1px solid #fc872d !important;
+	}
+
+	.tui-green-hover {
+		background: #2d965a !important;
+		color: #e5e5e5 !important;
+	}
+
+	.tui-green-outline {
+		color: #35b06a !important;
+		background: transparent;
+	}
+
+	.tui-green-outline::after {
+		border: 1rpx solid #35b06a !important;
+	}
+
+	.tui-blue-hover {
+		background: #0062CC !important;
+		color: #e5e5e5 !important;
+	}
+
+	.tui-blue-outline {
+		color: #007AFF !important;
+		background: transparent;
+	}
+
+	.tui-blue-outline::after {
+		border: 1rpx solid #007AFF !important;
+	}
+
+	/* #ifndef APP-NVUE */
+	.tui-btn-gradual {
+		background: linear-gradient(90deg, rgb(255, 89, 38), rgb(240, 14, 44)) !important;
+		color: #fff !important;
+	}
+
+	.tui-shadow-gradual {
+		box-shadow: 0 10rpx 14rpx 0 rgba(235, 9, 9, 0.15);
+	}
+
+	/* #endif */
+
+	.tui-gray-hover {
+		background: #a3a3a3 !important;
+		color: #898989;
+	}
+
+	/* #ifndef APP-NVUE */
+	.tui-gradual-hover {
+		background: linear-gradient(90deg, #d74620, #cd1225) !important;
+		color: #fff !important;
+	}
+
+	/* #endif */
+
+	.tui-gray-outline {
+		color: #999 !important;
+		background: transparent !important;
+	}
+
+	.tui-white-outline {
+		color: #fff !important;
+		background: transparent !important;
+	}
+
+	.tui-black-outline {
+		background: transparent !important;
+		color: #333 !important;
+	}
+
+	.tui-gray-outline::after {
+		border: 1rpx solid #ccc !important;
+	}
+
+	.tui-white-outline::after {
+		border: 1px solid #fff !important;
+	}
+
+	.tui-black-outline::after {
+		border: 1px solid #333 !important;
+	}
+
+	/*圆角 */
+
+	.tui-fillet {
+		border-radius: 50rpx;
+	}
+
+	.tui-btn-white.tui-fillet::after {
+		border-radius: 98rpx;
+	}
+
+	.tui-outline-fillet::after {
+		border-radius: 98rpx;
+	}
+
+	/*平角*/
+	.tui-rightAngle {
+		border-radius: 0;
+	}
+
+	.tui-btn-white.tui-rightAngle::after {
+		border-radius: 0;
+	}
+
+	.tui-outline-rightAngle::after {
+		border-radius: 0;
+	}
+</style>

+ 562 - 0
components/thorui/tui-calendar/tui-calendar.js

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

+ 814 - 0
components/thorui/tui-calendar/tui-calendar.vue

@@ -0,0 +1,814 @@
+<template>
+	<view>
+		<view :class="{ 'tui-bottom-popup': isFixed, 'tui-popup-show': isShow && isFixed }">
+			<view class="tui-calendar-header" :class="{ 'tui-calendar-radius': radius }" @touchmove.stop.prevent="stop" v-if="isFixed">
+				<view>日期选择</view>
+				<view class="tui-iconfont tui-font-close" hover-class="tui-opacity" :hover-stay-time="150" @tap="hide"></view>
+			</view>
+
+			<view class="tui-date-box">
+				<view class="tui-iconfont tui-font-arrowleft" :style="{ color: yearArrowColor }" hover-class="tui-opacity"
+				 :hover-stay-time="150" v-if="arrowType == 1" @tap="changeYear(0)"></view>
+				<view class="tui-iconfont tui-font-arrowleft" :style="{ color: monthArrowColor }" hover-class="tui-opacity"
+				 :hover-stay-time="150" @tap="changeMonth(0)"></view>
+				<view class="tui-date_time">{{ showTitle }}</view>
+				<view class="tui-iconfont tui-font-arrowright" :style="{ color: monthArrowColor }" hover-class="tui-opacity"
+				 :hover-stay-time="150" @tap="changeMonth(1)"></view>
+				<view class="tui-iconfont tui-font-arrowright" :style="{ color: yearArrowColor }" hover-class="tui-opacity"
+				 :hover-stay-time="150" v-if="arrowType == 1" @tap="changeYear(1)"></view>
+			</view>
+			<view class="tui-date-header">
+				<view class="tui-date">日</view>
+				<view class="tui-date">一</view>
+				<view class="tui-date">二</view>
+				<view class="tui-date">三</view>
+				<view class="tui-date">四</view>
+				<view class="tui-date">五</view>
+				<view class="tui-date">六</view>
+			</view>
+			<view class="tui-date-content" :class="{ 'tui-flex-start': isFixed && fixedHeight }" :style="{ height: isFixed && fixedHeight ? dateHeight * 6 + 'px' : 'auto' }">
+				<block v-for="(item, index) in weekdayArr" :key="index">
+					<view class="tui-date"></view>
+				</block>
+				<view class="tui-date" :class="{
+						'tui-date-pd_0': isFixed && fixedHeight,
+						'tui-opacity': openDisAbled(year, month, index + 1),
+						'tui-start-date': (type == 2 && startDate == `${year}-${month}-${index + 1}`) || type == 1,
+						'tui-end-date': (type == 2 && endDate == `${year}-${month}-${index + 1}`) || type == 1
+					}"
+				 :style="{ backgroundColor: isFixed ? getColor(index, 1) : 'transparent', height: isFixed && fixedHeight ? dateHeight + 'px' : 'auto' }"
+				 v-for="(item, index) in daysArr" :key="index" @tap="dateClick(index)">
+					<view class="tui-date-text" :style="{ color: isFixed ? getColor(index, 2) : getStatusData(3, index), backgroundColor: getStatusData(2, index) }">
+						<view v-if="isFixed || !getStatusData(4, index)">{{ index + 1 }}</view>
+						<view v-if="!getStatusData(4, index)" class="tui-custom-desc" :class="{'tui-lunar-unshow':!lunar && isFixed}">{{ getDescText(index,startDate,endDate)}}</view>
+						<text class="tui-iconfont tui-font-check" v-if="getStatusData(4, index)"></text>
+					</view>
+					<view class="tui-date-desc" :style="{ color: activeColor }" v-if="!lunar && type == 2 && startDate == `${year}-${month}-${index + 1}` && startDate != endDate">
+						{{ startText }}
+					</view>
+					<view class="tui-date-desc" :style="{ color: activeColor }" v-if="!lunar && type == 2 && endDate == `${year}-${month}-${index + 1}`">{{ endText }}</view>
+				</view>
+				<view class="tui-bg-month">{{ month }}</view>
+			</view>
+
+			<view class="tui-calendar-op" v-if="isFixed" @touchmove.stop.prevent="stop">
+				<view class="tui-calendar-result">
+					<text>{{ type == 1 ? activeDate : startDate }}</text>
+					<text v-if="endDate">至{{ endDate }}</text>
+				</view>
+				<view class="tui-calendar-btn_box">
+					<tui-button :type="btnType" height="72rpx" shape="circle" :size="28" @click="btnFix(false)">确定</tui-button>
+				</view>
+			</view>
+		</view>
+
+		<view class="tui-popup-mask" :class="[isShow ? 'tui-mask-show' : '']" @touchmove.stop.prevent="stop" v-if="isFixed"
+		 @tap="hide"></view>
+	</view>
+</template>
+<script>
+	//easycom组件模式 无需手动引入
+	// import tuiButton from "../tui-button/tui-button"
+	const calendar = require("./tui-calendar.js")
+	export default {
+		name: 'tuiCalendar',
+		// components:{
+		// 	tuiButton
+		// },
+		props: {
+			//1-切换月份和年份 2-切换月份
+			arrowType: {
+				type: [Number, String],
+				default: 1
+			},
+			//1-单个日期选择 2-开始日期+结束日期选择
+			type: {
+				type: Number,
+				default: 1
+			},
+			//可切换最大年份
+			maxYear: {
+				type: Number,
+				default: 2030
+			},
+			//可切换最小年份
+			minYear: {
+				type: Number,
+				default: 1920
+			},
+			//最小可选日期(不在范围内日期禁用不可选)
+			minDate: {
+				type: String,
+				default: '1920-01-01'
+			},
+			/**
+			 * 最大可选日期
+			 * 默认最大值为今天,之后的日期不可选
+			 * 2030-12-31
+			 * */
+			maxDate: {
+				type: String,
+				default: ''
+			},
+			//显示圆角
+			radius: {
+				type: Boolean,
+				default: true
+			},
+			//状态 数据顺序与当月天数一致,index=>day
+			/**
+				 * [{
+					 * text:"", 描述:2字以内
+					 * value:"",状态值 
+					 * bgColor:"",背景色
+					 * color:""  文字颜色,
+					 * check:false //是否显示对勾
+					 * 
+				 }]
+				 * 
+				 * **/
+			status: {
+				type: Array,
+				default () {
+					return [];
+				}
+			},
+			//月份切换箭头颜色
+			monthArrowColor: {
+				type: String,
+				default: '#999'
+			},
+			//年份切换箭头颜色
+			yearArrowColor: {
+				type: String,
+				default: '#bcbcbc'
+			},
+			//默认日期字体颜色
+			color: {
+				type: String,
+				default: '#333'
+			},
+			//选中|起始结束日期背景色
+			activeBgColor: {
+				type: String,
+				default: '#5677fc'
+			},
+			//选中|起始结束日期字体颜色
+			activeColor: {
+				type: String,
+				default: '#fff'
+			},
+			//范围内日期背景色
+			rangeBgColor: {
+				type: String,
+				default: 'rgba(86,119,252,0.1)'
+			},
+			//范围内日期字体颜色
+			rangeColor: {
+				type: String,
+				default: '#5677fc'
+			},
+			//type=2时生效,起始日期自定义文案
+			startText: {
+				type: String,
+				default: '开始'
+			},
+			//type=2时生效,结束日期自定义文案
+			endText: {
+				type: String,
+				default: '结束'
+			},
+			//按钮样式类型
+			btnType: {
+				type: String,
+				default: 'primary'
+			},
+			//固定在底部
+			isFixed: {
+				type: Boolean,
+				default: false
+			},
+			//固定日历容器高度,isFixed=true时生效
+			fixedHeight: {
+				type: Boolean,
+				default: true
+			},
+			//当前选中日期带选中效果
+			isActiveCurrent: {
+				type: Boolean,
+				default: true
+			},
+			//切换年月是否触发事件 type=1时生效
+			isChange: {
+				type: Boolean,
+				default: false
+			},
+			//是否显示农历
+			lunar: {
+				type: Boolean,
+				default: false
+			}
+		},
+		data() {
+			return {
+				isShow: false,
+				weekday: 1, // 星期几,值为1-7
+				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,
+				dateHeight: 20
+			};
+		},
+		computed: {
+			dataChange() {
+				return `${this.type}-${this.minDate}-${this.maxDate}`;
+			}
+		},
+		watch: {
+			dataChange(val) {
+				this.init();
+			},
+			fixedHeight(val) {
+				if (val) {
+					this.initDateHeight()
+				}
+			}
+		},
+		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;
+			},
+			//获取状态数据
+			getStatusData(type, index) {
+				//1-描述text,2-bgColor背景色,3-color文字颜色 4-check 是否显示对勾
+				let val = ['', 'transparent', '#333', ''][type - 1];
+				if (!this.isFixed && this.status && this.status.length > 0) {
+					let item = this.status[index];
+					if (item) {
+						switch (type) {
+							case 1:
+								val = item.text;
+								break;
+							case 2:
+								val = item.bgColor;
+								break;
+							case 3:
+								val = item.color;
+								break;
+							case 4:
+								val = item.check;
+								break;
+							default:
+								break;
+						}
+					}
+				}
+				return val;
+			},
+			getDescText(index, startDate, endDate) {
+				let text = this.lunar ? this.getLunar(this.year, this.month, index + 1) : "";
+				if (this.isFixed && this.type == 2) {
+					//此判断不能与上面条件一起判断
+					if(this.lunar){
+						let date = `${this.year}-${this.month}-${index + 1}`;
+						if (startDate == date && startDate != endDate) {
+							text = this.startText
+						} else if (endDate == date) {
+							text = this.endText
+						}
+					}
+				} else {
+					let status = this.getStatusData(1, index);
+					if (status)
+						text = status;
+				}
+				return text;
+			},
+			getLunar(year, month, day) {
+				let obj = calendar.solar2lunar(year, month, day);
+				return obj.IDayCn
+			},
+			initDateHeight() {
+				if (this.fixedHeight && this.isFixed) {
+					this.dateHeight = uni.getSystemInfoSync().windowWidth / 7;
+				}
+			},
+			init() {
+				this.initDateHeight();
+				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 + '';
+			},
+			stop() {
+				return !this.isFixed;
+			},
+			//一个月有多少天
+			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;
+			},
+			changeMonth(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();
+					}
+				}
+			},
+			changeYear(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.type == 1) {
+					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.type == 1) {
+						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;
+						}
+					}
+					if (!this.isFixed) {
+						this.btnFix();
+					}
+				}
+			},
+			show() {
+				this.isShow = true;
+			},
+			hide() {
+				this.isShow = false;
+			},
+			getWeekText(date) {
+				date = new Date(`${date.replace(/\-/g, '/')} 00:00:00`);
+				let week = date.getDay();
+				return '星期' + ['日', '一', '二', '三', '四', '五', '六'][week];
+			},
+			btnFix(show) {
+				if (!show) {
+					this.hide();
+				}
+				if (this.type == 1) {
+					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;
+					}
+					let lunar = calendar.solar2lunar(year, month, day)
+					this.$emit('change', {
+						year: year,
+						month: month,
+						day: day,
+						days: days,
+						result: result,
+						week: weekText,
+						isToday: isToday,
+						switch: show, //是否是切换年月操作
+						lunar: lunar
+					});
+				} 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 startLunar = calendar.solar2lunar(this.startYear, startMonth, startDay);
+
+					let endMonth = this.formatNum(this.endMonth);
+					let endDay = this.formatNum(this.endDay);
+					let endDate = `${this.endYear}-${endMonth}-${endDay}`;
+					let endWeek = this.getWeekText(endDate);
+					let endLunar = calendar.solar2lunar(this.endYear, endMonth, endDay);
+					this.$emit('change', {
+						startYear: this.startYear,
+						startMonth: this.startMonth,
+						startDay: this.startDay,
+						startDate: startDate,
+						startWeek: startWeek,
+						startLunar: startLunar,
+						endYear: this.endYear,
+						endMonth: this.endMonth,
+						endDay: this.endDay,
+						endDate: endDate,
+						endWeek: endWeek,
+						endLunar: endLunar
+					});
+				}
+			}
+		}
+	};
+</script>
+
+<style scoped>
+	@font-face {
+		font-family: 'tuiDateFont';
+		src: url(data:application/font-woff;charset=utf-8;base64,d09GRgABAAAAAAVgAA0AAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABGRlRNAAAFRAAAABoAAAAci0/w50dERUYAAAUkAAAAHgAAAB4AKQANT1MvMgAAAaAAAABDAAAAVjxuSNNjbWFwAAAB+AAAAEoAAAFS5iPQt2dhc3AAAAUcAAAACAAAAAj//wADZ2x5ZgAAAlQAAAFHAAABvPf29TBoZWFkAAABMAAAADAAAAA2GMsN3WhoZWEAAAFgAAAAHQAAACQHjAOFaG10eAAAAeQAAAATAAAAFgzQAPJsb2NhAAACRAAAABAAAAAQAOoBSG1heHAAAAGAAAAAHgAAACABEwA3bmFtZQAAA5wAAAFJAAACiCnmEVVwb3N0AAAE6AAAADQAAABLUwjqHHjaY2BkYGAAYp5Gj5/x/DZfGbhZGEDg1tUn7+F00P/LzOuY9YFcDgYmkCgAa0gNlHjaY2BkYGBu+N/AEMPCAALM6xgYGVABCwBT4AMaAAAAeNpjYGRgYGBn0GZgYgABEMkFhAwM/8F8BgANaAFLAAB42mNgZGFgnMDAysDA1Ml0hoGBoR9CM75mMGLkAIoysDIzYAUBaa4pDA7PGJ49ZG7438AQw9zA0AAUZgTJAQDrcAy8AHjaY2GAABYIDgLCBQx1AAcEAc8AeNpjYGBgZoBgGQZGBhDwAfIYwXwWBgMgzQGETAwMzxifcTx7+P8/kMUAYUkxS/6VVIXqAgNGNgY4lxGoB6QPBTAyDHsAADDkDYkAAAAAAAAAAAAAADQAagC2AN542m2QsU7DMBCG/Tt1bNPUiUnkSgiVtqKpxJAgVLVbeAa6MaK+B4JXgJWBjY21UtW5gpkdMTFX7dzApaJLhXU6n8+n//ttxtn458N79XJWZ8eMxS00C4wy9A1EP8PQncAlIQzS4WgsVtPpSmwzV3OFRqLetH5TSQMK939X61ptPZ2p2EAttNMLBRMrtschQblDeS34aY50cIkCzg/B2Y5C+VpyQxhFkRgu515O8jvU5mmPM2O0wJ5Z27vhX+yMsV437WvCdTM+GI40MgwKfuGammC0uURqeqFMfe9cxaJclkt5GMaB1hIR1VobOgpEiKq+sLZcIrJWhO3/Jw7qWlYj1Jf21FaCtmd5bevrlk28O/7A4spXTl4KTh9MTlqQ8PESBRstReic+sRj0Dni9fIqmNS/pXNWCvWOeYBmx5S9Bsn9Ah+5WtAAeNp9kD1OAzEQhZ/zByQSQiCoXVEA2vyUKRMp9Ailo0g23pBo1155nUg5AS0VB6DlGByAGyDRcgpelkmTImvt6PObmeexAZzjGwr/3yXuhBWO8ShcwREy4Sr1F+Ea+V24jhY+hRvUf4SbuFUD4RYu1BsdVO2Eu5vSbcsKZxgIV3CKJ+Eq9ZVwjfwqXMcVPoQb1L+EmxjjV7iFa2WpDOFhMEFgnEFjig3jAjEcLJIyBtahOfRmEsxMTzd6ETubOBso71dilwMeaDnngCntPbdmvkon/mDLgdSYbh4FS7YpjS4idCgbXyyc1d2oc7D9nu22tNi/a4E1x+xRDWzU/D3bM9JIbAyvkJI18jK3pBJTj2hrrPG7ZynW814IiU68y/SIx5o0dTr3bmniwOLn8owcfbS5kj33qBw+Y1kIeb/dTsQgil2GP5PYcRkAAAB42mNgYoAALjDJyIAO2MGiTIxMjMyMLIys7GmJeRmlmWZQ2pQ5OSORLaU0Mz2/FACDfwlbAAAAAf//AAIAAQAAAAwAAAAWAAAAAgABAAMABgABAAQAAAACAAAAAHjaY2BgYGQAgqtL1DlA9K2rT97DaABNlwiuAAA=) format('woff');
+		font-weight: normal;
+		font-style: normal;
+	}
+
+	.tui-iconfont {
+		font-family: 'tuiDateFont' !important;
+		font-size: 36rpx;
+		font-style: normal;
+		-webkit-font-smoothing: antialiased;
+		-moz-osx-font-smoothing: grayscale;
+	}
+
+	.tui-font-close:before {
+		content: '\e608';
+	}
+
+	.tui-font-check:before {
+		content: '\e6e1';
+	}
+
+	.tui-font-arrowright:before {
+		content: '\e600';
+	}
+
+	.tui-font-arrowleft:before {
+		content: '\e601';
+	}
+
+	.tui-date-box {
+		width: 100%;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		padding: 20rpx 0 30rpx;
+		background-color: #fff;
+	}
+
+	.tui-calendar-radius {
+		border-top-left-radius: 20rpx;
+		border-top-right-radius: 20rpx;
+		overflow: hidden;
+	}
+
+	.tui-date_time {
+		padding: 0 16rpx;
+		color: #333;
+		font-size: 32rpx;
+		line-height: 32rpx;
+		font-weight: bold;
+	}
+
+	.tui-font-arrowleft {
+		margin-right: 32rpx;
+	}
+
+	.tui-font-arrowright {
+		margin-left: 32rpx;
+	}
+
+	.tui-date-header {
+		width: 100%;
+		display: flex;
+		align-items: center;
+		background-color: #fff;
+		font-size: 24rpx;
+		line-height: 24rpx;
+		color: #555;
+		box-shadow: 0 15rpx 20rpx -15rpx #efefef;
+		position: relative;
+		z-index: 2;
+	}
+
+	.tui-date-content {
+		width: 100%;
+		display: flex;
+		flex-wrap: wrap;
+		padding: 12rpx 0;
+		box-sizing: border-box;
+		background-color: #fff;
+		position: relative;
+	}
+
+	.tui-flex-start {
+		align-content: flex-start;
+	}
+
+	.tui-bg-month {
+		position: absolute;
+		font-size: 260rpx;
+		line-height: 260rpx;
+		left: 50%;
+		top: 50%;
+		transform: translate(-50%, -50%);
+		color: #f5f5f7;
+		z-index: 1;
+	}
+
+	.tui-date {
+		width: 14.2857%;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		padding: 12rpx 0;
+		overflow: hidden;
+		position: relative;
+		z-index: 2;
+	}
+
+	.tui-date-pd_0 {
+		padding: 0 !important;
+	}
+
+	.tui-start-date {
+		border-top-left-radius: 8rpx;
+		border-bottom-left-radius: 8rpx;
+	}
+
+	.tui-end-date {
+		border-top-right-radius: 8rpx;
+		border-bottom-right-radius: 8rpx;
+	}
+
+	.tui-date-text {
+		width: 80rpx;
+		height: 80rpx;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		flex-direction: column;
+		font-size: 32rpx;
+		line-height: 32rpx;
+		position: relative;
+		border-radius: 50%;
+	}
+
+	.tui-btn-calendar {
+		padding: 16rpx;
+		box-sizing: border-box;
+		text-align: center;
+		text-decoration: none;
+	}
+
+	.tui-opacity {
+		opacity: 0.5;
+	}
+
+	.tui-bottom-popup {
+		width: 100%;
+		position: fixed;
+		left: 0;
+		right: 0;
+		bottom: 0;
+		z-index: 9999;
+		visibility: hidden;
+		transform: translate3d(0, 100%, 0);
+		transform-origin: center;
+		transition: all 0.3s ease-in-out;
+		min-height: 20rpx;
+	}
+
+	.tui-popup-show {
+		transform: translate3d(0, 0, 0);
+		visibility: visible;
+	}
+
+	.tui-popup-mask {
+		position: fixed;
+		top: 0;
+		left: 0;
+		right: 0;
+		bottom: 0;
+		background: rgba(0, 0, 0, 0.6);
+		z-index: 9996;
+		transition: all 0.3s ease-in-out;
+		opacity: 0;
+		visibility: hidden;
+	}
+
+	.tui-mask-show {
+		opacity: 1;
+		visibility: visible;
+	}
+
+	.tui-calendar-header {
+		width: 100%;
+		height: 80rpx;
+		padding: 0 40rpx;
+		display: flex;
+		justify-content: center;
+		align-items: center;
+		box-sizing: border-box;
+		font-size: 30rpx;
+		background-color: #fff;
+		color: #555;
+		position: relative;
+	}
+
+	.tui-font-close {
+		position: absolute;
+		right: 30rpx;
+		top: 50%;
+		transform: translateY(-50%);
+		color: #999;
+	}
+
+	.tui-btn-calendar {
+		padding: 16rpx;
+		box-sizing: border-box;
+		text-align: center;
+		text-decoration: none;
+	}
+
+	.tui-font-check {
+		color: #fff;
+		font-size: 54rpx;
+		line-height: 54rpx;
+	}
+
+	.tui-custom-desc {
+		width: 100%;
+		font-size: 24rpx;
+		line-height: 24rpx;
+		transform: scale(0.8);
+		transform-origin: center center;
+		text-align: center;
+	}
+
+	.tui-lunar-unshow {
+		position: absolute;
+		left: 0;
+		bottom: 8rpx;
+		z-index: 2;
+	}
+
+	.tui-date-desc {
+		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;
+	}
+
+	.tui-calendar-op {
+		width: 100%;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		flex-direction: column;
+		background-color: #fff;
+		padding: 0 42rpx 30rpx;
+		box-sizing: border-box;
+		font-size: 24rpx;
+		color: #666;
+	}
+
+	.tui-calendar-result {
+		height: 48rpx;
+		transform: scale(0.9);
+		transform-origin: center 100%;
+	}
+
+	.tui-calendar-btn_box {
+		width: 100%;
+	}
+</style>

+ 211 - 0
components/thorui/tui-card/tui-card.vue

@@ -0,0 +1,211 @@
+<template>
+	<view class="tui-card-class tui-card" :class="[full?'tui-card-full':'',border?'tui-card-border':'']" @tap="handleClick"
+	 @longtap="longTap">
+		<view class="tui-card-header" :class="{'tui-header-line':header.line}" :style="{background:header.bgcolor || '#fff'}">
+			<view class="tui-header-left">
+				<image :src="image.url" class="tui-header-thumb" :class="{'tui-thumb-circle':image.circle}" mode="widthFix" v-if="image.url"
+				 :style="{height:(image.height || 60)+'rpx',width:(image.width || 60)+'rpx'}"></image>
+				<text class="tui-header-title" :style="{fontSize:(title.size || 30)+'rpx',color:(title.color || '#7A7A7A')}" v-if="title.text">{{title.text}}</text>
+			</view>
+			<view class="tui-header-right" :style="{fontSize:(tag.size || 24)+'rpx',color:(tag.color || '#b2b2b2')}" v-if="tag.text">
+				{{tag.text}}
+			</view>
+		</view>
+		<view class="tui-card-body">
+			<slot name="body"></slot>
+		</view>
+		<view class="tui-card-footer">
+			<slot name="footer"></slot>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		name: "tuiCard",
+		props: {
+			//是否铺满
+			full: {
+				type: Boolean,
+				default: false
+			},
+			image: {
+				type: Object,
+				default: function() {
+					return {
+						url: "", //图片地址
+						height: 60, //图片高度
+						width: 60, //图片宽度
+						circle: false
+					}
+				}
+			},
+			//标题
+			title: {
+				type: Object,
+				default: function() {
+					return {
+						text: "", //标题文字
+						size: 30, //字体大小
+						color: "#7A7A7A" //字体颜色
+					}
+				}
+			},
+			//标签,时间等
+			tag: {
+				type: Object,
+				default: function() {
+					return {
+						text: "", //标签文字
+						size: 24, //字体大小
+						color: "#b2b2b2" //字体颜色
+					}
+				}
+			},
+			header: {
+				type: Object,
+				default: function() {
+					return {
+						bgcolor: "#fff", //背景颜色
+						line: false //是否去掉底部线条
+					}
+				}
+			},
+			//是否设置外边框
+			border: {
+				type: Boolean,
+				default: false
+			},
+			index: {
+				type: Number,
+				default: 0
+			}
+		},
+		methods: {
+			handleClick() {
+				this.$emit('click', {
+					index: this.index
+				});
+			},
+			longTap() {
+				this.$emit('longclick', {
+					index: this.index
+				});
+			}
+		}
+	}
+</script>
+
+<style scoped>
+	.tui-card {
+		margin: 0 30rpx;
+		font-size: 28rpx;
+		background-color: #fff;
+		border-radius: 10rpx;
+		box-shadow: 0 0 10rpx #eee;
+		box-sizing: border-box;
+		overflow: hidden;
+	}
+
+	.tui-card-full {
+		margin: 0 !important;
+		border-radius: 0 !important;
+	}
+
+	.tui-card-full::after {
+		border-radius: 0 !important;
+	}
+
+	.tui-card-border {
+		position: relative;
+		box-shadow: none !important
+	}
+
+	.tui-card-border::after {
+		content: ' ';
+		position: absolute;
+		height: 200%;
+		width: 200%;
+		border: 1px solid #ddd;
+		transform-origin: 0 0;
+		-webkit-transform-origin: 0 0;
+		-webkit-transform: scale(0.5);
+		transform: scale(0.5);
+		left: 0;
+		top: 0;
+		border-radius: 20rpx;
+		box-sizing: border-box;
+		pointer-events: none;
+	}
+
+	.tui-card-header {
+		width: 100%;
+		padding: 20rpx;
+		display: flex;
+		align-items: center;
+		justify-content: space-between;
+		position: relative;
+		box-sizing: border-box;
+		overflow: hidden;
+		border-top-left-radius: 10rpx;
+		border-top-right-radius: 10rpx;
+	}
+
+	.tui-card-header::after {
+		content: '';
+		position: absolute;
+		border-bottom: 1rpx solid #eaeef1;
+		-webkit-transform: scaleY(0.5);
+		transform: scaleY(0.5);
+		bottom: 0;
+		right: 0;
+		left: 0;
+		pointer-events: none;
+	}
+
+	.tui-header-line::after {
+		border-bottom: 0 !important;
+	}
+
+	.tui-header-thumb {
+		height: 60rpx;
+		width: 60rpx;
+		vertical-align: middle;
+		margin-right: 20rpx;
+		border-radius: 6rpx;
+	}
+
+	.tui-thumb-circle {
+		border-radius: 50% !important;
+	}
+
+	.tui-header-title {
+		display: inline-block;
+		font-size: 30rpx;
+		color: #7a7a7a;
+		vertical-align: middle;
+		max-width: 460rpx;
+		overflow: hidden;
+		white-space: nowrap;
+		text-overflow: ellipsis;
+	}
+
+	.tui-header-right {
+		font-size: 24rpx;
+		color: #b2b2b2;
+	}
+
+	.tui-card-body {
+		font-size: 32rpx;
+		color: #262b3a;
+		box-sizing: border-box;
+	}
+
+	.tui-card-footer {
+		font-size: 28rpx;
+		color: #596d96;
+		border-bottom-left-radius: 10rpx;
+		border-bottom-right-radius: 10rpx;
+		box-sizing: border-box;
+	}
+</style>

+ 515 - 0
components/thorui/tui-cascade-selection/tui-cascade-selection.vue

@@ -0,0 +1,515 @@
+<template>
+	<view class="tui-cascade-selection">
+		<scroll-view
+			scroll-x
+			scroll-with-animation
+			:scroll-into-view="scrollViewId"
+			:style="{ backgroundColor: headerBgColor }"
+			class="tui-bottom-line"
+			:class="{ 'tui-btm-none': !headerLine }"
+		>
+			<view class="tui-selection-header" :style="{ height: tabsHeight, backgroundColor: backgroundColor }">
+				<view
+					class="tui-header-item"
+					:class="{ 'tui-font-bold': index === currentTab && bold }"
+					:style="{ color: index === currentTab ? activeColor : color, fontSize: size + 'rpx' }"
+					:id="`id_${index}`"
+					@tap.stop="swichNav"
+					:data-current="index"
+					v-for="(item, index) in selectedArr"
+					:key="index"
+				>
+					{{ item.text }}
+					<view class="tui-active-line" :style="{ backgroundColor: lineColor }" v-if="index === currentTab && showLine"></view>
+				</view>
+			</view>
+		</scroll-view>
+		<swiper class="tui-selection-list" :current="currentTab" duration="300" @change="switchTab" :style="{ height: height, backgroundColor: backgroundColor }">
+			<swiper-item v-for="(item, index) in selectedArr" :key="index">
+				<scroll-view scroll-y :scroll-into-view="item.scrollViewId" class="tui-selection-item" :style="{ height: height }">
+					<view class="tui-first-item" :style="{ height: firstItemTop }"></view>
+					<view
+						class="tui-selection-cell"
+						:style="{ padding: padding, backgroundColor: backgroundColor }"
+						:id="`id_${subIndex}`"
+						v-for="(subItem, subIndex) in item.list"
+						:key="subIndex"
+						@tap="change(index, subIndex, subItem)"
+					>
+						<icon type="success_no_circle" v-if="item.index === subIndex" :color="checkMarkColor" :size="checkMarkSize" class="tui-icon-success"></icon>
+						<image :src="subItem.src" v-if="subItem.src" class="tui-cell-img" :style="{ width: imgWidth, height: imgHeight, borderRadius: radius }"></image>
+						<view
+							class="tui-cell-title"
+							:class="{ 'tui-font-bold': item.index === subIndex && textBold, 'tui-flex-shrink': nowrap }"
+							:style="{ color: item.index === subIndex ? textActiveColor : textColor, fontSize: textSize + 'rpx' }"
+						>
+							{{ subItem.text }}
+						</view>
+						<view class="tui-cell-sub_title" :style="{ color: subTextColor, fontSize: subTextSize + 'rpx' }" v-if="subItem.subText">{{ subItem.subText }}</view>
+					</view>
+				</scroll-view>
+			</swiper-item>
+		</swiper>
+	</view>
+</template>
+
+<script>
+export default {
+	name: 'tuiCascadeSelection',
+	props: {
+		/**
+			 * 如果下一级是请求返回,则为第一级数据,否则所有数据
+			 * 数据格式
+			  [{
+				  src: "",
+				  text: "",
+				  subText: "",
+				  value: 0,
+				  children:[{
+					  text: "",
+					  subText: "",
+					  value: 0,
+					  children:[]
+			   }]
+			  }]
+			 * */
+		itemList: {
+			type: Array,
+			default: () => {
+				return [];
+			}
+		},
+		/*
+		   初始化默认选中数据
+		   [{
+			text: "",//选中text
+			subText: '',//选中subText
+			value: '',//选中value
+			src: '', //选中src,没有则传空或不传
+			index: 0, //选中数据在当前layer索引
+			list: [{src: "", text: "", subText: "", value: 101}] //所有layer数据集合
+		  }];
+		    
+		   */
+		defaultItemList: {
+			type: Array,
+			value: []
+		},
+		//是否显示header底部细线
+		headerLine: {
+			type: Boolean,
+			default: true
+		},
+		//header背景颜色
+		headerBgColor: {
+			type: String,
+			default: '#FFFFFF'
+		},
+		//顶部标签栏高度
+		tabsHeight: {
+			type: String,
+			default: '88rpx'
+		},
+		//默认显示文字
+		text: {
+			type: String,
+			default: '请选择'
+		},
+		//tabs 文字大小
+		size: {
+			type: Number,
+			default: 28
+		},
+		//tabs 文字颜色
+		color: {
+			type: String,
+			default: '#555'
+		},
+		//选中颜色
+		activeColor: {
+			type: String,
+			default: '#5677fc'
+		},
+		//选中后文字加粗
+		bold: {
+			type: Boolean,
+			default: true
+		},
+		//选中后是否显示底部线条
+		showLine: {
+			type: Boolean,
+			default: true
+		},
+		//线条颜色
+		lineColor: {
+			type: String,
+			default: '#5677fc'
+		},
+		//icon 大小
+		checkMarkSize: {
+			type: Number,
+			default: 15
+		},
+		//icon 颜色
+		checkMarkColor: {
+			type: String,
+			default: '#5677fc'
+		},
+		//item 图片宽度
+		imgWidth: {
+			type: String,
+			default: '40rpx'
+		},
+		//item 图片高度
+		imgHeight: {
+			type: String,
+			default: '40rpx'
+		},
+		//图片圆角
+		radius: {
+			type: String,
+			default: '50%'
+		},
+		//item text颜色
+		textColor: {
+			type: String,
+			default: '#333'
+		},
+		textActiveColor: {
+			type: String,
+			default: '#333'
+		},
+		//选中后字体是否加粗
+		textBold: {
+			type: Boolean,
+			default: true
+		},
+		//item text字体大小
+		textSize: {
+			type: Number,
+			default: 28
+		},
+		//text 是否不换行
+		nowrap: {
+			type: Boolean,
+			default: false
+		},
+		//item subText颜色
+		subTextColor: {
+			type: String,
+			default: '#999'
+		},
+		//item subText字体大小
+		subTextSize: {
+			type: Number,
+			default: 24
+		},
+		// item padding
+		padding: {
+			type: String,
+			default: '20rpx 30rpx'
+		},
+		//占位高度,第一条数据距离顶部距离
+		firstItemTop: {
+			type: String,
+			default: '20rpx'
+		},
+		//swiper 高度
+		height: {
+			type: String,
+			default: '300px'
+		},
+		//item  swiper 内容部分背景颜色
+		backgroundColor: {
+			type: String,
+			default: '#FFFFFF'
+		},
+		//子集数据是否请求返回(默认false,一次性返回所有数据)
+		request: {
+			type: Boolean,
+			default: false
+		},
+		//子级数据(当有改变时,默认当前选中项新增子级数据,request=true时生效)
+		receiveData: {
+			type: Array,
+			default: () => {
+				return [];
+			}
+		},
+		//改变值则重置数据
+		reset: {
+			type: [Number, String],
+			default: 0
+		}
+	},
+	watch: {
+		itemList(val) {
+			this.initData(val, -1);
+		},
+		receiveData(val) {
+			this.subLevelData(val, this.currentTab);
+		},
+		reset() {
+			this.initData(this.itemList, -1);
+		}
+	},
+	created() {
+		let defaultItemList = this.defaultItemList || [];
+		if (defaultItemList.length > 0) {
+			defaultItemList.map(item => {
+				item.scrollViewId = `id_${item.index}`;
+			});
+			this.selectedArr = defaultItemList;
+			this.currentTab = defaultItemList.length - 1;
+			this.$nextTick(() => {
+				this.checkCor();
+			});
+		} else {
+			this.initData(this.itemList, -1);
+		}
+	},
+	data() {
+		return {
+			currentTab: 0,
+			//tab栏scrollview滚动的位置
+			scrollViewId: 'id__1',
+			selectedArr: []
+		};
+	},
+	methods: {
+		initData(data, layer) {
+			if (!data || data.length === 0) return;
+			if (this.request) {
+				//第一级数据
+				this.subLevelData(data, layer);
+			} else {
+				let selectedValue = this.selectedValue || {};
+				if (selectedValue.type) {
+					this.setDefaultData(selectedValue);
+				} else {
+					this.subLevelData(this.getItemList(layer, -1), layer);
+				}
+			}
+		},
+		removeChildren(data) {
+			let list = data.map(item => {
+				delete item['children'];
+				return item;
+			});
+			return list;
+		},
+		getItemList(layer, index) {
+			let list = [];
+			let arr = JSON.parse(JSON.stringify(this.itemList));
+			if (layer == -1) {
+				list = this.removeChildren(arr);
+			} else {
+				let value = this.selectedArr[0].index;
+				value = value == -1 ? index : value;
+				list = arr[value].children || [];
+				if (layer > 0) {
+					for (let i = 1; i < layer + 1; i++) {
+						let val = layer === i ? index : this.selectedArr[i].index;
+						list = list[val].children || [];
+						if (list.length === 0) break;
+					}
+				}
+				list = this.removeChildren(list);
+			}
+			return list;
+		},
+		//滚动切换
+		switchTab: function(e) {
+			this.currentTab = e.detail.current;
+			this.checkCor();
+		},
+		//点击标题切换当
+		swichNav: function(e) {
+			let cur = e.currentTarget.dataset.current;
+			if (this.currentTab != cur) {
+				this.currentTab = cur;
+			}
+		},
+		checkCor: function() {
+			let item = this.selectedArr[this.currentTab];
+			item.scrollViewId = 'id__1';
+			this.$nextTick(() => {
+				setTimeout(() => {
+					let val = item.index < 2 ? 0 : Number(item.index - 2);
+					item.scrollViewId = `id_${val}`;
+				}, 2);
+			});
+			
+			if (this.currentTab > 1) {
+				this.scrollViewId = `id_${this.currentTab - 1}`;
+			} else {
+				this.scrollViewId = `id_0`;
+			}
+		},
+		change(index, subIndex, subItem) {
+			let item = this.selectedArr[index];
+			if (item.index == subIndex) return;
+			item.index = subIndex;
+			item.text = subItem.text;
+			item.value = subItem.value;
+			item.subText = subItem.subText || '';
+			item.src = subItem.src || '';
+
+			this.$emit('change', {
+				layer: index,
+				subIndex: subIndex, //layer=> Array index
+				...subItem
+			});
+
+			if (!this.request) {
+				let data = this.getItemList(index, subIndex);
+				this.subLevelData(data, index);
+			}
+		},
+		//新增子级数据时处理
+		subLevelData(data, layer) {
+			if (!data || data.length === 0) {
+				if (layer == -1) return;
+				//完成选择
+				let result = JSON.parse(JSON.stringify(this.selectedArr));
+				let lastItem = result[result.length - 1] || {};
+				let text = '';
+				result.map(item => {
+					text += item.text;
+					delete item['list'];
+					//delete item['index'];
+					delete item['scrollViewId'];
+					return item;
+				});
+				this.$emit('complete', {
+					result: result,
+					value: lastItem.value,
+					text: text,
+					subText: lastItem.subText,
+					src: lastItem.src
+				});
+			} else {
+				//重置数据( >layer层级)
+				let item = [
+					{
+						text: this.text,
+						subText: '',
+						value: '',
+						src: '',
+						index: -1,
+						scrollViewId: 'id__1',
+						list: data
+					}
+				];
+				if (layer == -1) {
+					this.selectedArr = item;
+				} else {
+					let retainArr = this.selectedArr.slice(0, layer + 1);
+					this.selectedArr = retainArr.concat(item);
+				}
+				this.$nextTick(() => {
+					this.currentTab = this.selectedArr.length - 1;
+				});
+			}
+		}
+	}
+};
+</script>
+
+<style scoped>
+.tui-cascade-selection {
+	width: 100%;
+	box-sizing: border-box;
+}
+
+.tui-selection-header {
+	width: 100%;
+	display: flex;
+	align-items: center;
+	position: relative;
+	box-sizing: border-box;
+}
+
+.tui-bottom-line {
+	position: relative;
+}
+
+.tui-bottom-line::after {
+	width: 100%;
+	content: '';
+	position: absolute;
+	border-bottom: 1rpx solid #eaeef1;
+	-webkit-transform: scaleY(0.5) translateZ(0);
+	transform: scaleY(0.5) translateZ(0);
+	transform-origin: 0 100%;
+	bottom: 0;
+	right: 0;
+	left: 0;
+}
+
+.tui-btm-none::after {
+	border-bottom: 0 !important;
+}
+
+.tui-header-item {
+	max-width: 240rpx;
+	padding: 15rpx 30rpx;
+	box-sizing: border-box;
+	flex-shrink: 0;
+	overflow: hidden;
+	white-space: nowrap;
+	text-overflow: ellipsis;
+	position: relative;
+}
+
+.tui-font-bold {
+	font-weight: bold;
+}
+
+.tui-active-line {
+	width: 60rpx;
+	height: 6rpx;
+	border-radius: 4rpx;
+	position: absolute;
+	bottom: 0;
+	right: 0;
+	left: 50%;
+	transform: translateX(-50%);
+}
+
+.tui-selection-cell {
+	width: 100%;
+	box-sizing: border-box;
+	display: flex;
+	align-items: center;
+}
+
+.tui-icon-success {
+	margin-right: 12rpx;
+}
+
+.tui-cell-img {
+	margin-right: 12rpx;
+	flex-shrink: 0;
+}
+
+.tui-cell-title {
+	word-break: break-all;
+}
+
+.tui-flex-shrink {
+	flex-shrink: 0;
+}
+
+.tui-font-bold {
+	font-weight: bold;
+}
+
+.tui-cell-sub_title {
+	margin-left: 20rpx;
+	word-break: break-all;
+}
+.tui-first-item {
+	width: 100%;
+}
+</style>

+ 251 - 0
components/thorui/tui-circular-progress/tui-circular-progress.vue

@@ -0,0 +1,251 @@
+<template>
+	<view class="tui-circular-container" :style="{ width: diam + 'px', height: (height || diam) + 'px' }">
+		<canvas class="tui-circular-default" :canvas-id="defaultCanvasId" :id="defaultCanvasId" :style="{ width: diam + 'px', height: (height || diam) + 'px' }"
+		 v-if="defaultShow"></canvas>
+		<canvas class="tui-circular-progress" :canvas-id="progressCanvasId" :id="progressCanvasId" :style="{ width: diam + 'px', height: (height || diam) + 'px' }"></canvas>
+		<slot />
+	</view>
+</template>
+
+<script>
+	export default {
+		name: 'tuiCircularProgress',
+		props: {
+			/*
+			  传值需使用rpx进行转换保证各终端兼容
+			  px = rpx / 750 * wx.getSystemInfoSync().windowWidth
+			  圆形进度条(画布)宽度,直径 [px]
+			*/
+			diam: {
+				type: Number,
+				default: 60
+			},
+			//圆形进度条(画布)高度,默认取diam值[当画半弧时传值,height有值时则取height]
+			height: {
+				type: Number,
+				default: 0
+			},
+			//进度条线条宽度[px]
+			lineWidth: {
+				type: Number,
+				default: 4
+			},
+			/*
+			 线条的端点样式
+			 butt:向线条的每个末端添加平直的边缘
+			 round	向线条的每个末端添加圆形线帽
+			 square	向线条的每个末端添加正方形线帽
+			*/
+			lineCap: {
+				type: String,
+				default: 'round'
+			},
+			//圆环进度字体大小 [px]
+			fontSize: {
+				type: Number,
+				default: 12
+			},
+			//圆环进度字体颜色
+			fontColor: {
+				type: String,
+				default: '#5677fc'
+			},
+			//是否显示进度文字
+			fontShow: {
+				type: Boolean,
+				default: true
+			},
+			/*
+			 自定义显示文字[默认为空,显示百分比,fontShow=true时生效]
+			 可以使用 slot自定义显示内容
+			*/
+			percentText: {
+				type: String,
+				default: ''
+			},
+			//是否显示默认(背景)进度条
+			defaultShow: {
+				type: Boolean,
+				default: true
+			},
+			//默认进度条颜色
+			defaultColor: {
+				type: String,
+				default: '#CCC'
+			},
+			//进度条颜色
+			progressColor: {
+				type: String,
+				default: '#5677fc'
+			},
+			//进度条渐变颜色[结合progressColor使用,默认为空]
+			gradualColor: {
+				type: String,
+				default: ''
+			},
+			//起始弧度,单位弧度
+			sAngle: {
+				type: Number,
+				default: -Math.PI / 2
+			},
+			//指定弧度的方向是逆时针还是顺时针。默认是false,即顺时针
+			counterclockwise: {
+				type: Boolean,
+				default: false
+			},
+			//进度百分比 [10% 传值 10]
+			percentage: {
+				type: Number,
+				default: 0
+			},
+			//进度百分比缩放倍数[使用半弧为100%时,则可传2]
+			multiple: {
+				type: Number,
+				default: 1
+			},
+			//动画执行时间[单位毫秒,低于50无动画]
+			duration: {
+				type: Number,
+				default: 800
+			},
+			//backwards: 动画从头播;forwards:动画从上次结束点接着播
+			activeMode: {
+				type: String,
+				default: 'backwards'
+			}
+		},
+		watch: {
+			percentage(val) {
+				this.initDraw()
+			}
+		},
+		data() {
+			return {
+				// #ifdef MP-WEIXIN
+				progressCanvasId:"progressCanvasId",
+				defaultCanvasId: "defaultCanvasId",
+				// #endif
+				// #ifndef MP-WEIXIN
+				progressCanvasId:this.getCanvasId(),
+				defaultCanvasId: this.getCanvasId(),
+				// #endif
+				progressContext: null,
+				linearGradient: null,
+				//起始百分比
+				startPercentage: 0
+				// dpi
+				//pixelRatio: uni.getSystemInfoSync().pixelRatio
+			};
+		},
+		mounted() {
+			this.initDraw(true)
+		},
+		methods: {
+			//初始化绘制
+			initDraw(init) {
+				let start = this.activeMode === 'backwards' ? 0 : this.startPercentage;
+				if (this.defaultShow && init) {
+					this.drawDefaultCircular();
+				}
+				this.drawProgressCircular(start);
+			},
+			//默认(背景)圆环
+			drawDefaultCircular() {
+				let ctx = uni.createCanvasContext(this.defaultCanvasId, this);
+				ctx.setLineWidth(this.lineWidth);
+				ctx.setStrokeStyle(this.defaultColor);
+				//终止弧度
+				let eAngle = Math.PI * (this.height ? 1 : 2) + this.sAngle;
+				this.drawArc(ctx, eAngle);
+			},
+			//进度圆环
+			drawProgressCircular(startPercentage) {
+				let ctx = this.progressContext;
+				let gradient = this.linearGradient;
+				if (!ctx) {
+					ctx = uni.createCanvasContext(this.progressCanvasId, this);
+					//创建一个线性的渐变颜色 CanvasGradient对象
+					gradient = ctx.createLinearGradient(0, 0, this.diam, 0);
+					gradient.addColorStop('0', this.progressColor);
+					if (this.gradualColor) {
+						gradient.addColorStop('1', this.gradualColor);
+					}
+					this.progressContext = ctx;
+					this.linearGradient = gradient;
+				}
+				ctx.setLineWidth(this.lineWidth);
+				ctx.setStrokeStyle(gradient);
+				let time = this.duration / this.percentage;
+				if (this.percentage > 0 || !this.fontShow) {
+					startPercentage = this.duration < 50 ? this.percentage - 1 : startPercentage;
+					startPercentage++;
+					if (startPercentage > this.percentage) {
+						this.$emit('end', {
+							canvasId: this.progressCanvasId
+						});
+						return;
+					}
+				}
+				if (this.fontShow) {
+					ctx.setFontSize(this.fontSize);
+					ctx.setFillStyle(this.fontColor);
+					ctx.setTextAlign('center');
+					ctx.setTextBaseline('middle');
+					let percentage = this.percentText;
+					if (!percentage) {
+						percentage = this.counterclockwise ? 100 - startPercentage * this.multiple : startPercentage * this.multiple;
+						percentage = `${percentage}%`;
+					}
+					let radius = this.diam / 2;
+					ctx.fillText(percentage, radius, radius);
+					if (this.percentage === 0 || (this.counterclockwise && startPercentage === 100)) {
+						ctx.draw();
+						return;
+					}
+				}
+				let eAngle = ((2 * Math.PI) / 100) * startPercentage + this.sAngle;
+				this.drawArc(ctx, eAngle);
+				setTimeout(() => {
+					this.startPercentage = startPercentage;
+					this.drawProgressCircular(startPercentage);
+					this.$emit('change', {
+						percentage: startPercentage
+					});
+				}, time);
+				// #ifdef H5
+				// requestAnimationFrame(()=>{})
+				// #endif
+			},
+			//创建弧线
+			drawArc(ctx, eAngle) {
+				ctx.setLineCap(this.lineCap);
+				ctx.beginPath();
+				let radius = this.diam / 2; //x=y
+				ctx.arc(radius, radius, radius - this.lineWidth, this.sAngle, eAngle, this.counterclockwise);
+				ctx.stroke();
+				ctx.draw();
+			},
+			//生成canvasId
+			getCanvasId() {
+				let uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
+					return (c === 'x' ? (Math.random() * 16) | 0 : 'r&0x3' | '0x8').toString(16);
+				});
+				return uuid;
+			}
+		}
+	};
+</script>
+
+<style scoped>
+	.tui-circular-container,
+	.tui-circular-default {
+		position: relative;
+	}
+
+	.tui-circular-progress {
+		position: absolute;
+		left: 0;
+		top: 0;
+		z-index: 10;
+	}
+</style>

+ 166 - 0
components/thorui/tui-collapse/tui-collapse.vue

@@ -0,0 +1,166 @@
+<template>
+	<view class="tui-collapse" :style="{backgroundColor:bgColor}">
+		<view class="tui-collapse-head" :style="{backgroundColor:hdBgColor}" @tap.stop="handleClick">
+			<view class="tui-header" :class="{'tui-opacity':disabled}">
+				<slot name="title"></slot>
+				<view class="tui-collapse-icon tui-icon-arrow" :class="{'tui-icon-active':isOpen}" :style="{color:arrowColor}" v-if="arrow"></view>
+			</view>
+		</view>
+		<view class="tui-collapse-body_box" :style="{backgroundColor:bdBgColor,height:isOpen?height:'0rpx'}">
+			<view class="tui-collapse-body" :class="{'tui-collapse-transform':height=='auto','tui-collapse-body_show':isOpen && height=='auto'}">
+				<slot name="content"></slot>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		name: "tuiCollapse",
+		props: {
+			//collapse背景颜色
+			bgColor: {
+				type: String,
+				default: 'transparent'
+			},
+			//collapse-head 背景颜色
+			hdBgColor: {
+				type: String,
+				default: '#fff'
+			},
+			//collapse-body 背景颜色
+			bdBgColor: {
+				type: String,
+				default: 'transparent'
+			},
+			//collapse-body实际高度 open时使用
+			height: {
+				type: String,
+				default: 'auto'
+			},
+			//索引
+			index: {
+				type: Number,
+				default: 0
+			},
+			//当前索引,index==current时展开
+			current: {
+				type: Number,
+				default: -1
+			},
+			// 是否禁用
+			disabled: {
+				type: [Boolean, String],
+				default: false
+			},
+			//是否带箭头
+			arrow: {
+				type: [Boolean, String],
+				default: true
+			},
+			//箭头颜色
+			arrowColor: {
+				type: String,
+				default: "#333"
+			}
+		},
+		watch: {
+			current() {
+				this.updateCurrentChange()
+			}
+		},
+		created() {
+			this.updateCurrentChange()
+		},
+		data() {
+			return {
+				isOpen: false
+			};
+		},
+		methods: {
+			updateCurrentChange() {
+				this.isOpen = this.index == this.current
+			},
+			handleClick() {
+				if (this.disabled) return;
+				this.$emit("click", {
+					index: Number(this.index)
+				})
+			}
+		}
+	}
+</script>
+
+<style scoped>
+	@font-face {
+		font-family: 'tuiCollapse';
+		src: url(data:application/font-woff;charset=utf-8;base64,d09GRgABAAAAAAQ4AA0AAAAABlgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABGRlRNAAAEHAAAABoAAAAciRx3B0dERUYAAAP8AAAAHgAAAB4AKQAKT1MvMgAAAaAAAABCAAAAVjxuR/JjbWFwAAAB9AAAAD4AAAFCAA/pq2dhc3AAAAP0AAAACAAAAAj//wADZ2x5ZgAAAkAAAABEAAAARCs1U/toZWFkAAABMAAAADAAAAA2FpaT+mhoZWEAAAFgAAAAHQAAACQHngOFaG10eAAAAeQAAAAPAAAAEAwAAEBsb2NhAAACNAAAAAoAAAAKACIAAG1heHAAAAGAAAAAHwAAACABDwAdbmFtZQAAAoQAAAFJAAACiCnmEVVwb3N0AAAD0AAAACMAAAA1DunpUnjaY2BkYGAAYja/oO54fpuvDNwsDCBwc4/6fzjtwNDNfICpBMjlYGACiQIAGVAKZnjaY2BkYGBu+N/AEMPCAALMBxgYGVABCwBVNgMsAAAAeNpjYGRgYGBhEGQA0QwMTEDMBYQMDP/BfAYACnYBLQB42mNgZGFgnMDAysDA1Ml0hoGBoR9CM75mMGLkAIoysDIzYAUBaa4pDA7PGJ4xMDf8b2CIYW5gaAAKM4LkANq9C9sAAHjaY2GAABYIdgAAAMAATQB42mNgYGBmgGAZBkYGELAB8hjBfBYGBSDNAoRA/jOG//8hpBQzVCUDIxsDjMnAyAQkmBhQASPDsAcAMCAGoQAAAAAAAAAAAAAAIgAAAAEAQACLA8ACdAAQAAAlASYiBhQXARYyNwE2NCYiBwIA/oYNIBkMAZcNIA0BlwwZIA3uAXoMGSAN/mkMDAGXDSAZDAB42n2QPU4DMRCFn/MHJBJCIKhdUQDa/JQpEyn0CKWjSDbekGjXXnmdSDkBLRUHoOUYHIAbINFyCl6WSZMia+3o85uZ57EBnOMbCv/fJe6EFY7xKFzBETLhKvUX4Rr5XbiOFj6FG9R/hJu4VQPhFi7UGx1U7YS7m9JtywpnGAhXcIon4Sr1lXCN/CpcxxU+hBvUv4SbGONXuIVrZakM4WEwQWCcQWOKDeMCMRwskjIG1qE59GYSzExPN3oRO5s4GyjvV2KXAx5oOeeAKe09t2a+Sif+YMuB1JhuHgVLtimNLiJ0KBtfLJzV3ahzsP2e7ba02L9rgTXH7FENbNT8Pdsz0khsDK+QkjXyMrekElOPaGus8btnKdbzXgiJTrzL9IjHmjR1OvduaeLA4ufyjBx9tLmSPfeoHD5jWQh5v91OxCCKXYY/k9hxGQAAAHjaY2BigAAuMMnIgA5YwKJMjExciUVF+eW6KfnleQAZ0wQyAAAAAAH//wACAAEAAAAMAAAAFgAAAAIAAQADAAMAAQAEAAAAAgAAAAB42mNgYGBkAIKrS9Q5QPTNPer/YTQAQ+0HIAAA) format('woff');
+		font-weight: normal;
+		font-style: normal;
+	}
+
+	.tui-collapse-icon {
+		font-family: "tuiCollapse" !important;
+		font-style: normal;
+		-webkit-font-smoothing: antialiased;
+		-moz-osx-font-smoothing: grayscale;
+	}
+
+	.tui-icon-arrow:before {
+		content: "\e600";
+	}
+
+	.tui-icon-arrow {
+		font-size: 32rpx;
+		transform: rotate(0);
+		transform-origin: center center;
+		transition: all 0.3s;
+		position: absolute;
+		top: 50%;
+		margin-top: -8px;
+		right: 30rpx;
+	}
+
+	.tui-arrow-padding {
+		padding-right: 62rpx;
+		box-sizing: border-box;
+	}
+
+	.tui-icon-active {
+		transform: rotate(180deg);
+		transform-origin: center center;
+	}
+
+	.tui-header {
+		position: relative;
+		z-index: 2;
+	}
+   .tui-collapse-body_box{
+	   transition: all 0.25s;
+	   overflow: hidden;
+   }
+	.tui-collapse-body {
+		transition: all 0.25s;
+		overflow: hidden;
+		position: relative;
+		z-index: 1;
+	}
+
+	.tui-collapse-transform {
+		opacity: 0;
+		visibility: hidden;
+		-webkit-transform: translateY(-40%);
+		transform: translateY(-40%);
+	}
+
+	.tui-collapse-body_show {
+		opacity: 1;
+		visibility: visible;
+		-webkit-transform: translateY(0);
+		transform: translateY(0);
+	}
+
+	.tui-opacity {
+		opacity: 0.6;
+	}
+</style>

+ 257 - 0
components/thorui/tui-countdown/tui-countdown.vue

@@ -0,0 +1,257 @@
+<template>
+	<view class="tui-countdown-box">
+		<view class="tui-countdown-item" :style="{ background: backgroundColor, borderColor: borderColor, width: getWidth(d, width) + 'rpx', height: height + 'rpx' }" v-if="days">
+			<view class="tui-countdown-time" :class="[scale ? 'tui-countdown-scale' : '']" :style="{ fontSize: size + 'rpx', color: color, lineHeight: size + 'rpx' }">
+				{{ d }}
+			</view>
+		</view>
+		<view
+			class="tui-countdown-colon"
+			:class="{ 'tui-colon-pad': borderColor == 'transparent' }"
+			:style="{ lineHeight: colonSize + 'rpx', fontSize: colonSize + 'rpx', color: colonColor }"
+			v-if="days"
+		>
+			{{ isColon ? ':' : '天' }}
+		</view>
+		<view class="tui-countdown-item" :style="{ background: backgroundColor, borderColor: borderColor, width: getWidth(h, width) + 'rpx', height: height + 'rpx' }" v-if="hours">
+			<view class="tui-countdown-time" :class="[scale ? 'tui-countdown-scale' : '']" :style="{ fontSize: size + 'rpx', color: color, lineHeight: size + 'rpx' }">
+				{{ h }}
+			</view>
+		</view>
+		<view
+			class="tui-countdown-colon"
+			:class="{ 'tui-colon-pad': borderColor == 'transparent' }"
+			:style="{ lineHeight: colonSize + 'rpx', fontSize: colonSize + 'rpx', color: colonColor }"
+			v-if="hours"
+		>
+			{{ isColon ? ':' : '时' }}
+		</view>
+		<view
+			class="tui-countdown-item"
+			:style="{ background: backgroundColor, borderColor: borderColor, width: getWidth(i, width) + 'rpx', height: height + 'rpx' }"
+			v-if="minutes"
+		>
+			<view class="tui-countdown-time" :class="[scale ? 'tui-countdown-scale' : '']" :style="{ fontSize: size + 'rpx', color: color, lineHeight: size + 'rpx' }">
+				{{ i }}
+			</view>
+		</view>
+		<view
+			class="tui-countdown-colon"
+			:class="{ 'tui-colon-pad': borderColor == 'transparent' }"
+			:style="{ lineHeight: colonSize + 'rpx', fontSize: colonSize + 'rpx', color: colonColor }"
+			v-if="minutes"
+		>
+			{{ isColon ? ':' : '分' }}
+		</view>
+		<view
+			class="tui-countdown-item"
+			:style="{ background: backgroundColor, borderColor: borderColor, width: getWidth(s, width) + 'rpx', height: height + 'rpx' }"
+			v-if="seconds"
+		>
+			<view class="tui-countdown-time" :class="[scale ? 'tui-countdown-scale' : '']" :style="{ fontSize: size + 'rpx', color: color, lineHeight: size + 'rpx' }">
+				{{ s }}
+			</view>
+		</view>
+		<view
+			class="tui-countdown-colon"
+			:class="{ 'tui-colon-pad': borderColor == 'transparent' }"
+			:style="{ lineHeight: colonSize + 'rpx', fontSize: colonSize + 'rpx', color: colonColor }"
+			v-if="seconds && !isColon"
+		>
+			{{ unitEn ? 's' : '秒' }}
+		</view>
+	</view>
+</template>
+
+<script>
+export default {
+	name: 'tuiCountdown',
+	props: {
+		//数字框宽度
+		width: {
+			type: Number,
+			default: 25
+		},
+		//数字框高度
+		height: {
+			type: Number,
+			default: 25
+		},
+		//数字框border颜色
+		borderColor: {
+			type: String,
+			default: '#333'
+		},
+		//数字框背景颜色
+		backgroundColor: {
+			type: String,
+			default: '#fff'
+		},
+		//数字框字体大小
+		size: {
+			type: Number,
+			default: 24
+		},
+		//数字框字体颜色
+		color: {
+			type: String,
+			default: '#333'
+		},
+		//是否缩放 0.9
+		scale: {
+			type: Boolean,
+			default: false
+		},
+		//冒号大小
+		colonSize: {
+			type: Number,
+			default: 28
+		},
+		//冒号颜色
+		colonColor: {
+			type: String,
+			default: '#333'
+		},
+		//剩余时间 (单位:秒)
+		time: {
+			type: Number,
+			default: 0
+		},
+		//是否包含天
+		days: {
+			type: Boolean,
+			default: false
+		},
+		//是否包含小时
+		hours: {
+			type: Boolean,
+			default: true
+		},
+		//是否包含分钟
+		minutes: {
+			type: Boolean,
+			default: true
+		},
+		//是否包含秒
+		seconds: {
+			type: Boolean,
+			default: true
+		},
+		//单位用英文缩写表示 仅seconds秒数有效
+		unitEn: {
+			type: Boolean,
+			default: false
+		},
+		//是否展示为冒号,false为文字
+		isColon: {
+			type: Boolean,
+			default: true
+		}
+	},
+	watch: {
+		time(val) {
+			clearInterval(this.countdown);
+			this.doLoop();
+		}
+	},
+	data() {
+		return {
+			countdown: null,
+			d: '0',
+			h: '00',
+			i: '00',
+			s: '00'
+		};
+	},
+	created() {
+		this.doLoop();
+	},
+	beforeDestroy() {
+		clearInterval(this.countdown);
+		this.countdown = null;
+	},
+	methods: {
+		getWidth: function(num, width) {
+			return num > 99 ? (width / 2) * num.toString().length : width;
+		},
+		endOfTime() {
+			clearInterval(this.countdown);
+			this.countdown = null;
+			this.$emit('end', {});
+		},
+		doLoop: function() {
+			let seconds = this.time || 0;
+			this.countDown(seconds);
+			this.countdown = setInterval(() => {
+				seconds--;
+				if (seconds < 0) {
+					this.endOfTime();
+					return;
+				}
+				this.countDown(seconds);
+			}, 1000);
+		},
+		countDown(seconds) {
+			let [day, hour, minute, second] = [0, 0, 0, 0];
+			if (seconds > 0) {
+				day = this.days ? Math.floor(seconds / (60 * 60 * 24)) : 0;
+				hour = this.hours ? Math.floor(seconds / (60 * 60)) - day * 24 : 0;
+				minute = this.minutes ? Math.floor(seconds / 60) - hour * 60 - day * 24 * 60 : 0;
+				second = Math.floor(seconds) - day * 24 * 60 * 60 - hour * 60 * 60 - minute * 60;
+			} else {
+				this.endOfTime();
+			}
+			hour = hour < 10 ? '0' + hour : hour;
+			minute = minute < 10 ? '0' + minute : minute;
+			second = second < 10 ? '0' + second : second;
+			this.d = day;
+			this.h = hour;
+			this.i = minute;
+			this.s = second;
+		}
+	}
+};
+</script>
+
+<style scoped>
+.tui-countdown-box {
+	display: flex;
+	align-items: center;
+}
+
+.tui-countdown-box {
+	display: flex;
+	align-items: center;
+}
+
+.tui-countdown-item {
+	border: 1rpx solid;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	padding: 2rpx;
+	border-radius: 6rpx;
+	white-space: nowrap;
+	transform: translateZ(0);
+}
+
+.tui-countdown-time {
+	margin: 0;
+	padding: 0;
+}
+
+.tui-countdown-colon {
+	display: flex;
+	justify-content: center;
+	padding: 0 5rpx;
+}
+
+.tui-colon-pad {
+	padding: 0 !important;
+}
+
+.tui-countdown-scale {
+	transform: scale(0.9);
+	transform-origin: center center;
+}
+</style>

+ 537 - 0
components/thorui/tui-datetime/tui-datetime.vue

@@ -0,0 +1,537 @@
+<template>
+	<view class="tui-datetime-picker">
+		<view class="tui-mask" :class="{ 'tui-mask-show': isShow }" @touchmove.stop.prevent="stop" catchtouchmove="stop" @tap="hide"></view>
+		<view class="tui-header" :class="{ 'tui-show': isShow }">
+			<view class="tui-picker-header" :class="{ 'tui-date-radius': radius }" @touchmove.stop.prevent="stop" catchtouchmove="stop">
+				<view class="tui-btn-picker" :style="{ color: cancelColor }" hover-class="tui-opacity" :hover-stay-time="150"
+				 @tap="hide">取消</view>
+				<view class="tui-btn-picker" :style="{ color: color }" hover-class="tui-opacity" :hover-stay-time="150" @tap="btnFix">确定</view>
+			</view>
+			<view class="tui-date-header" v-if="unitTop">
+				<view class="tui-date-unit" v-if="type < 4 || type == 7">年</view>
+				<view class="tui-date-unit" v-if="type < 4 || type == 7">月</view>
+				<view class="tui-date-unit" v-if="type == 1 || type == 2 || type == 7">日</view>
+				<view class="tui-date-unit" v-if="type == 1 || type == 4 || type == 5 || type == 7">时</view>
+				<view class="tui-date-unit" v-if="type == 1 || type > 3">分</view>
+				<view class="tui-date-unit" v-if="type > 4">秒</view>
+			</view>
+			<view class="tui-picker-body">
+				<picker-view :value="value" @change="change" class="tui-picker-view">
+					<picker-view-column v-if="!reset && (type < 4 || type == 7)">
+						<view class="tui-column-item" :class="{ 'tui-font-size_32': !unitTop && type == 7 }" v-for="(item, index) in years"
+						 :key="index">
+							{{ item }}
+							<text class="tui-unit-text" v-if="!unitTop">年</text>
+						</view>
+					</picker-view-column>
+					<picker-view-column v-if="!reset && (type < 4 || type == 7)">
+						<view class="tui-column-item" :class="{ 'tui-font-size_32': !unitTop && type == 7 }" v-for="(item, index) in months"
+						 :key="index">
+							{{ formatNum(item) }}
+							<text class="tui-unit-text" v-if="!unitTop">月</text>
+						</view>
+					</picker-view-column>
+					<picker-view-column v-if="!reset && (type == 1 || type == 2 || type == 7)">
+						<view class="tui-column-item" :class="{ 'tui-font-size_32': !unitTop && type == 7 }" v-for="(item, index) in days"
+						 :key="index">
+							{{ formatNum(item) }}
+							<text class="tui-unit-text" v-if="!unitTop">日</text>
+						</view>
+					</picker-view-column>
+					<picker-view-column v-if="!reset && (type == 1 || type == 4 || type == 5 || type == 7)">
+						<view class="tui-column-item" :class="{ 'tui-font-size_32': !unitTop && type == 7 }" v-for="(item, index) in hours"
+						 :key="index">
+							{{ formatNum(item) }}
+							<text class="tui-unit-text" v-if="!unitTop">时</text>
+						</view>
+					</picker-view-column>
+					<picker-view-column v-if="!reset && (type == 1 || type > 3)">
+						<view class="tui-column-item" :class="{ 'tui-font-size_32': !unitTop && type == 7 }" v-for="(item, index) in minutes"
+						 :key="index">
+							{{ formatNum(item) }}
+							<text class="tui-unit-text" v-if="!unitTop">分</text>
+						</view>
+					</picker-view-column>
+					<picker-view-column v-if="!reset && type > 4">
+						<view class="tui-column-item" :class="{ 'tui-font-size_32': !unitTop && type == 7 }" v-for="(item, index) in seconds"
+						 :key="index">
+							{{ formatNum(item) }}
+							<text class="tui-unit-text" v-if="!unitTop">秒</text>
+						</view>
+					</picker-view-column>
+				</picker-view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		name: 'tuiDatetime',
+		props: {
+			//1-日期+时间(年月日+时分) 2-日期(年月日) 3-日期(年月) 4-时间(时分) 5-时分秒 6-分秒 7-年月日 时分秒
+			type: {
+				type: Number,
+				default: 1
+			},
+			//年份区间
+			startYear: {
+				type: Number,
+				default: 1980
+			},
+			//年份区间
+			endYear: {
+				type: Number,
+				default: 2050
+			},
+			//"取消"字体颜色
+			cancelColor: {
+				type: String,
+				default: '#888'
+			},
+			//"确定"字体颜色
+			color: {
+				type: String,
+				default: '#5677fc'
+			},
+			//设置默认显示日期 2019-08-01 || 2019-08-01 17:01 || 2019/08/01
+			setDateTime: {
+				type: String,
+				default: ''
+			},
+			//单位置顶
+			unitTop: {
+				type: Boolean,
+				default: false
+			},
+			//圆角设置
+			radius: {
+				type: Boolean,
+				default: false
+			}
+		},
+		data() {
+			return {
+				isShow: false,
+				years: [],
+				months: [],
+				days: [],
+				hours: [],
+				minutes: [],
+				seconds: [],
+				year: 0,
+				month: 0,
+				day: 0,
+				hour: 0,
+				minute: 0,
+				second: 0,
+				startDate: '',
+				endDate: '',
+				value: [0, 0, 0, 0, 0, 0],
+				reset: false
+			};
+		},
+		mounted() {
+			this.initData();
+		},
+		computed: {
+			yearOrMonth() {
+				return `${this.year}-${this.month}`;
+			},
+			propsChange() {
+				return `${this.setDateTime}-${this.type}-${this.startYear}-${this.endYear}`;
+			}
+		},
+		watch: {
+			yearOrMonth() {
+				this.setDays();
+			},
+			propsChange() {
+				this.reset = true;
+				setTimeout(() => {
+					this.initData();
+				}, 10);
+			}
+		},
+		methods: {
+			stop() {},
+			formatNum: function(num) {
+				return num < 10 ? '0' + num : num + '';
+			},
+			generateArray: function(start, end) {
+				return Array.from(new Array(end + 1).keys()).slice(start);
+			},
+			getIndex: function(arr, val) {
+				let index = arr.indexOf(val);
+				return ~index ? index : 0;
+			},
+			//日期时间处理
+			initSelectValue() {
+				let fdate = this.setDateTime.replace(/\-/g, '/');
+				fdate = fdate && fdate.indexOf('/') == -1 ? `2020/01/01 ${fdate}` : fdate;
+				let time = null;
+				if (fdate) time = new Date(fdate);
+				else time = new Date();
+				this.year = time.getFullYear();
+				this.month = time.getMonth() + 1;
+				this.day = time.getDate();
+				this.hour = time.getHours();
+				this.minute = time.getMinutes();
+				this.second = time.getSeconds();
+			},
+			initData() {
+				this.initSelectValue();
+				this.reset = false;
+				switch (this.type) {
+					case 1:
+						this.value = [0, 0, 0, 0, 0];
+						this.setYears();
+						this.setMonths();
+						this.setDays();
+						this.setHours();
+						this.setMinutes();
+						break;
+					case 2:
+						this.value = [0, 0, 0];
+						this.setYears();
+						this.setMonths();
+						this.setDays();
+						break;
+					case 3:
+						this.value = [0, 0];
+						this.setYears();
+						this.setMonths();
+						break;
+					case 4:
+						this.value = [0, 0];
+						this.setHours();
+						this.setMinutes();
+						break;
+					case 5:
+						this.value = [0, 0, 0];
+						this.setHours();
+						this.setMinutes();
+						this.setSeconds();
+						break;
+					case 6:
+						this.value = [0, 0];
+						this.setMinutes();
+						this.setSeconds();
+						break;
+					case 7:
+						this.value = [0, 0, 0, 0, 0, 0];
+						this.setYears();
+						this.setMonths();
+						this.setDays();
+						this.setHours();
+						this.setMinutes();
+						this.setSeconds();
+						break;
+					default:
+						break;
+				}
+			},
+			setYears() {
+				this.years = this.generateArray(this.startYear, this.endYear);
+				setTimeout(() => {
+					this.$set(this.value, 0, this.getIndex(this.years, this.year));
+				}, 8);
+			},
+			setMonths() {
+				this.months = this.generateArray(1, 12);
+				setTimeout(() => {
+					this.$set(this.value, 1, this.getIndex(this.months, this.month));
+				}, 8);
+			},
+			setDays() {
+				if (this.type == 3 || this.type == 4) return;
+				let totalDays = new Date(this.year, this.month, 0).getDate();
+				this.days = this.generateArray(1, totalDays);
+				setTimeout(() => {
+					this.$set(this.value, 2, this.getIndex(this.days, this.day));
+				}, 8);
+			},
+			setHours() {
+				this.hours = this.generateArray(0, 23);
+				setTimeout(() => {
+					let index = this.type == 5 || this.type == 7 ? this.value.length - 3 : this.value.length - 2;
+					this.$set(this.value, index, this.getIndex(this.hours, this.hour));
+				}, 8);
+			},
+			setMinutes() {
+				this.minutes = this.generateArray(0, 59);
+				setTimeout(() => {
+					let index = this.type > 4 ? this.value.length - 2 : this.value.length - 1;
+					this.$set(this.value, index, this.getIndex(this.minutes, this.minute));
+				}, 8);
+			},
+			setSeconds() {
+				this.seconds = this.generateArray(0, 59);
+				setTimeout(() => {
+					this.$set(this.value, this.value.length - 1, this.getIndex(this.seconds, this.second));
+				}, 8);
+			},
+			show() {
+				setTimeout(() => {
+					this.isShow = true;
+				}, 50);
+			},
+			hide() {
+				this.isShow = false;
+			},
+			change(e) {
+				this.value = e.detail.value;
+				switch (this.type) {
+					case 1:
+						this.year = this.years[this.value[0]];
+						this.month = this.months[this.value[1]];
+						this.day = this.days[this.value[2]];
+						this.hour = this.hours[this.value[3]];
+						this.minute = this.minutes[this.value[4]];
+						break;
+					case 2:
+						this.year = this.years[this.value[0]];
+						this.month = this.months[this.value[1]];
+						this.day = this.days[this.value[2]];
+						break;
+					case 3:
+						this.year = this.years[this.value[0]];
+						this.month = this.months[this.value[1]];
+						break;
+					case 4:
+						this.hour = this.hours[this.value[0]];
+						this.minute = this.minutes[this.value[1]];
+						break;
+					case 5:
+						this.hour = this.hours[this.value[0]];
+						this.minute = this.minutes[this.value[1]];
+						this.second = this.seconds[this.value[2]];
+						break;
+					case 6:
+						this.minute = this.minutes[this.value[0]];
+						this.second = this.seconds[this.value[1]];
+						break;
+					case 7:
+						this.year = this.years[this.value[0]];
+						this.month = this.months[this.value[1]];
+						this.day = this.days[this.value[2]];
+						this.hour = this.hours[this.value[3]];
+						this.minute = this.minutes[this.value[4]];
+						this.second = this.seconds[this.value[5]];
+						break;
+					default:
+						break;
+				}
+			},
+			btnFix() {
+				setTimeout(() => {
+					let result = {};
+					let year = this.year;
+					let month = this.formatNum(this.month || 0);
+					let day = this.formatNum(this.day || 0);
+					let hour = this.formatNum(this.hour || 0);
+					let minute = this.formatNum(this.minute || 0);
+					let second = this.formatNum(this.second || 0);
+					switch (this.type) {
+						case 1:
+							result = {
+								year: year,
+								month: month,
+								day: day,
+								hour: hour,
+								minute: minute,
+								result: `${year}-${month}-${day} ${hour}:${minute}`
+							};
+							break;
+						case 2:
+							result = {
+								year: year,
+								month: month,
+								day: day,
+								result: `${year}-${month}-${day}`
+							};
+							break;
+						case 3:
+							result = {
+								year: year,
+								month: month,
+								result: `${year}-${month}`
+							};
+							break;
+						case 4:
+							result = {
+								hour: hour,
+								minute: minute,
+								result: `${hour}:${minute}`
+							};
+							break;
+						case 5:
+							result = {
+								hour: hour,
+								minute: minute,
+								second: second,
+								result: `${hour}:${minute}:${second}`
+							};
+							break;
+						case 6:
+							result = {
+								minute: minute,
+								second: second,
+								result: `${minute}:${second}`
+							};
+							break;
+						case 7:
+							result = {
+								year: year,
+								month: month,
+								day: day,
+								hour: hour,
+								minute: minute,
+								second: second,
+								result: `${year}-${month}-${day} ${hour}:${minute}:${second}`
+							};
+							break;
+						default:
+							break;
+					}
+					this.$emit('confirm', result);
+					this.hide();
+				}, 80);
+			}
+		}
+	};
+</script>
+
+<style scoped>
+	.tui-datetime-picker {
+		position: relative;
+		z-index: 999;
+	}
+
+	.tui-picker-view {
+		height: 100%;
+		box-sizing: border-box;
+	}
+
+	.tui-mask {
+		position: fixed;
+		z-index: 9998;
+		top: 0;
+		right: 0;
+		bottom: 0;
+		left: 0;
+		background-color: rgba(0, 0, 0, 0.6);
+		visibility: hidden;
+		opacity: 0;
+		transition: all 0.3s ease-in-out;
+	}
+
+	.tui-mask-show {
+		visibility: visible !important;
+		opacity: 1 !important;
+	}
+
+	.tui-header {
+		z-index: 9999;
+		position: fixed;
+		bottom: 0;
+		left: 0;
+		width: 100%;
+		transition: all 0.3s ease-in-out;
+		transform: translateY(100%);
+	}
+
+	.tui-date-header {
+		width: 100%;
+		height: 52rpx;
+		display: flex;
+		align-items: center;
+		justify-content: space-between;
+		background-color: #fff;
+		font-size: 26rpx;
+		line-height: 26rpx;
+		color: #555;
+		/* #ifdef MP */
+		box-shadow: 0 15rpx 10rpx -15rpx #efefef;
+		/* #endif */
+		/* #ifndef MP */
+		box-shadow: 0 15rpx 10rpx -15rpx #888;
+		/* #endif */
+		position: relative;
+		z-index: 2;
+	}
+
+	.tui-date-unit {
+		flex: 1;
+		text-align: center;
+	}
+
+	.tui-show {
+		transform: translateY(0);
+	}
+
+	.tui-picker-header {
+		width: 100%;
+		height: 90rpx;
+		padding: 0 40rpx;
+		display: flex;
+		justify-content: space-between;
+		align-items: center;
+		box-sizing: border-box;
+		font-size: 32rpx;
+		background-color: #fff;
+		position: relative;
+	}
+
+	.tui-date-radius {
+		border-top-left-radius: 20rpx;
+		border-top-right-radius: 20rpx;
+		overflow: hidden;
+	}
+
+	.tui-picker-header::after {
+		content: '';
+		position: absolute;
+		border-bottom: 1rpx solid #eaeef1;
+		-webkit-transform: scaleY(0.5);
+		transform: scaleY(0.5);
+		bottom: 0;
+		right: 0;
+		left: 0;
+	}
+
+	.tui-picker-body {
+		width: 100%;
+		height: 520rpx;
+		overflow: hidden;
+		background-color: #fff;
+	}
+
+	.tui-column-item {
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		font-size: 36rpx;
+		color: #333;
+	}
+
+	.tui-font-size_32 {
+		font-size: 32rpx !important;
+	}
+
+	.tui-unit-text {
+		font-size: 24rpx !important;
+		padding-left: 8rpx;
+	}
+
+	.tui-btn-picker {
+		padding: 16rpx;
+		box-sizing: border-box;
+		text-align: center;
+		text-decoration: none;
+	}
+
+	.tui-opacity {
+		opacity: 0.5;
+	}
+</style>

+ 103 - 0
components/thorui/tui-divider/tui-divider.vue

@@ -0,0 +1,103 @@
+<template>
+	<view class="tui-divider" :style="{ height: height + 'rpx' }">
+		<view class="tui-divider-line" :style="{ width: width, background: getBgColor(gradual, gradualColor, dividerColor) }"></view>
+		<view
+			class="tui-divider-text"
+			:style="{ color: color, fontSize: size + 'rpx', lineHeight: size + 'rpx', backgroundColor: backgroundColor, fontWeight: bold ? 'bold' : 'normal' }"
+		>
+			<slot></slot>
+		</view>
+	</view>
+</template>
+
+<script>
+export default {
+	name: 'tuiDivider',
+	props: {
+		//divider占据高度
+		height: {
+			type: Number,
+			default: 100
+		},
+		//divider宽度,可填写具体长度,如400rpx
+		width: {
+			type: String,
+			default: '100%'
+		},
+		//divider颜色,如果为渐变线条,此属性失效
+		dividerColor: {
+			type: String,
+			default: '#e5e5e5'
+		},
+		//文字颜色
+		color: {
+			type: String,
+			default: '#999'
+		},
+		//文字大小 rpx
+		size: {
+			type: Number,
+			default: 24
+		},
+		bold: {
+			type: Boolean,
+			default: false
+		},
+		//背景颜色,和当前页面背景色保持一致
+		backgroundColor: {
+			type: String,
+			default: '#fafafa'
+		},
+		//是否为渐变线条,为true,divideColor失效
+		gradual: {
+			type: Boolean,
+			default: false
+		},
+		//渐变色值,to right ,提供两个色值即可,由浅至深
+		gradualColor: {
+			type: Array,
+			default: function() {
+				return ['#eee', '#ccc'];
+			}
+		}
+	},
+	methods: {
+		getBgColor: function(gradual, gradualColor, dividerColor) {
+			let bgColor = dividerColor;
+			if (gradual) {
+				bgColor = 'linear-gradient(to right,' + gradualColor[0] + ',' + gradualColor[1] + ',' + gradualColor[1] + ',' + gradualColor[0] + ')';
+			}
+			return bgColor;
+		}
+	}
+};
+</script>
+
+<style scoped>
+.tui-divider {
+	width: 100%;
+	position: relative;
+	text-align: center;
+	display: flex;
+	justify-content: center;
+	align-items: center;
+	box-sizing: border-box;
+	overflow: hidden;
+}
+
+.tui-divider-line {
+	position: absolute;
+	height: 1rpx;
+	top: 50%;
+	left: 50%;
+	-webkit-transform: scaleY(0.5) translateX(-50%) translateZ(0);
+	transform: scaleY(0.5) translateX(-50%) translateZ(0);
+}
+
+.tui-divider-text {
+	position: relative;
+	text-align: center;
+	padding: 0 18rpx;
+	z-index: 1;
+}
+</style>

+ 98 - 0
components/thorui/tui-drawer/tui-drawer.vue

@@ -0,0 +1,98 @@
+<template>
+	<view  @touchmove.stop.prevent>
+		<view v-if="mask" class="tui-drawer-mask" :class="{'tui-drawer-mask_show':visible}" @tap="handleMaskClick"></view>
+		<view class="tui-drawer-container" :class="[mode=='left'?'tui-drawer-container_left':'tui-drawer-container_right',visible?'tui-drawer-container_show':'']">
+			<slot></slot>
+		</view>
+	</view>
+
+</template>
+
+<script>
+	/**
+	 * 超过一屏时插槽使用scroll-view
+	 **/
+	export default {
+		name:"tuiDrawer",
+		props: {
+			visible: {
+				type: Boolean,
+				default: false
+			},
+			mask: {
+				type: Boolean,
+				default: true
+			},
+			maskClosable: {
+				type: Boolean,
+				default: true
+			},
+			mode: {
+				type: String,
+				default: 'true' // left right
+			}
+		},
+		methods: {
+			handleMaskClick() {
+				if (!this.maskClosable) {
+					return;
+				}
+				this.$emit('close', {});
+			}
+		}
+	}
+</script>
+
+<style scoped>
+	.tui-drawer-mask {
+		opacity: 0;
+		visibility: hidden;
+		position: fixed;
+		top: 0;
+		left: 0;
+		right: 0;
+		bottom: 0;
+		z-index: 8888;
+		background-color: rgba(0, 0, 0, 0.6);
+		transition: all 0.3s ease-in-out;
+	}
+	.tui-drawer-mask_show {
+		display: block;
+		visibility: visible;
+		opacity: 1;
+	}
+
+	.tui-drawer-container {
+		position: fixed;
+		left: 50%;
+		height: 100.2%;
+		top: 0;
+		transform: translate3d(-50%, -50%, 0);
+		transform-origin: center;
+		transition: all 0.3s ease-in-out;
+		z-index: 99999;
+		opacity: 0;
+		overflow-y: scroll;
+		background-color: #fff;
+		-webkit-overflow-scrolling: touch;
+		-ms-touch-action: pan-y cross-slide-y;
+		-ms-scroll-chaining: none;
+		-ms-scroll-limit: 0 50 0 50;
+	}
+	.tui-drawer-container_left {
+		left: 0;
+		top: 50%;
+		transform: translate3d(-100%, -50%, 0);
+	}
+
+	.tui-drawer-container_right {
+		right: 0;
+		top: 50%;
+		left: auto;
+		transform: translate3d(100%, -50%, 0);
+	}
+	.tui-drawer-container_show {
+		opacity: 1;
+		transform: translate3d(0, -50%, 0);
+	}
+</style>

+ 69 - 0
components/thorui/tui-dropdown-list/tui-dropdown-list.vue

@@ -0,0 +1,69 @@
+<template>
+	<view class="tui-selected-class tui-dropdown-list" :style="{ height: selectHeight ? selectHeight + 'rpx' : 'auto' }">
+		<slot name="selectionbox"></slot>
+		<view
+			class="tui-dropdown-view"
+			:class="[show ? 'tui-dropdownlist-show' : '']"
+			:style="{ backgroundColor: backgroundColor, height: show ? height + 'rpx' : 0, top: top + 'rpx' }"
+		>
+			<slot name="dropdownbox"></slot>
+		</view>
+	</view>
+</template>
+
+<script>
+export default {
+	name: 'tuiDropdownList',
+	props: {
+		//控制显示
+		show: {
+			type: Boolean,
+			default: false
+		},
+		//背景颜色
+		backgroundColor: {
+			type: String,
+			default: 'transparent'
+		},
+		//top  rpx
+		top: {
+			type: Number,
+			default: 0
+		},
+		//下拉框高度 rpx
+		height: {
+			type: Number,
+			default: 0
+		},
+		//选择框高度 单位rpx
+		selectHeight: {
+			type: Number,
+			default: 0
+		}
+	},
+	methods: {}
+};
+</script>
+
+<style scoped>
+.tui-dropdown-list {
+	position: relative;
+}
+
+.tui-dropdown-view {
+	width: 100%;
+	overflow: hidden;
+	position: absolute;
+	z-index: -99;
+	left: 0;
+	opacity: 0;
+	/* visibility: hidden; */
+	transition: all 0.2s ease-in-out;
+}
+
+.tui-dropdownlist-show {
+	opacity: 1;
+	z-index: 996;
+	/* visibility: visible; */
+}
+</style>

+ 255 - 0
components/thorui/tui-fab/tui-fab.vue

@@ -0,0 +1,255 @@
+<template>
+	<view @touchmove.stop.prevent>
+		<view class="tui-fab-box" :class="{'tui-fab-right':!left || (left && right)}" :style="{left:getLeft(),right:getRight(),bottom:bottom+'rpx'}">
+			<view class="tui-fab-btn" :class="{'tui-visible':isOpen,'tui-fab-hidden':hidden}">
+				<view class="tui-fab-item-box" :class="{'tui-fab-item-left':left && !right && item.imgUrl}" v-for="(item,index) in btnList"
+				 :key="index" @tap.stop="handleClick(index)">
+					<view :class="[left && !right?'tui-text-left':'tui-text-right']" v-if="item.imgUrl" :style="{fontSize:item.fontSize+'rpx',color:item.color}">{{item.text || ""}}</view>
+					<view class="tui-fab-item" :style="{width:width+'rpx',height:height+'rpx',background:item.bgColor || bgColor,borderRadius:radius}">
+						<view class="tui-fab-title" v-if="!item.imgUrl" :style="{fontSize:item.fontSize+'rpx',color:item.color}">{{item.text || ""}}</view>
+						<image :src="item.imgUrl" class="tui-fab-img" v-else :style="{width:item.imgWidth+'rpx',height:item.imgHeight+'rpx'}"></image>
+					</view>
+				</view>
+			</view>
+			<view class="tui-fab-item" :class="{'tui-active':isOpen}" :style="{width:width+'rpx',height:height+'rpx',borderRadius:radius,background:bgColor,color:color}"
+			 @tap.stop="handleClick(-1)">
+				<view class="tui-fab-icon tui-icon-plus"></view>
+			</view>
+		</view>
+		<view class="tui-fab-mask" :class="{'tui-visible':isOpen}" @tap="handleClickCancel"></view>
+	</view>
+</template>
+
+<script>
+	//拓展出来的按钮不应多于6个,否则违反了作为悬浮按钮的快速、高效的原则
+	export default {
+		name: "tuiFab",
+		props: {
+			//rpx 为0时值为auto
+			left: {
+				type: Number,
+				default: 0
+			},
+			//rpx 当为0时且left不为0,值为auto
+			right: {
+				type: Number,
+				default: 80
+			},
+			//rpx bottom值
+			bottom: {
+				type: Number,
+				default: 100
+			},
+			//默认按钮 宽度 rpx
+			width: {
+				type: Number,
+				default: 108
+			},
+			//默认按钮 高度 rpx
+			height: {
+				type: Number,
+				default: 108
+			},
+			//圆角值
+			radius: {
+				type: String,
+				default: "50%"
+			},
+			//默认按钮背景颜色
+			bgColor: {
+				type: String,
+				default: "#5677fc"
+			},
+			//字体颜色
+			color: {
+				type: String,
+				default: "#fff"
+			},
+			//拓展按钮
+			// bgColor: "#5677fc",
+			// //图标/图片地址
+			// imgUrl: "/static/images/fab/fab_reward.png",
+			// //图片高度 rpx
+			// imgHeight: 60,
+			// //图片宽度 rpx
+			// imgWidth: 60,
+			// //名称
+			// text: "名称",
+			// //字体大小
+			// fontSize: 30,
+			// //字体颜色
+			// color: "#fff"
+			btnList: {
+				type: Array,
+				default () {
+					return []
+				}
+			},
+			//点击遮罩 是否可关闭
+			maskClosable: {
+				type: Boolean,
+				default: false
+			}
+		},
+		data() {
+			return {
+				isOpen: false,
+				hidden: true,
+				timer: null
+			};
+		},
+		methods: {
+			getLeft() {
+				let val = "auto"
+				if (this.left && !this.right) {
+					val = this.left + 'rpx'
+				}
+				return val
+			},
+			getRight() {
+				let val = this.right + 'rpx'
+				if (this.left && !this.right) {
+					val = "auto"
+				}
+				return val
+			},
+			handleClick: function(index) {
+				this.hidden = false
+				clearTimeout(this.timer)
+				if (index == -1 && this.btnList.length) {
+					this.isOpen = !this.isOpen
+				} else {
+					this.$emit("click", {
+						index: index
+					})
+					this.isOpen = false
+				}
+				if (!this.isOpen) {
+					this.timer = setTimeout(() => {
+						this.hidden = true
+					}, 200)
+				}
+			},
+			handleClickCancel: function() {
+				if (!this.maskClosable) return;
+				this.isOpen = false
+			}
+		},
+		beforeDestroy() {
+			clearTimeout(this.timer)
+			this.timer = null
+		}
+	}
+</script>
+
+<style scoped>
+	@font-face {
+		font-family: 'tuifab';
+		src: url(data:application/font-woff;charset=utf-8;base64,d09GRgABAAAAAAREAA0AAAAABnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABGRlRNAAAEKAAAABoAAAAciPExJUdERUYAAAQIAAAAHgAAAB4AKQAKT1MvMgAAAaAAAABCAAAAVjyBSAVjbWFwAAAB9AAAAD4AAAFCAA/pvmdhc3AAAAQAAAAACAAAAAj//wADZ2x5ZgAAAkAAAABRAAAAYFkYQQNoZWFkAAABMAAAADAAAAA2Fm5OF2hoZWEAAAFgAAAAHQAAACQH3QOFaG10eAAAAeQAAAAPAAAAEAwAAANsb2NhAAACNAAAAAoAAAAKADAAAG1heHAAAAGAAAAAHwAAACABDwAobmFtZQAAApQAAAFJAAACiCnmEVVwb3N0AAAD4AAAAB8AAAAx2XRuznjaY2BkYGAAYtGolt54fpuvDNwsDCBwc1krH5xm/t/I/J+5FsjlYGACiQIAGAEKZHjaY2BkYGBu+N/AEMPCAALM/xkYGVABCwBZ4wNrAAAAeNpjYGRgYGBhkGEA0QwMTEDMBYQMDP/BfAYAC4kBOAB42mNgZGFgnMDAysDA1Ml0hoGBoR9CM75mMGLkAIoysDIzYAUBaa4pDA7PhJ8JMzf8b2CIYW5gaAAKM4LkAN21DAEAAHjaY2GAABYIZgYAAIMAEAB42mNgYGBmgGAZBkYGELAB8hjBfBYGBSDNAoRA/jPh//8hpOQHqEoGRjYGGJOBkQlIMDGgAkaGYQ8AUSIHswAAAAAAAAAAAAAAMAAAeNpjYGRg/t/I/J+5lkGagYFRUVCPUYmNXVCRj1FETFxRUI7RyMxcUNGO0USN+fS/HEY5XTnGfznicnLijFPAHMYpYnJyjFvBlBgWBQBNJxKpAAAAeNp9kD1OAzEQhZ/zByQSQiCoXVEA2vyUKRMp9Ailo0g23pBo1155nUg5AS0VB6DlGByAGyDRcgpelkmTImvt6PObmeexAZzjGwr/3yXuhBWO8ShcwREy4Sr1F+Ea+V24jhY+hRvUf4SbuFUD4RYu1BsdVO2Eu5vSbcsKZxgIV3CKJ+Eq9ZVwjfwqXMcVPoQb1L+EmxjjV7iFa2WpDOFhMEFgnEFjig3jAjEcLJIyBtahOfRmEsxMTzd6ETubOBso71dilwMeaDnngCntPbdmvkon/mDLgdSYbh4FS7YpjS4idCgbXyyc1d2oc7D9nu22tNi/a4E1x+xRDWzU/D3bM9JIbAyvkJI18jK3pBJTj2hrrPG7ZynW814IiU68y/SIx5o0dTr3bmniwOLn8owcfbS5kj33qBw+Y1kIeb/dTsQgil2GP5PYcRkAAAB42mNgYoAALjDJyIAOWMCiTIxMbFmZiRmJ+QALXAKKAAAAAAH//wACAAEAAAAMAAAAFgAAAAIAAQADAAMAAQAEAAAAAgAAAAB42mNgYGBkAIKrS9Q5QPTNZa18MBoAPbcFzgAA) format('woff');
+		font-weight: normal;
+		font-style: normal;
+	}
+
+	.tui-fab-icon {
+		font-family: "tuifab" !important;
+		font-style: normal;
+		-webkit-font-smoothing: antialiased;
+		-moz-osx-font-smoothing: grayscale;
+		padding: 10rpx;
+	}
+
+	.tui-icon-plus:before {
+		content: "\e613";
+	}
+
+	.tui-fab-box {
+		display: flex;
+		justify-content: center;
+		flex-direction: column;
+		position: fixed;
+		z-index: 99997;
+	}
+
+	.tui-fab-right {
+		align-items: flex-end;
+	}
+
+	.tui-fab-btn {
+		transform: scale(0);
+		transition: all 0.2s ease-in-out;
+		opacity: 0;
+		visibility: hidden;
+	}
+
+	.tui-fab-hidden {
+		height: 0;
+		width: 0;
+	}
+
+
+	.tui-fab-item-box {
+		display: flex;
+		align-items: center;
+		justify-content: flex-end;
+		padding-bottom: 40rpx;
+	}
+
+	.tui-fab-item-left {
+		flex-flow: row-reverse;
+	}
+
+	.tui-fab-title {
+		width: 90%;
+		text-align: center;
+		white-space: nowrap;
+		overflow: hidden;
+		text-overflow: ellipsis;
+	}
+
+	.tui-text-left {
+		padding-left: 28rpx;
+	}
+
+	.tui-text-right {
+		padding-right: 28rpx;
+	}
+
+	.tui-fab-img {
+		display: block;
+	}
+
+	.tui-fab-item {
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		box-shadow: 0 0 5px 2px rgba(0, 0, 0, 0.1);
+		transition: all 0.2s linear;
+	}
+
+	.tui-radius {
+		border-radius: 50%;
+	}
+
+	.tui-active {
+		transform: rotate(135deg);
+	}
+
+	.tui-fab-mask {
+		position: fixed;
+		top: 0;
+		left: 0;
+		right: 0;
+		bottom: 0;
+		background: rgba(0, 0, 0, 0.75);
+		z-index: 99996;
+		transition: all 0.2s ease-in-out;
+		opacity: 0;
+		visibility: hidden;
+	}
+
+	.tui-visible {
+		visibility: visible;
+		opacity: 1;
+		transform: scale(1);
+	}
+</style>

+ 118 - 0
components/thorui/tui-footer/tui-footer.vue

@@ -0,0 +1,118 @@
+<template>
+	<view class="tui-footer-class tui-footer" :class="[fixed?'tui-fixed':'']" :style='{backgroundColor:backgroundColor}'>
+		<view class="tui-footer-link" v-if="navigate.length>0">
+			<block v-for="(item,index) in navigate" :key="index">
+				<navigator class="tui-link" hover-class="tui-link-hover" :hover-stop-propagation="true" :style="{color:(item.color || '#596d96'),fontSize:(item.size || 28)+'rpx'}"
+				 :open-type="item.type" :url="item.url" :target="item.target" :delta="item.delta" :app-id="item.appid"
+				 :path="item.path" :extra-data="item.extradata" :bindsuccess="item.bindsuccess" :bindfail="item.bindfail">{{item.text}}</navigator>
+			</block>
+		</view>
+		<view class="tui-footer-copyright" :style="{color:color,fontSize:size+'rpx'}">
+			{{copyright}}
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		name: "tuiFooter",
+		props: {
+			//type target url delta appid path extradata bindsuccess bindfail text color size
+			//链接设置  数据格式对应上面注释的属性值
+			navigate: {
+				type: Array,
+				default:function(){
+					return  []
+				}
+			},
+			//底部文本
+			copyright: {
+				type: String,
+				default: "All Rights Reserved."
+			},
+			//copyright 字体颜色
+			color: {
+				type: String,
+				default: "#A7A7A7"
+			},
+			//copyright 字体大小
+			size: {
+				type: Number,
+				default: 24
+			},
+			//footer背景颜色
+			backgroundColor: {
+				type: String,
+				default: "transparent"
+			},
+			//是否固定在底部
+			fixed: {
+				type: Boolean,
+				default: true
+			}
+		},
+		methods: {
+		}
+	}
+</script>
+
+<style scoped>
+	.tui-footer {
+		width: 100%;
+		overflow: hidden;
+		padding: 30rpx 24rpx;
+		box-sizing: border-box;
+	}
+
+	.tui-fixed {
+		position: fixed;
+		z-index: 9999;
+		bottom: 0;
+		left: 0;
+	}
+
+	.tui-footer-link {
+		color: #596d96;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		font-size: 28rpx;
+	}
+
+	.tui-link {
+		position: relative;
+		padding: 0 18rpx;
+		line-height: 1;
+	}
+
+	.tui-link::before {
+		content: " ";
+		position: absolute;
+		right: 0;
+		top: 0;
+		width: 1px;
+		bottom: 0;
+		border-right: 1px solid #d3d3d3;
+		-webkit-transform-origin: 100% 0;
+		transform-origin: 100% 0;
+		-webkit-transform: scaleX(0.5);
+		transform: scaleX(0.5);
+	}
+
+	.tui-link:last-child::before {
+		border-right: 0 !important
+	}
+
+	.tui-link-hover {
+		opacity: 0.5
+	}
+
+	.tui-footer-copyright {
+		font-size: 24rpx;
+		color: #A7A7A7;
+		line-height: 1;
+		text-align: center;
+		padding-top: 16rpx;
+		padding-bottom:env(safe-area-inset-bottom);
+	}
+</style>

+ 137 - 0
components/thorui/tui-grid-item/tui-grid-item.vue

@@ -0,0 +1,137 @@
+<template>
+	<view class="tui-grid" :class="[bottom?'':'tui-grid-bottom','tui-grid-'+(cell<2?3:cell)]" :hover-class="hover?'tui-item-hover':''"
+	 :hover-stay-time="150" :style="{backgroundColor:backgroundColor}" @tap="handleClick">
+		<view class='tui-grid-bg'>
+			<slot></slot>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		name: "tuiGridItem",
+		props: {
+			cell: {
+				type: [Number,String],
+				default: 3
+			},
+			backgroundColor: {
+				type: String,
+				default: "#fff"
+			},
+			//是否有点击效果
+			hover: {
+				type: Boolean,
+				default: true
+			},
+			//底部线条
+			bottom: {
+				type: Boolean,
+				default: true
+			},
+			index: {
+				type: Number,
+				default: 0
+			}
+		},
+		methods: {
+			handleClick() {
+				this.$emit('click', {
+					index: this.index
+				});
+			}
+		}
+	}
+</script>
+
+<style scoped>
+	.tui-grid {
+		position: relative;
+		padding: 40rpx 20rpx;
+		box-sizing: border-box;
+		background: #fff;
+		float: left;
+	}
+
+	.tui-grid-2 {
+		width: 50%;
+	}
+
+	.tui-grid-3 {
+		width: 33.333333333%;
+	}
+
+	.tui-grid-4 {
+		width: 25%;
+		padding: 30rpx 20rpx !important;
+	}
+
+	.tui-grid-5 {
+		width: 20%;
+		padding: 20rpx !important;
+	}
+
+	.tui-grid-2:nth-of-type(2n)::before {
+		width: 0;
+		border-right: 0;
+	}
+
+	.tui-grid-3:nth-of-type(3n)::before {
+		width: 0;
+		border-right: 0;
+	}
+
+	.tui-grid-4:nth-of-type(4n)::before {
+		width: 0;
+		border-right: 0;
+	}
+
+	.tui-grid-5:nth-of-type(5n)::before {
+		width: 0;
+		border-right: 0;
+	}
+
+	.tui-grid::before {
+		content: " ";
+		position: absolute;
+		right: 0;
+		top: 0;
+		width: 1px;
+		bottom: 0;
+		border-right: 1px solid #eaeef1;
+		-webkit-transform-origin: 100% 0;
+		transform-origin: 100% 0;
+		-webkit-transform: scaleX(0.5);
+		transform: scaleX(0.5);
+	}
+
+	.tui-grid::after {
+		content: " ";
+		position: absolute;
+		left: 0;
+		bottom: 0;
+		right: 0;
+		height: 1px;
+		border-bottom: 1px solid #eaeef1;
+		-webkit-transform-origin: 0 100%;
+		transform-origin: 0 100%;
+		-webkit-transform: scaleY(0.5);
+		transform: scaleY(0.5);
+	}
+
+	.tui-grid-bottom::after {
+		height: 0 !important;
+		border-bottom: 0 !important
+	}
+
+	.tui-grid-bg {
+		position: relative;
+		padding: 0;
+		width: 100%;
+		box-sizing: border-box;
+	}
+
+	.tui-item-hover {
+		background-color: #f7f7f9 !important;
+	}
+</style>

+ 44 - 0
components/thorui/tui-grid/tui-grid.vue

@@ -0,0 +1,44 @@
+<template>
+	<view class="tui-grids" :class="{'tui-border-top':unlined}">
+		<slot></slot>
+	</view>
+</template>
+
+<script>
+	export default {
+		name:"tuiGrid",
+		props: {
+			//是否去掉上线条
+			unlined: {
+				type: Boolean,
+				default: false
+			}
+		}
+	}
+</script>
+
+<style scoped>
+	.tui-grids {
+		width: 100%;
+		position: relative;
+		overflow: hidden;
+	}
+
+	.tui-grids::after {
+		content: " ";
+		position: absolute;
+		left: 0;
+		top: 0;
+		width: 100%;
+		height: 1px;
+		border-top: 1px solid #eaeef1;
+		-webkit-transform-origin: 0 0;
+		transform-origin: 0 0;
+		-webkit-transform: scaleY(0.5);
+		transform: scaleY(0.5);
+	}
+
+	.tui-border-top::after {
+		border-top: 0 !important;
+	}
+</style>

Разница между файлами не показана из-за своего большого размера
+ 55 - 0
components/thorui/tui-icon/tui-icon.vue


+ 1034 - 0
components/thorui/tui-image-cropper/tui-image-cropper.vue

@@ -0,0 +1,1034 @@
+<template>
+	<view class="tui-container" @touchmove.stop.prevent="stop">
+		<view class="tui-image-cropper" @touchend="cutTouchEnd" @touchstart="cutTouchStart" @touchmove="cutTouchMove">
+			<view class="tui-content">
+				<view class="tui-content-top tui-bg-transparent" :style="{ height: cutY + 'px', transitionProperty: cutAnimation ? '' : 'background' }"></view>
+				<view class="tui-content-middle" :style="{ height: canvasHeight + 'px' }">
+					<view class="tui-bg-transparent" :style="{ width: cutX + 'px', transitionProperty: cutAnimation ? '' : 'background' }"></view>
+					<view
+						class="tui-cropper-box"
+						:style="{ width: canvasWidth + 'px', height: canvasHeight + 'px', borderColor: borderColor, transitionProperty: cutAnimation ? '' : 'background' }"
+					>
+						<view
+							v-for="(item, index) in 4"
+							:key="index"
+							class="tui-edge"
+							:class="[`tui-${index < 2 ? 'top' : 'bottom'}-${index === 0 || index === 2 ? 'left' : 'right'}`]"
+							:style="{
+								width: edgeWidth,
+								height: edgeWidth,
+								borderColor: edgeColor,
+								borderWidth: edgeBorderWidth,
+								left: index === 0 || index === 2 ? `-${edgeOffsets}` : 'auto',
+								right: index === 1 || index === 3 ? `-${edgeOffsets}` : 'auto',
+								top: index < 2 ? `-${edgeOffsets}` : 'auto',
+								bottom: index > 1 ? `-${edgeOffsets}` : 'auto'
+							}"
+						></view>
+					</view>
+					<view class="tui-flex-auto tui-bg-transparent" :style="{ transitionProperty: cutAnimation ? '' : 'background' }"></view>
+				</view>
+				<view class="tui-flex-auto tui-bg-transparent" :style="{ transitionProperty: cutAnimation ? '' : 'background' }"></view>
+			</view>
+			<image
+				@load="imageLoad"
+				@error="imageLoad"
+				@touchstart="start"
+				@touchmove="move"
+				@touchend="end"
+				:style="{
+					width: imgWidth ? imgWidth + 'px' : 'auto',
+					height: imgHeight ? imgHeight + 'px' : 'auto',
+					transform: imgTransform,
+					transitionDuration: (cutAnimation ? 0.35 : 0) + 's'
+				}"
+				class="tui-cropper-image"
+				:src="imageUrl"
+				v-if="imageUrl"
+				mode="widthFix"
+			></image>
+		</view>
+		<canvas
+			canvas-id="tui-image-cropper"
+			id="tui-image-cropper"
+			:disable-scroll="true"
+			:style="{ width: CROPPER_WIDTH * scaleRatio + 'px', height: CROPPER_HEIGHT * scaleRatio + 'px' }"
+			class="tui-cropper-canvas"
+		></canvas>
+		<view class="tui-cropper-tabbar" v-if="!custom">
+			<view class="tui-op-btn" @tap.stop="back">取消</view>
+			<image :src="rotateImg" class="tui-rotate-img" @tap="setAngle"></image>
+			<view class="tui-op-btn" @tap.stop="getImage">完成</view>
+		</view>
+	</view>
+</template>
+
+<script>
+/**
+ * 注意:组件中使用的图片地址,将文件复制到自己项目中
+ * 如果图片位置与组件同级,编译成小程序时图片会丢失
+ * 拷贝static下整个components文件夹
+ *也可直接转成base64(不建议)
+ * */
+export default {
+	name: 'tuiImageCropper',
+	props: {
+		//图片路径
+		imageUrl: {
+			type: String,
+			default: ''
+		},
+		/*
+			 默认正方形,可修改大小控制比例
+			 裁剪框高度 px
+			*/
+		height: {
+			type: Number,
+			default: 280
+		},
+		//裁剪框宽度 px
+		width: {
+			type: Number,
+			default: 280
+		},
+		//裁剪框最小宽度 px
+		minWidth: {
+			type: Number,
+			default: 100
+		},
+		//裁剪框最小高度 px
+		minHeight: {
+			type: Number,
+			default: 100
+		},
+		//裁剪框最大宽度 px
+		maxWidth: {
+			type: Number,
+			default: 360
+		},
+		//裁剪框最大高度 px
+		maxHeight: {
+			type: Number,
+			default: 360
+		},
+		//裁剪框border颜色
+		borderColor: {
+			type: String,
+			default: 'rgba(255,255,255,0.1)'
+		},
+		//裁剪框边缘线颜色
+		edgeColor: {
+			type: String,
+			default: '#FFFFFF'
+		},
+		//裁剪框边缘线宽度 w=h
+		edgeWidth: {
+			type: String,
+			default: '34rpx'
+		},
+		//裁剪框边缘线border宽度
+		edgeBorderWidth: {
+			type: String,
+			default: '6rpx'
+		},
+		//偏移距离,根据edgeBorderWidth进行调整
+		edgeOffsets: {
+			type: String,
+			default: '6rpx'
+		},
+		/**
+		 * 如果宽度和高度都为true则裁剪框禁止拖动
+		 * 裁剪框宽度锁定
+		 */
+		lockWidth: {
+			type: Boolean,
+			default: false
+		},
+		//裁剪框高度锁定
+		lockHeight: {
+			type: Boolean,
+			default: false
+		},
+		//锁定裁剪框比例(放大或缩小)
+		lockRatio: {
+			type: Boolean,
+			default: false
+		},
+		//生成的图片尺寸相对剪裁框的比例
+		scaleRatio: {
+			type: Number,
+			default: 2
+		},
+		//图片的质量,取值范围为 (0, 1],不在范围内时当作1.0处理
+		quality: {
+			type: Number,
+			default: 0.8
+		},
+		//图片旋转角度
+		rotateAngle: {
+			type: Number,
+			default: 0
+		},
+		//图片最小缩放比
+		minScale: {
+			type: Number,
+			default: 0.5
+		},
+		//图片最大缩放比
+		maxScale: {
+			type: Number,
+			default: 2
+		},
+		//是否禁用触摸旋转(为false则可以触摸转动图片,limitMove为false生效)
+		disableRotate: {
+			type: Boolean,
+			default: true
+		},
+		//是否限制移动范围(剪裁框只能在图片内,为true不可触摸转动图片)
+		limitMove: {
+			type: Boolean,
+			default: true
+		},
+		//自定义操作栏(为true时隐藏底部操作栏)
+		custom: {
+			type: Boolean,
+			default: false
+		},
+		//值发生改变开始裁剪(custom为true时生效)
+		startCutting: {
+			type: [Number, Boolean],
+			default: 0
+		},
+		/**
+		 * 是否返回base64(H5端默认base64)
+		 * 支持平台:App,微信小程序,支付宝小程序,H5(默认url就是base64)
+		 **/
+		isBase64: {
+			type: Boolean,
+			default: false
+		},
+		//裁剪时是否显示loadding
+		loadding: {
+			type: Boolean,
+			default: true
+		},
+		//旋转icon
+		rotateImg: {
+			type: String,
+			default: '/static/components/cropper/img_rotate.png'
+		}
+	},
+	data() {
+		return {
+			MOVE_THROTTLE: null, //触摸移动节流setTimeout
+			MOVE_THROTTLE_FLAG: true, //节流标识
+			TIME_CUT_CENTER: null,
+			CROPPER_WIDTH: 200, //裁剪框宽
+			CROPPER_HEIGHT: 200, //裁剪框高
+			CUT_START: null,
+			cutX: 0, //画布x轴起点
+			cutY: 0, //画布y轴起点0
+			touchRelative: [
+				{
+					x: 0,
+					y: 0
+				}
+			], //手指或鼠标和图片中心的相对位置
+			flagCutTouch: false, //是否是拖动裁剪框
+			hypotenuseLength: 0, //双指触摸时斜边长度
+			flagEndTouch: false, //是否结束触摸
+			canvasWidth: 0,
+			canvasHeight: 0,
+			imgWidth: 0, //图片宽度
+			imgHeight: 0, //图片高度
+			scale: 1, //图片缩放比
+			angle: 0, //图片旋转角度
+			cutAnimation: false, //是否开启图片和裁剪框过渡
+			cutAnimationTime: null,
+			imgTop: 0, //图片上边距
+			imgLeft: 0, //图片左边距
+			ctx: null,
+			sysInfo: null
+		};
+	},
+	computed: {
+		imgTransform: function() {
+			return `translate3d(${this.imgLeft - this.imgWidth / 2}px,${this.imgTop - this.imgHeight / 2}px,0) scale(${this.scale}) rotate(${this.angle}deg)`;
+		}
+	},
+	watch: {
+		imageUrl(val, oldVal) {
+			this.imageReset();
+			this.showLoading();
+			uni.getImageInfo({
+				src: val,
+				success: res => {
+					//计算图片尺寸
+					this.imgComputeSize(res.width, res.height);
+					if (this.limitMove) {
+						//限制移动,不留空白处理
+						this.imgMarginDetectionScale();
+					}
+				},
+				fail: err => {
+					this.imgComputeSize();
+					if (this.limitMove) {
+						this.imgMarginDetectionScale();
+					}
+				}
+			});
+		},
+		//监听截取框宽高变化
+		canvasWidth(val) {
+			if (val < this.minWidth) {
+				this.canvasWidth = this.minWidth;
+			}
+			this.computeCutSize();
+		},
+		canvasHeight(val) {
+			if (val < this.minHeight) {
+				this.canvasHeight = this.minHeight;
+			}
+			this.computeCutSize();
+		},
+		rotateAngle(val) {
+			this.cutAnimation = true;
+			this.angle = val;
+		},
+		angle(val) {
+			this.moveStop();
+			if (this.limitMove && val % 90) {
+				this.angle = Math.round(val / 90) * 90;
+			}
+			this.imgMarginDetectionScale();
+		},
+		cutAnimation(val) {
+			//开启过渡260毫秒之后自动关闭
+			clearTimeout(this.cutAnimationTime);
+			if (val) {
+				this.cutAnimationTime = setTimeout(() => {
+					this.cutAnimation = false;
+				}, 260);
+			}
+		},
+		limitMove(val) {
+			if (val) {
+				if (this.angle % 90) {
+					this.angle = Math.round(this.angle / 90) * 90;
+				}
+				this.imgMarginDetectionScale();
+			}
+		},
+		cutY(value) {
+			this.cutDetectionPosition();
+		},
+		cutX(value) {
+			this.cutDetectionPosition();
+		},
+		startCutting(val) {
+			if (this.custom && val) {
+				this.getImage();
+			}
+		}
+	},
+	mounted() {
+		this.sysInfo = uni.getSystemInfoSync();
+		this.imgTop = this.sysInfo.windowHeight / 2;
+		this.imgLeft = this.sysInfo.windowWidth / 2;
+		this.CROPPER_WIDTH = this.width;
+		this.CROPPER_HEIGHT = this.height;
+		this.canvasHeight = this.height;
+		this.canvasWidth = this.width;
+		this.ctx = uni.createCanvasContext('tui-image-cropper', this);
+		this.setCutCenter();
+		//设置裁剪框大小>设置图片尺寸>绘制canvas
+		this.computeCutSize();
+		//检查裁剪框是否在范围内
+		this.cutDetectionPosition();
+		setTimeout(() => {
+			this.$emit('ready', {});
+		}, 200);
+	},
+	methods: {
+		//返回裁剪后图片信息
+		getImage() {
+			if (!this.imageUrl) {
+				uni.showToast({
+					title: '请选择图片',
+					icon: 'none'
+				});
+				return;
+			}
+			this.loadding && this.showLoading();
+			let draw = () => {
+				//图片实际大小
+				let imgWidth = this.imgWidth * this.scale * this.scaleRatio;
+				let imgHeight = this.imgHeight * this.scale * this.scaleRatio;
+				//canvas和图片的相对距离
+				let xpos = this.imgLeft - this.cutX;
+				let ypos = this.imgTop - this.cutY;
+				//旋转画布
+				this.ctx.translate(xpos * this.scaleRatio, ypos * this.scaleRatio);
+				this.ctx.rotate((this.angle * Math.PI) / 180);
+				this.ctx.drawImage(this.imageUrl, -imgWidth / 2, -imgHeight / 2, imgWidth, imgHeight);
+				this.ctx.draw(false, () => {
+					let params = {
+						width: this.canvasWidth * this.scaleRatio,
+						height: Math.round(this.canvasHeight * this.scaleRatio),
+						destWidth: this.canvasWidth * this.scaleRatio,
+						destHeight: Math.round(this.canvasHeight) * this.scaleRatio,
+						fileType: 'png',
+						quality: this.quality
+					};
+					let data = {
+						url: '',
+						base64: '',
+						width: this.canvasWidth * this.scaleRatio,
+						height: this.canvasHeight * this.scaleRatio
+					};
+					// #ifdef MP-ALIPAY
+
+					if (this.isBase64) {
+						this.ctx.toDataURL(params).then(dataURL => {
+							data.base64 = dataURL;
+							this.loadding && uni.hideLoading();
+							this.$emit('cropper', data);
+						});
+					} else {
+						this.ctx.toTempFilePath({
+							...params,
+							success: res => {
+								data.url = res.tempFilePath;
+								this.loadding && uni.hideLoading();
+								this.$emit('cropper', data);
+							}
+						});
+					}
+					// #endif
+
+					// #ifndef MP-ALIPAY
+					// #ifdef MP-BAIDU || MP-TOUTIAO || H5
+					this.isBase64 = false;
+					// #endif
+					if (this.isBase64) {
+						uni.canvasGetImageData({
+							canvasId: 'tui-image-cropper',
+							x: 0,
+							y: 0,
+							width: this.canvasWidth * this.scaleRatio,
+							height: Math.round(this.canvasHeight * this.scaleRatio),
+							success: res => {
+								const arrayBuffer = new Uint8Array(res.data);
+								const base64 = uni.arrayBufferToBase64(arrayBuffer);
+								data.base64 = base64;
+								this.loadding && uni.hideLoading();
+								this.$emit('cropper', data);
+							}
+						});
+					} else {
+						uni.canvasToTempFilePath(
+							{
+								...params,
+								canvasId: 'tui-image-cropper',
+								success: res => {
+									data.url = res.tempFilePath;
+									// #ifdef H5
+									data.base64 = res.tempFilePath;
+									// #endif
+									this.loadding && uni.hideLoading();
+									this.$emit('cropper', data);
+								},
+								fail(res) {
+									console.log(res);
+								}
+							},
+							this
+						);
+					}
+					// #endif
+				});
+			};
+			if (this.CROPPER_WIDTH != this.canvasWidth || this.CROPPER_HEIGHT != this.canvasHeight) {
+				this.CROPPER_WIDTH = this.canvasWidth;
+				this.CROPPER_HEIGHT = this.canvasHeight;
+				this.ctx.draw();
+				this.$nextTick(() => {
+					setTimeout(() => {
+						draw();
+					}, 100);
+				});
+			} else {
+				draw();
+			}
+		},
+		/**
+		 * 设置剪裁框和图片居中
+		 */
+		setCutCenter() {
+			let sys = this.sysInfo || uni.getSystemInfoSync();
+			let cutY = (sys.windowHeight - this.canvasHeight) * 0.5;
+			let cutX = (sys.windowWidth - this.canvasWidth) * 0.5;
+			//顺序不能变
+			this.imgTop = this.imgTop - this.cutY + cutY;
+			this.cutY = cutY; //截取的框上边距
+			this.imgLeft = this.imgLeft - this.cutX + cutX;
+			this.cutX = cutX; //截取的框左边距
+		},
+		imageReset() {
+			// this.cutAnimation = true;
+			this.scale = 1;
+			this.angle = 0;
+			let sys = this.sysInfo || uni.getSystemInfoSync();
+			this.imgTop = sys.windowHeight / 2;
+			this.imgLeft = sys.windowWidth / 2;
+		},
+		imageLoad(e) {
+			this.imageReset();
+			uni.hideLoading();
+			this.$emit('imageLoad', {});
+		},
+		//检测剪裁框位置是否在允许的范围内(屏幕内)
+		cutDetectionPosition() {
+			let cutDetectionPositionTop = () => {
+					//检测上边距是否在范围内
+					if (this.cutY < 0) {
+						this.cutY = 0;
+					}
+					if (this.cutY > this.sysInfo.windowHeight - this.canvasHeight) {
+						this.cutY = this.sysInfo.windowHeight - this.canvasHeight;
+					}
+				},
+				cutDetectionPositionLeft = () => {
+					//检测左边距是否在范围内
+					if (this.cutX < 0) {
+						this.cutX = 0;
+					}
+					if (this.cutX > this.sysInfo.windowWidth - this.canvasWidth) {
+						this.cutX = this.sysInfo.windowWidth - this.canvasWidth;
+					}
+				};
+			//裁剪框坐标处理(如果只写一个参数则另一个默认为0,都不写默认居中)
+			if (this.cutY == null && this.cutX == null) {
+				let cutY = (this.sysInfo.windowHeight - this.canvasHeight) * 0.5;
+				let cutX = (this.sysInfo.windowWidth - this.canvasWidth) * 0.5;
+				this.cutY = cutY; //截取的框上边距
+				this.cutX = cutX; //截取的框左边距
+			} else if (this.cutY != null && this.cutX != null) {
+				cutDetectionPositionTop();
+				cutDetectionPositionLeft();
+			} else if (this.cutY != null && this.cutX == null) {
+				cutDetectionPositionTop();
+				this.cutX = (this.sysInfo.windowWidth - this.canvasWidth) / 2;
+			} else if (this.cutY == null && this.cutX != null) {
+				cutDetectionPositionLeft();
+				this.cutY = (this.sysInfo.windowHeight - this.canvasHeight) / 2;
+			}
+		},
+		/**
+		 * 图片边缘检测-位置
+		 */
+		imgMarginDetectionPosition(scale) {
+			if (!this.limitMove) return;
+			let left = this.imgLeft;
+			let top = this.imgTop;
+			scale = scale || this.scale;
+			let imgWidth = this.imgWidth;
+			let imgHeight = this.imgHeight;
+			if ((this.angle / 90) % 2) {
+				imgWidth = this.imgHeight;
+				imgHeight = this.imgWidth;
+			}
+			left = this.cutX + (imgWidth * scale) / 2 >= left ? left : this.cutX + (imgWidth * scale) / 2;
+			left = this.cutX + this.canvasWidth - (imgWidth * scale) / 2 <= left ? left : this.cutX + this.canvasWidth - (imgWidth * scale) / 2;
+			top = this.cutY + (imgHeight * scale) / 2 >= top ? top : this.cutY + (imgHeight * scale) / 2;
+			top = this.cutY + this.canvasHeight - (imgHeight * scale) / 2 <= top ? top : this.cutY + this.canvasHeight - (imgHeight * scale) / 2;
+			this.imgLeft = left;
+			this.imgTop = top;
+			this.scale = scale;
+		},
+		/**
+		 * 图片边缘检测-缩放
+		 */
+		imgMarginDetectionScale(scale) {
+			if (!this.limitMove) return;
+			scale = scale || this.scale;
+			let imgWidth = this.imgWidth;
+			let imgHeight = this.imgHeight;
+			if ((this.angle / 90) % 2) {
+				imgWidth = this.imgHeight;
+				imgHeight = this.imgWidth;
+			}
+			if (imgWidth * scale < this.canvasWidth) {
+				scale = this.canvasWidth / imgWidth;
+			}
+			if (imgHeight * scale < this.canvasHeight) {
+				scale = Math.max(scale, this.canvasHeight / imgHeight);
+			}
+			this.imgMarginDetectionPosition(scale);
+		},
+		/**
+		 * 计算图片尺寸
+		 */
+		imgComputeSize(width, height) {
+			//默认按图片最小边 = 对应裁剪框尺寸
+			let imgWidth = width,
+				imgHeight = height;
+			if (imgWidth && imgHeight) {
+				if (imgWidth / imgHeight > (this.canvasWidth || this.width) / (this.canvasHeight || this.height)) {
+					imgHeight = this.canvasHeight || this.height;
+					imgWidth = (width / height) * imgHeight;
+				} else {
+					imgWidth = this.canvasWidth || this.width;
+					imgHeight = (height / width) * imgWidth;
+				}
+			} else {
+				let sys = this.sysInfo || uni.getSystemInfoSync();
+				imgWidth = sys.windowWidth;
+				imgHeight = 0;
+			}
+			this.imgWidth = imgWidth;
+			this.imgHeight = imgHeight;
+		},
+		//改变截取框大小
+		computeCutSize() {
+			if (this.canvasWidth > this.sysInfo.windowWidth) {
+				this.canvasWidth = this.sysInfo.windowWidth;
+			} else if (this.canvasWidth + this.cutX > this.sysInfo.windowWidth) {
+				this.cutX = this.sysInfo.windowWidth - this.cutX;
+			}
+			if (this.canvasHeight > this.sysInfo.windowHeight) {
+				this.canvasHeight = this.sysInfo.windowHeight;
+			} else if (this.canvasHeight + this.cutY > this.sysInfo.windowHeight) {
+				this.cutY = this.sysInfo.windowHeight - this.cutY;
+			}
+		},
+		//开始触摸
+		start(e) {
+			this.flagEndTouch = false;
+			if (e.touches.length == 1) {
+				//单指拖动
+				this.touchRelative[0] = {
+					x: e.touches[0].clientX - this.imgLeft,
+					y: e.touches[0].clientY - this.imgTop
+				};
+			} else {
+				//双指放大
+				let width = Math.abs(e.touches[0].clientX - e.touches[1].clientX);
+				let height = Math.abs(e.touches[0].clientY - e.touches[1].clientY);
+				this.touchRelative = [
+					{
+						x: e.touches[0].clientX - this.imgLeft,
+						y: e.touches[0].clientY - this.imgTop
+					},
+					{
+						x: e.touches[1].clientX - this.imgLeft,
+						y: e.touches[1].clientY - this.imgTop
+					}
+				];
+				this.hypotenuseLength = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2));
+			}
+		},
+		moveThrottle() {
+			if (this.sysInfo.platform == 'android') {
+				clearTimeout(this.MOVE_THROTTLE);
+				this.MOVE_THROTTLE = setTimeout(() => {
+					this.MOVE_THROTTLE_FLAG = true;
+				}, 800 / 40);
+				return this.MOVE_THROTTLE_FLAG;
+			} else {
+				this.MOVE_THROTTLE_FLAG = true;
+			}
+		},
+		move(e) {
+			if (this.flagEndTouch || !this.MOVE_THROTTLE_FLAG) return;
+			this.MOVE_THROTTLE_FLAG = false;
+			this.moveThrottle();
+			this.moveDuring();
+			if (e.touches.length == 1) {
+				//单指拖动
+				let left = e.touches[0].clientX - this.touchRelative[0].x,
+					top = e.touches[0].clientY - this.touchRelative[0].y;
+				//图像边缘检测,防止截取到空白
+				this.imgLeft = left;
+				this.imgTop = top;
+				this.imgMarginDetectionPosition();
+			} else {
+				//双指放大
+				let width = Math.abs(e.touches[0].clientX - e.touches[1].clientX),
+					height = Math.abs(e.touches[0].clientY - e.touches[1].clientY),
+					hypotenuse = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2)),
+					scale = this.scale * (hypotenuse / this.hypotenuseLength),
+					current_deg = 0;
+				scale = scale <= this.minScale ? this.minScale : scale;
+				scale = scale >= this.maxScale ? this.maxScale : scale;
+				//图像边缘检测,防止截取到空白
+				// this.scale = scale;
+				this.imgMarginDetectionScale(scale);
+				//双指旋转(如果没禁用旋转)
+				let touchRelative = [
+					{
+						x: e.touches[0].clientX - this.imgLeft,
+						y: e.touches[0].clientY - this.imgTop
+					},
+					{
+						x: e.touches[1].clientX - this.imgLeft,
+						y: e.touches[1].clientY - this.imgTop
+					}
+				];
+				if (!this.disableRotate) {
+					let first_atan = (180 / Math.PI) * Math.atan2(touchRelative[0].y, touchRelative[0].x);
+					let first_atan_old = (180 / Math.PI) * Math.atan2(this.touchRelative[0].y, this.touchRelative[0].x);
+					let second_atan = (180 / Math.PI) * Math.atan2(touchRelative[1].y, touchRelative[1].x);
+					let second_atan_old = (180 / Math.PI) * Math.atan2(this.touchRelative[1].y, this.touchRelative[1].x);
+					//当前旋转的角度
+					let first_deg = first_atan - first_atan_old,
+						second_deg = second_atan - second_atan_old;
+					if (first_deg != 0) {
+						current_deg = first_deg;
+					} else if (second_deg != 0) {
+						current_deg = second_deg;
+					}
+				}
+				this.touchRelative = touchRelative;
+				this.hypotenuseLength = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2));
+				//更新视图
+				this.angle = this.angle + current_deg;
+				this.scale = this.scale;
+			}
+		},
+		//结束操作
+		end(e) {
+			this.flagEndTouch = true;
+			this.moveStop();
+		},
+		//裁剪框处理
+		cutTouchMove(e) {
+			if (this.flagCutTouch && this.MOVE_THROTTLE_FLAG) {
+				if (this.lockRatio && (this.lockWidth || this.lockHeight)) return;
+				//节流
+				this.MOVE_THROTTLE_FLAG = false;
+				this.moveThrottle();
+				let width = this.canvasWidth,
+					height = this.canvasHeight,
+					cutY = this.cutY,
+					cutX = this.cutX,
+					size_correct = () => {
+						width = width <= this.maxWidth ? (width >= this.minWidth ? width : this.minWidth) : this.maxWidth;
+						height = height <= this.maxHeight ? (height >= this.minHeight ? height : this.minHeight) : this.maxHeight;
+					},
+					size_inspect = () => {
+						if ((width > this.maxWidth || width < this.minWidth || height > this.maxHeight || height < this.minHeight) && this.lockRatio) {
+							size_correct();
+							return false;
+						} else {
+							size_correct();
+							return true;
+						}
+					};
+				height = this.CUT_START.height + (this.CUT_START.corner > 1 && this.CUT_START.corner < 4 ? 1 : -1) * (this.CUT_START.y - e.touches[0].clientY);
+				switch (this.CUT_START.corner) {
+					case 1:
+						width = this.CUT_START.width - this.CUT_START.x + e.touches[0].clientX;
+						if (this.lockRatio) {
+							height = width / (this.canvasWidth / this.canvasHeight);
+						}
+						if (!size_inspect()) return;
+						break;
+					case 2:
+						width = this.CUT_START.width - this.CUT_START.x + e.touches[0].clientX;
+						if (this.lockRatio) {
+							height = width / (this.canvasWidth / this.canvasHeight);
+						}
+						if (!size_inspect()) return;
+						cutY = this.CUT_START.cutY - (height - this.CUT_START.height);
+						break;
+					case 3:
+						width = this.CUT_START.width + this.CUT_START.x - e.touches[0].clientX;
+						if (this.lockRatio) {
+							height = width / (this.canvasWidth / this.canvasHeight);
+						}
+						if (!size_inspect()) return;
+						cutY = this.CUT_START.cutY - (height - this.CUT_START.height);
+						cutX = this.CUT_START.cutX - (width - this.CUT_START.width);
+						break;
+					case 4:
+						width = this.CUT_START.width + this.CUT_START.x - e.touches[0].clientX;
+						if (this.lockRatio) {
+							height = width / (this.canvasWidth / this.canvasHeight);
+						}
+						if (!size_inspect()) return;
+						cutX = this.CUT_START.cutX - (width - this.CUT_START.width);
+						break;
+					default:
+						break;
+				}
+				if (!this.lockWidth && !this.lockHeight) {
+					this.canvasWidth = width;
+					this.cutX = cutX;
+					this.canvasHeight = height;
+					this.cutY = cutY;
+				} else if (!this.lockWidth) {
+					this.canvasWidth = width;
+					this.cutX = cutX;
+				} else if (!this.lockHeight) {
+					this.canvasHeight = height;
+					this.cutY = cutY;
+				}
+				this.imgMarginDetectionScale();
+			}
+		},
+		cutTouchStart(e) {
+			let currentX = e.touches[0].clientX;
+			let currentY = e.touches[0].clientY;
+
+			/*
+			 * (右下-1 右上-2 左上-3 左下-4)
+			 * left_x [3,4]
+			 * top_y [2,3]
+			 * right_x [1,2]
+			 * bottom_y [1,4]
+			 */
+			let left_x1 = this.cutX - 24;
+			let left_x2 = this.cutX + 24;
+
+			let top_y1 = this.cutY - 24;
+			let top_y2 = this.cutY + 24;
+
+			let right_x1 = this.cutX + this.canvasWidth - 24;
+			let right_x2 = this.cutX + this.canvasWidth + 24;
+
+			let bottom_y1 = this.cutY + this.canvasHeight - 24;
+			let bottom_y2 = this.cutY + this.canvasHeight + 24;
+
+			if (currentX > right_x1 && currentX < right_x2 && currentY > bottom_y1 && currentY < bottom_y2) {
+				this.moveDuring();
+				this.flagCutTouch = true;
+				this.flagEndTouch = true;
+				this.CUT_START = {
+					width: this.canvasWidth,
+					height: this.canvasHeight,
+					x: currentX,
+					y: currentY,
+					corner: 1
+				};
+			} else if (currentX > right_x1 && currentX < right_x2 && currentY > top_y1 && currentY < top_y2) {
+				this.moveDuring();
+				this.flagCutTouch = true;
+				this.flagEndTouch = true;
+				this.CUT_START = {
+					width: this.canvasWidth,
+					height: this.canvasHeight,
+					x: currentX,
+					y: currentY,
+					cutY: this.cutY,
+					cutX: this.cutX,
+					corner: 2
+				};
+			} else if (currentX > left_x1 && currentX < left_x2 && currentY > top_y1 && currentY < top_y2) {
+				this.moveDuring();
+				this.flagCutTouch = true;
+				this.flagEndTouch = true;
+				this.CUT_START = {
+					width: this.canvasWidth,
+					height: this.canvasHeight,
+					cutY: this.cutY,
+					cutX: this.cutX,
+					x: currentX,
+					y: currentY,
+					corner: 3
+				};
+			} else if (currentX > left_x1 && currentX < left_x2 && currentY > bottom_y1 && currentY < bottom_y2) {
+				this.moveDuring();
+				this.flagCutTouch = true;
+				this.flagEndTouch = true;
+				this.CUT_START = {
+					width: this.canvasWidth,
+					height: this.canvasHeight,
+					cutY: this.cutY,
+					cutX: this.cutX,
+					x: currentX,
+					y: currentY,
+					corner: 4
+				};
+			}
+		},
+		cutTouchEnd(e) {
+			this.moveStop();
+			this.flagCutTouch = false;
+		},
+		//停止移动时需要做的操作
+		moveStop() {
+			//清空之前的自动居中延迟函数并添加最新的
+			clearTimeout(this.TIME_CUT_CENTER);
+			this.TIME_CUT_CENTER = setTimeout(() => {
+				//动画启动
+				if (!this.cutAnimation) {
+					this.cutAnimation = true;
+				}
+				this.setCutCenter();
+			}, 800);
+		},
+		//移动中
+		moveDuring() {
+			//清空之前的自动居中延迟函数
+			clearTimeout(this.TIME_CUT_CENTER);
+		},
+		showLoading() {
+			uni.showLoading({
+				title: '请稍候...',
+				mask: true
+			});
+		},
+		stop() {},
+		back() {
+			uni.navigateBack();
+		},
+		setAngle() {
+			this.cutAnimation = true;
+			this.angle = this.angle + 90;
+		}
+	}
+};
+</script>
+
+<style scoped>
+.tui-container {
+	width: 100vw;
+	height: 100vh;
+	background-color: rgba(0, 0, 0, 0.6);
+	position: fixed;
+	top: 0;
+	left: 0;
+	z-index: 1;
+}
+
+.tui-image-cropper {
+	width: 100vw;
+	height: 100vh;
+	position: absolute;
+}
+
+.tui-content {
+	width: 100vw;
+	height: 100vh;
+	position: absolute;
+	z-index: 9;
+	display: flex;
+	flex-direction: column;
+	pointer-events: none;
+}
+
+.tui-bg-transparent {
+	background-color: rgba(0, 0, 0, 0.6);
+	transition-duration: 0.35s;
+}
+
+.tui-content-top {
+	pointer-events: none;
+}
+
+.tui-content-middle {
+	width: 100%;
+	height: 200px;
+	display: flex;
+	box-sizing: border-box;
+}
+
+.tui-cropper-box {
+	position: relative;
+	/* transition-duration: 0.3s; */
+	border-style: solid;
+	border-width: 1rpx;
+	box-sizing: border-box;
+}
+
+.tui-flex-auto {
+	flex: auto;
+}
+
+.tui-cropper-image {
+	width: 100%;
+	border-style: none;
+	position: absolute;
+	top: 0;
+	left: 0;
+	z-index: 2;
+	-webkit-backface-visibility: hidden;
+	backface-visibility: hidden;
+	transform-origin: center;
+}
+
+.tui-cropper-canvas {
+	position: fixed;
+	z-index: 10;
+	left: -2000px;
+	top: -2000px;
+	pointer-events: none;
+}
+
+.tui-edge {
+	border-style: solid;
+	pointer-events: auto;
+	position: absolute;
+	box-sizing: border-box;
+}
+
+.tui-top-left {
+	border-bottom-width: 0 !important;
+	border-right-width: 0 !important;
+}
+
+.tui-top-right {
+	border-bottom-width: 0 !important;
+	border-left-width: 0 !important;
+}
+
+.tui-bottom-left {
+	border-top-width: 0 !important;
+	border-right-width: 0 !important;
+}
+
+.tui-bottom-right {
+	border-top-width: 0 !important;
+	border-left-width: 0 !important;
+}
+
+.tui-cropper-tabbar {
+	width: 100%;
+	height: 120rpx;
+	padding: 0 40rpx;
+	box-sizing: border-box;
+	position: fixed;
+	left: 0;
+	bottom: 0;
+	z-index: 99;
+	display: flex;
+	align-items: center;
+	justify-content: space-between;
+	color: #ffffff;
+	font-size: 32rpx;
+}
+
+.tui-cropper-tabbar::after {
+	content: ' ';
+	position: absolute;
+	top: 0;
+	right: 0;
+	left: 0;
+	border-top: 1rpx solid rgba(255, 255, 255, 0.2);
+	-webkit-transform: scaleY(0.5) translateZ(0);
+	transform: scaleY(0.5) translateZ(0);
+	transform-origin: 0 100%;
+}
+
+.tui-op-btn {
+	height: 80rpx;
+	display: flex;
+	align-items: center;
+}
+
+.tui-rotate-img {
+	width: 44rpx;
+	height: 44rpx;
+}
+</style>

+ 149 - 0
components/thorui/tui-image-group/tui-image-group.vue

@@ -0,0 +1,149 @@
+<template>
+	<view class="tui-image-container" :class="{ 'tui-image-direction': direction == 'column' }">
+		<view
+			v-for="(item, index) in imageList"
+			:key="index"
+			class="tui-image-item_box"
+			:style="{
+				width: width,
+				height: height,
+				borderRadius: radius,
+				marginLeft: direction == 'column' ? 0 : (index && distance) + 'rpx',
+				marginTop: direction == 'row' ? 0 : (index && distance) + 'rpx'
+			}"
+			@tap="bindClick(index, item.id)"
+		>
+			<image
+				class="tui-image-item"
+				:mode="mode"
+				:lazy-load="lazyLoad"
+				fade-show="fadeShow"
+				:webp="webp"
+				:show-menu-by-longpress="longpress"
+				@error="error"
+				@load="load"
+				:style="{ width: width, height: height, borderRadius: radius, borderWidth: borderWidth, borderColor: borderColor }"
+				:src="item.src"
+			></image>
+			<slot />
+		</view>
+	</view>
+</template>
+
+<script>
+export default {
+	name: 'tuiImageGroup',
+	props: {
+		//图片集合
+		/*
+		  [{id:1,src:"1.png"}]
+		*/
+		imageList: {
+			type: Array,
+			default: () => {
+				return [];
+			}
+		},
+		//图片宽度
+		width: {
+			type: String,
+			default: '120rpx'
+		},
+		//图片高度
+		height: {
+			type: String,
+			default: '120rpx'
+		},
+		//图片边框宽度 rpx
+		borderWidth: {
+			type: String,
+			default: '0'
+		},
+		//图片边框颜色 可传rgba
+		borderColor: {
+			type: String,
+			default: '#fff'
+		},
+		//图片圆角
+		radius: {
+			type: String,
+			default: '50%'
+		},
+		//图片裁剪、缩放的模式
+		mode: {
+			type: String,
+			default: 'scaleToFill'
+		},
+		//图片懒加载。只针对page与scroll-view下的image有效
+		lazyLoad: {
+			type: Boolean,
+			default: true
+		},
+		//图片显示动画效果 | 仅App-nvue 2.3.4+ Android有效
+		fadeShow: {
+			type: Boolean,
+			default: true
+		},
+		//默认不解析 webP 格式,只支持网络资源 | 微信小程序2.9.0
+		webp: {
+			type: Boolean,
+			default: false
+		},
+		//开启长按图片显示识别小程序码菜单 | 微信小程序2.7.0
+		longpress: {
+			type: Boolean,
+			default: false
+		},
+		//是否组合排列
+		isGroup: {
+			type: Boolean,
+			default: false
+		},
+		//排列方向 row ,column
+		direction: {
+			type: String,
+			default: 'row'
+		},
+		//偏移距离 rpx
+		distance: {
+			type: [Number, String],
+			default: -16
+		}
+	},
+	data() {
+		return {};
+	},
+	methods: {
+		error(e) {
+			this.$emit('errorEvent', e);
+		},
+		load(e) {
+			this.$emit('loaded', e);
+		},
+		bindClick(index, id) {
+			this.$emit('click', {
+				index: index,
+				id: id || ''
+			});
+		}
+	}
+};
+</script>
+
+<style scoped>
+.tui-image-container {
+	display: inline-flex;
+	align-items: center;
+}
+.tui-image-direction {
+	flex-direction: column;
+}
+.tui-image-item_box {
+	position: relative;
+}
+.tui-image-item {
+	border-style: solid;
+	flex-shrink: 0;
+	display: block;
+}
+</style>

+ 73 - 0
components/thorui/tui-keyboard-input/tui-keyboard-input.vue

@@ -0,0 +1,73 @@
+<template>
+	<view class="tui-keyboard-input tui-pwd-box" :style="{backgroundColor:backgroundColor}">
+		<view class="tui-inner-box">
+			<view class="tui-input" :class="[inputvalue.length===4?'tui-margin-right':'']" :style="{fontSize:size+'rpx',color:color,width:(inputvalue.length===4?90:70)+'rpx' }"
+			 v-for="(item,index) in inputvalue" :key="index">{{item}}</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		name: "tuiKeyboardInput",
+		props: {
+			//背景颜色
+			backgroundColor: {
+				type: String,
+				default: "#fff"
+			},
+			size: {
+				type: Number,
+				default: 32
+			},
+			color: {
+				type: String,
+				default: "#333"
+			},
+			//输入框的值:数组格式,长度即为输入框个数
+			inputvalue: {
+				type: Array,
+				default: ["", "", "", "", "", ""] //密码圆点 ●
+			}
+		},
+		data() {
+			return {
+
+			};
+		}
+	}
+</script>
+
+<style scoped>
+	.tui-pwd-box {
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		box-sizing: border-box;
+		vertical-align: top;
+	}
+
+	.tui-inner-box {
+		display: flex;
+		align-items: center;
+		justify-content: center;
+	}
+
+	.tui-input {
+		height: 80rpx;
+		position: relative;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		margin-right: 20rpx;
+		border-bottom: 2px solid #666;
+	}
+
+	.tui-margin-right {
+		margin-right: 30rpx;
+	}
+
+	.tui-input:last-child {
+		margin-right: 0 !important;
+	}
+</style>

+ 240 - 0
components/thorui/tui-keyboard/tui-keyboard.vue

@@ -0,0 +1,240 @@
+<template>
+	<view>
+		<view class="tui-keyboard-mask" :class="[show?'tui-mask-show':'']" v-if="mask" @tap="handleClose"></view>
+		<view class="tui-keyboard" :class="{'tui-keyboard-radius':radius,'tui-keyboard-action':action,'tui-keyboard-show':show}">
+			<slot></slot>
+			<view class="tui-keyboard-grids">
+				<!--{{(index==9 || index==10 || index==11)?'tui-grid-bottom':''}}-->
+				<view class="tui-keyboard-grid" :class="{'tui-bg-gray':index==9 || index==11}" v-for="(item,index) in itemList"
+				 :key="index" hover-class="tui-keyboard-hover" :hover-stay-time="150" @tap="handleClick" :data-index="index">
+					<view v-if="index<11" class="tui-keyboard-item" :class="{'tui-fontsize-32':index==9}">{{getKeyBoard(index,action)}}</view>
+					<view v-else class="tui-keyboard-item">
+						<view class="tui-icon tui-keyboard-delete"></view>
+					</view>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		name: "tuiKeyboard",
+		props: {
+			//是否需要mask
+			mask: {
+				type: Boolean,
+				default: true
+			},
+			//控制键盘显示
+			show: {
+				type: Boolean,
+				default: false
+			},
+			//是否直接显示,不需要动画,一般使用在锁屏密码
+			action: {
+				type: Boolean,
+				default: true
+			},
+			//是否带圆角
+			radius: {
+				type: Boolean,
+				default: false
+			}
+		},
+		data() {
+			return {
+				itemList: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
+			};
+		},
+		methods: {
+			getKeyBoard: function(index, action) {
+				var content = index + 1;
+				if (index == 9) {
+					content = action ? "取消" : "清除";
+				} else if (index == 10) {
+					content = 0;
+				}
+				return content;
+			},
+			//关闭
+			handleClose() {
+				if (!this.show) {
+					return;
+				}
+				this.$emit('close', {});
+			},
+			handleClick(e) {
+				if (!this.show) {
+					return;
+				}
+				const dataset = e.currentTarget.dataset;
+				this.$emit('click', {
+					index: Number(dataset.index)
+				})
+			}
+		}
+	}
+</script>
+
+<style scoped>
+	@font-face {
+		font-family: 'keyboardFont';
+		src: url(data:application/font-woff;charset=utf-8;base64,d09GRgABAAAAAASgAA0AAAAABugAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABGRlRNAAAEhAAAABoAAAAch/nJvUdERUYAAARkAAAAHgAAAB4AKQAKT1MvMgAAAZwAAABDAAAAVj4mSapjbWFwAAAB8AAAAD4AAAFCAA/rY2dhc3AAAARcAAAACAAAAAj//wADZ2x5ZgAAAjwAAACsAAAA0BLVU2FoZWFkAAABMAAAAC0AAAA2FXPmsWhoZWEAAAFgAAAAHAAAACQH3gOFaG10eAAAAeAAAAAOAAAAEAwAAABsb2NhAAACMAAAAAoAAAAKAGgAAG1heHAAAAF8AAAAHwAAACABEQBLbmFtZQAAAugAAAFJAAACiCnmEVVwb3N0AAAENAAAACgAAAA6nLlLs3jaY2BkYGAAYukqK754fpuvDNwsDCBwU+tiFBKtwMLA9ABIczAwgUQB4ccH+gAAAHjaY2BkYGBu+N/AEMPCAAJAkpEBFbAAAEcKAm142mNgZGBgYGGwZ2BmAAEmIOYCQgaG/2A+AwAPIgFdAHjaY2BkYWCcwMDKwMDUyXSGgYGhH0IzvmYwYuQAijKwMjNgBQFprikMDs93PN/B3PC/gSGGuYGhASjMCJIDAPenDU0AeNpjYYAAFigGAACAAA0AAHjaY2BgYGaAYBkGRgYQsAHyGMF8FgYFIM0ChED+8x3//0NICW+oSgZGNgYYk4GRCUgwMaACRoZhDwAItAhZAAAAAAAAAAAAAABoAAB42l3MTQqCUBSG4fNpqBxECS/+YFTXRGcFKteZjW0nuoqWVtOgPbgKZ1cqaBDN3snzkklE+xUZEwUkqSOCzGx4EGGEsJYd2vURgQdbomhayC0iu8h8lEVmiR1sS4TVGVFYqeaEVjXmVT8TsWjf83yYIjFq1QM9I0/1c9HMMI06zfHgmMeRY8HDwOKnjSlYZvdQ5u4yB+gVbqrX97cAOxsHn9GF/9G3iV4WbSWBeNp9kD1OAzEQhZ/zByQSQiCoXVEA2vyUKRMp9Ailo0g23pBo1155nUg5AS0VB6DlGByAGyDRcgpelkmTImvt6PObmeexAZzjGwr/3yXuhBWO8ShcwREy4Sr1F+Ea+V24jhY+hRvUf4SbuFUD4RYu1BsdVO2Eu5vSbcsKZxgIV3CKJ+Eq9ZVwjfwqXMcVPoQb1L+EmxjjV7iFa2WpDOFhMEFgnEFjig3jAjEcLJIyBtahOfRmEsxMTzd6ETubOBso71dilwMeaDnngCntPbdmvkon/mDLgdSYbh4FS7YpjS4idCgbXyyc1d2oc7D9nu22tNi/a4E1x+xRDWzU/D3bM9JIbAyvkJI18jK3pBJTj2hrrPG7ZynW814IiU68y/SIx5o0dTr3bmniwOLn8owcfbS5kj33qBw+Y1kIeb/dTsQgil2GP5PYcRkAAAB42mNgYoAALjDJyIAOWMCiTIxM/FmZiXkFiXnxxRmJeckZpQA1nQZRAAAAAf//AAIAAQAAAAwAAAAWAAAAAgABAAMAAwABAAQAAAACAAAAAHjaY2BgYGQAgqtL1DlA9E2ti1EwGgA9dwYGAAA=) format('woff');
+		font-weight: normal;
+		font-style: normal;
+	}
+
+	.tui-icon {
+		font-family: "keyboardFont" !important;
+		font-size: 22px;
+		font-style: normal;
+		-webkit-font-smoothing: antialiased;
+		-moz-osx-font-smoothing: grayscale;
+		line-height: 1;
+		color: #333;
+	}
+
+	.tui-keyboard-delete:before {
+		content: "\e7b8";
+	}
+
+	.tui-keyboard-mask {
+		position: fixed;
+		top: 0;
+		left: 0;
+		right: 0;
+		bottom: 0;
+		background-color: rgba(0, 0, 0, 0.6);
+		z-index: 998;
+		transition: all 0.3s ease-in-out;
+		opacity: 0;
+		visibility: hidden;
+	}
+
+	.tui-mask-show {
+		opacity: 1;
+		visibility: visible;
+	}
+
+	.tui-keyboard {
+		width: 100%;
+		position: fixed;
+		left: 0;
+		right: 0;
+		bottom: 0;
+		z-index: 999;
+		padding-bottom: env(safe-area-inset-bottom);
+		background-color: #fff;
+	}
+
+	.tui-keyboard-radius {
+		border-top-left-radius: 16rpx;
+		border-top-right-radius: 16rpx;
+		overflow: hidden;
+	}
+
+	.tui-keyboard-action {
+		visibility: hidden;
+		transform: translate3d(0, 100%, 0);
+		transform-origin: center;
+		transition: all 0.3s ease-in-out;
+	}
+
+	.tui-keyboard-show {
+		transform: translate3d(0, 0, 0);
+		visibility: visible;
+	}
+
+	.tui-bg-gray {
+		background-color: #e7e6eb !important;
+	}
+
+	.tui-keyboard-grids {
+		width: 100%;
+		position: relative;
+		overflow: hidden;
+		display: flex;
+		display: -webkit-flex;
+		flex-direction: row;
+		flex-wrap: wrap;
+	}
+
+	.tui-keyboard-grids::after {
+		content: " ";
+		position: absolute;
+		left: 0;
+		top: 0;
+		width: 100%;
+		height: 1px;
+		border-top: 1px solid #eaeef1;
+		-webkit-transform-origin: 0 0;
+		transform-origin: 0 0;
+		-webkit-transform: scaleY(0.5);
+		transform: scaleY(0.5);
+	}
+
+	.tui-keyboard-grid {
+		position: relative;
+		padding: 24rpx 20rpx;
+		box-sizing: border-box;
+		background-color: #fff;
+		width: 33.33333333%;
+	}
+
+	.tui-keyboard-grid:nth-of-type(3n)::before {
+		width: 0;
+		border-right: 0;
+	}
+
+	.tui-keyboard-grid::before {
+		content: " ";
+		position: absolute;
+		right: 0;
+		top: 0;
+		width: 1px;
+		bottom: 0;
+		border-right: 1px solid #eaeef1;
+		-webkit-transform-origin: 100% 0;
+		transform-origin: 100% 0;
+		-webkit-transform: scaleX(0.5);
+		transform: scaleX(0.5);
+	}
+
+	.tui-keyboard-grid::after {
+		content: " ";
+		position: absolute;
+		left: 0;
+		bottom: 0;
+		right: 0;
+		height: 1px;
+		border-bottom: 1px solid #eaeef1;
+		-webkit-transform-origin: 0 100%;
+		transform-origin: 0 100%;
+		-webkit-transform: scaleY(0.5);
+		transform: scaleY(0.5);
+	}
+
+	.tui-grid-bottom::after {
+		height: 0 !important;
+		border-bottom: 0 !important;
+	}
+
+	.tui-keyboard-hover {
+		background-color: #f7f7f9 !important;
+	}
+
+	.tui-keyboard-item {
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		font-size: 48rpx;
+		height: 60rpx;
+		color: #000;
+	}
+
+	.tui-fontsize-32 {
+		font-size: 32rpx;
+		color: #333 !important;
+	}
+</style>

+ 171 - 0
components/thorui/tui-list-cell/tui-list-cell.vue

@@ -0,0 +1,171 @@
+<template>
+	<view
+		class="tui-list-class tui-list-cell"
+		:class="[
+			arrow ? 'tui-cell-arrow' : '',
+			arrow && arrowRight ? '' : 'tui-arrow-right',
+			unlined ? 'tui-cell-unlined' : '',
+			lineLeft ? 'tui-line-left' : '',
+			lineRight ? 'tui-line-right' : '',
+			arrow && arrowColor ? 'tui-arrow-' + arrowColor : '',
+			radius ? 'tui-radius' : ''
+		]"
+		:hover-class="hover ? 'tui-cell-hover' : ''"
+		:style="{ backgroundColor: backgroundColor, fontSize: size + 'rpx', color: color, padding: padding }"
+		:hover-stay-time="150"
+		@tap="handleClick"
+	>
+		<slot></slot>
+	</view>
+</template>
+
+<script>
+export default {
+	name: 'tuiListCell',
+	props: {
+		//是否有箭头
+		arrow: {
+			type: Boolean,
+			default: false
+		},
+		//箭头颜色 传值: white,gray,warning,danger
+		arrowColor: {
+			type: String,
+			default: ''
+		},
+		//是否有点击效果
+		hover: {
+			type: Boolean,
+			default: true
+		},
+		//隐藏线条
+		unlined: {
+			type: Boolean,
+			default: false
+		},
+		//线条是否有左偏移距离
+		lineLeft: {
+			type: Boolean,
+			default: true
+		},
+		//线条是否有右偏移距离
+		lineRight: {
+			type: Boolean,
+			default: false
+		},
+		padding: {
+			type: String,
+			default: '26rpx 30rpx'
+		},
+		//背景颜色
+		backgroundColor: {
+			type: String,
+			default: '#fff'
+		},
+		//字体大小
+		size: {
+			type: Number,
+			default: 28
+		},
+		//字体颜色
+		color: {
+			type: String,
+			default: '#333'
+		},
+		//是否加圆角
+		radius: {
+			type: Boolean,
+			default: false
+		},
+		//箭头是否有偏移距离
+		arrowRight: {
+			type: Boolean,
+			default: true
+		},
+		index: {
+			type: Number,
+			default: 0
+		}
+	},
+	methods: {
+		handleClick() {
+			this.$emit('click', {
+				index: this.index
+			});
+		}
+	}
+};
+</script>
+
+<style scoped>
+.tui-list-cell {
+	position: relative;
+	width: 100%;
+	box-sizing: border-box;
+}
+.tui-radius {
+	border-radius: 6rpx;
+	overflow: hidden;
+}
+
+.tui-cell-hover {
+	background-color: #f1f1f1 !important;
+}
+
+.tui-list-cell::after {
+	content: '';
+	position: absolute;
+	border-bottom: 1px solid #eaeef1;
+	-webkit-transform: scaleY(0.5) translateZ(0);
+	transform: scaleY(0.5) translateZ(0);
+	transform-origin: 0 100%;
+	bottom: 0;
+	right: 0;
+	left: 0;
+}
+
+.tui-line-left::after {
+	left: 30rpx !important;
+}
+
+.tui-line-right::after {
+	right: 30rpx !important;
+}
+
+.tui-cell-unlined::after {
+	border-bottom: 0 !important;
+}
+
+.tui-cell-arrow::before {
+	content: ' ';
+	height: 10px;
+	width: 10px;
+	border-width: 2px 2px 0 0;
+	border-color: #c0c0c0;
+	border-style: solid;
+	-webkit-transform: matrix(0.5, 0.5, -0.5, 0.5, 0, 0);
+	transform: matrix(0.5, 0.5, -0.5, 0.5, 0, 0);
+	position: absolute;
+	top: 50%;
+	margin-top: -6px;
+	right: 30rpx;
+}
+.tui-arrow-right::before {
+	right: 0 !important;
+}
+.tui-arrow-gray::before {
+	border-color: #666666 !important;
+}
+.tui-arrow-white::before {
+	border-color: #ffffff !important;
+}
+.tui-arrow-warning::before {
+	border-color: #ff7900 !important;
+}
+.tui-arrow-success::before {
+	border-color: #19be6b !important;
+}
+.tui-arrow-danger::before {
+	border-color: #eb0909 !important;
+}
+</style>

+ 87 - 0
components/thorui/tui-list-view/tui-list-view.vue

@@ -0,0 +1,87 @@
+<template>
+	<view class="tui-list-view tui-view-class" :style="{backgroundColor:backgroundColor}">
+		<view class="tui-list-title" v-if="title">{{title}}</view>
+		<view class="tui-list-content" :class="[unlined?'tui-border-'+unlined:'']">
+			<slot></slot>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		name: "tuiListView",
+		props: {
+			title: {
+				type: String,
+				default: ''
+			},
+			backgroundColor:{
+				type: String,
+				default: 'transparent'
+			},
+			unlined: {
+				type: String,
+				default: '' //top,bottom,all
+			}
+		}
+	}
+</script>
+
+<style scoped>
+	.tui-list-title {
+		width: 100%;
+		padding: 30rpx;
+		box-sizing: border-box;
+		font-size: 30rpx;
+		line-height: 30rpx;
+		color: #666;
+	}
+
+	.tui-list-content {
+		width: 100%;
+		position: relative;
+	}
+
+	.tui-list-content::before {
+		content: " ";
+		position: absolute;
+		top: 0;
+		right: 0;
+		left: 0;
+		border-top: 1rpx solid #eaeef1;
+		-webkit-transform: scaleY(0.5) translateZ(0);
+		transform: scaleY(0.5) translateZ(0);
+		transform-origin: 0 0;
+		z-index: 2;
+		pointer-events: none;
+	}
+
+	.tui-list-content::after {
+		content: '';
+		width: 100%;
+		position: absolute;
+		border-bottom: 1rpx solid #eaeef1;
+		-webkit-transform: scaleY(0.5) translateZ(0);
+		transform: scaleY(0.5) translateZ(0);
+		transform-origin: 0 100%;
+		bottom: 0;
+		right: 0;
+		left: 0;
+	}
+
+	.tui-border-top::before {
+		border-top: 0;
+	}
+
+	.tui-border-bottom::after {
+		border-bottom: 0;
+	}
+
+	.tui-border-all::after {
+		border-bottom: 0;
+	}
+
+	.tui-border-all::before {
+		border-top: 0;
+	}
+</style>

+ 78 - 0
components/thorui/tui-loading/tui-loading.vue

@@ -0,0 +1,78 @@
+<template>
+	<view class="tui-loading-init">
+		<view class="tui-loading-center"></view>
+		<view class="tui-loadmore-tips">{{text}}</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		name: "tuiLoading",
+		props: {
+			text: {
+				type: String,
+				default: "正在加载..."
+			}
+		}
+	}
+</script>
+
+<style scoped>
+	.tui-loading-init {
+		min-width: 200rpx;
+		min-height: 200rpx;
+		max-width: 500rpx;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		flex-direction: column;
+		position: fixed;
+		top: 50%;
+		left: 50%;
+		transform: translate(-50%, -50%);
+		z-index: 9999;
+		font-size: 26rpx;
+		color: #fff;
+		background-color: rgba(0, 0, 0, 0.7);
+		border-radius: 10rpx;
+	}
+
+	.tui-loading-center {
+		width: 50rpx;
+		height: 50rpx;
+		border: 3px solid #fff;
+		border-radius: 50%;
+		margin: 0 6px;
+		display: inline-block;
+		vertical-align: middle;
+		clip-path: polygon(0% 0%, 100% 0%, 100% 40%, 0% 40%);
+		animation: rotate 1s linear infinite;
+		margin-bottom: 36rpx;
+	}
+
+	.tui-loadmore-tips {
+		text-align: center;
+		padding: 0 20rpx;
+		box-sizing: border-box;
+	}
+
+	@-webkit-keyframes rotate {
+		from {
+			transform: rotatez(0deg);
+		}
+
+		to {
+			transform: rotatez(360deg);
+		}
+	}
+
+	@keyframes rotate {
+		from {
+			transform: rotatez(0deg);
+		}
+
+		to {
+			transform: rotatez(360deg);
+		}
+	}
+</style>

+ 161 - 0
components/thorui/tui-loadmore/tui-loadmore.vue

@@ -0,0 +1,161 @@
+<template>
+	<view class="tui-loadmore">
+		<view :class="['tui-loading-'+index, (index==3 && type)?'tui-loading-'+type:'']"></view>
+		<view class="tui-loadmore-tips">{{text}}</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		name: "tuiLoadmore",
+		props: {
+			//显示文本
+			text: {
+				type: String,
+				default: "正在加载..."
+			},
+			//loading 样式 :1,2,3
+			index: {
+				type: Number,
+				default: 1
+			},
+			//颜色设置,只有index=3时生效:primary,red,orange,green
+			type: {
+				type: String,
+				default: ""
+			}
+		}
+	}
+</script>
+
+<style scoped>
+	.tui-loadmore {
+		width: 48%;
+		margin: 1.5em auto;
+		line-height: 1.5em;
+		font-size: 24rpx;
+		text-align: center;
+	}
+
+	.tui-loading-1 {
+		margin: 0 5px;
+		width: 20px;
+		height: 20px;
+		display: inline-block;
+		vertical-align: middle;
+		-webkit-animation: a 1s steps(12) infinite;
+		animation: a 1s steps(12) infinite;
+		background: transparent url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAiIGhlaWdodD0iMTIwIiB2aWV3Qm94PSIwIDAgMTAwIDEwMCI+PHBhdGggZmlsbD0ibm9uZSIgZD0iTTAgMGgxMDB2MTAwSDB6Ii8+PHJlY3Qgd2lkdGg9IjciIGhlaWdodD0iMjAiIHg9IjQ2LjUiIHk9IjQwIiBmaWxsPSIjRTlFOUU5IiByeD0iNSIgcnk9IjUiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAgLTMwKSIvPjxyZWN0IHdpZHRoPSI3IiBoZWlnaHQ9IjIwIiB4PSI0Ni41IiB5PSI0MCIgZmlsbD0iIzk4OTY5NyIgcng9IjUiIHJ5PSI1IiB0cmFuc2Zvcm09InJvdGF0ZSgzMCAxMDUuOTggNjUpIi8+PHJlY3Qgd2lkdGg9IjciIGhlaWdodD0iMjAiIHg9IjQ2LjUiIHk9IjQwIiBmaWxsPSIjOUI5OTlBIiByeD0iNSIgcnk9IjUiIHRyYW5zZm9ybT0icm90YXRlKDYwIDc1Ljk4IDY1KSIvPjxyZWN0IHdpZHRoPSI3IiBoZWlnaHQ9IjIwIiB4PSI0Ni41IiB5PSI0MCIgZmlsbD0iI0EzQTFBMiIgcng9IjUiIHJ5PSI1IiB0cmFuc2Zvcm09InJvdGF0ZSg5MCA2NSA2NSkiLz48cmVjdCB3aWR0aD0iNyIgaGVpZ2h0PSIyMCIgeD0iNDYuNSIgeT0iNDAiIGZpbGw9IiNBQkE5QUEiIHJ4PSI1IiByeT0iNSIgdHJhbnNmb3JtPSJyb3RhdGUoMTIwIDU4LjY2IDY1KSIvPjxyZWN0IHdpZHRoPSI3IiBoZWlnaHQ9IjIwIiB4PSI0Ni41IiB5PSI0MCIgZmlsbD0iI0IyQjJCMiIgcng9IjUiIHJ5PSI1IiB0cmFuc2Zvcm09InJvdGF0ZSgxNTAgNTQuMDIgNjUpIi8+PHJlY3Qgd2lkdGg9IjciIGhlaWdodD0iMjAiIHg9IjQ2LjUiIHk9IjQwIiBmaWxsPSIjQkFCOEI5IiByeD0iNSIgcnk9IjUiIHRyYW5zZm9ybT0icm90YXRlKDE4MCA1MCA2NSkiLz48cmVjdCB3aWR0aD0iNyIgaGVpZ2h0PSIyMCIgeD0iNDYuNSIgeT0iNDAiIGZpbGw9IiNDMkMwQzEiIHJ4PSI1IiByeT0iNSIgdHJhbnNmb3JtPSJyb3RhdGUoLTE1MCA0NS45OCA2NSkiLz48cmVjdCB3aWR0aD0iNyIgaGVpZ2h0PSIyMCIgeD0iNDYuNSIgeT0iNDAiIGZpbGw9IiNDQkNCQ0IiIHJ4PSI1IiByeT0iNSIgdHJhbnNmb3JtPSJyb3RhdGUoLTEyMCA0MS4zNCA2NSkiLz48cmVjdCB3aWR0aD0iNyIgaGVpZ2h0PSIyMCIgeD0iNDYuNSIgeT0iNDAiIGZpbGw9IiNEMkQyRDIiIHJ4PSI1IiByeT0iNSIgdHJhbnNmb3JtPSJyb3RhdGUoLTkwIDM1IDY1KSIvPjxyZWN0IHdpZHRoPSI3IiBoZWlnaHQ9IjIwIiB4PSI0Ni41IiB5PSI0MCIgZmlsbD0iI0RBREFEQSIgcng9IjUiIHJ5PSI1IiB0cmFuc2Zvcm09InJvdGF0ZSgtNjAgMjQuMDIgNjUpIi8+PHJlY3Qgd2lkdGg9IjciIGhlaWdodD0iMjAiIHg9IjQ2LjUiIHk9IjQwIiBmaWxsPSIjRTJFMkUyIiByeD0iNSIgcnk9IjUiIHRyYW5zZm9ybT0icm90YXRlKC0zMCAtNS45OCA2NSkiLz48L3N2Zz4=) no-repeat;
+		background-size: 100%;
+	}
+
+	@-webkit-keyframes a {
+		0% {
+			-webkit-transform: rotate(0deg);
+			transform: rotate(0deg);
+		}
+
+		to {
+			-webkit-transform: rotate(1turn);
+			transform: rotate(1turn);
+		}
+	}
+
+	@keyframes a {
+		0% {
+			-webkit-transform: rotate(0deg);
+			transform: rotate(0deg);
+		}
+
+		to {
+			-webkit-transform: rotate(1turn);
+			transform: rotate(1turn);
+		}
+	}
+
+	.tui-loadmore-tips {
+		display: inline-block;
+		vertical-align: middle;
+	}
+
+	.tui-loading-2 {
+		width: 28rpx;
+		height: 28rpx;
+		border: 1px solid #8f8d8e;
+		border-radius: 50%;
+		margin: 0 6px;
+		display: inline-block;
+		vertical-align: middle;
+		clip-path: polygon(0% 0%,100% 0%,100% 30%,0% 30%);
+		animation: rotate 1s linear infinite;
+	}
+
+	@-webkit-keyframes rotate {
+		from {
+			transform: rotatez(0deg);
+		}
+
+		to {
+			transform: rotatez(360deg);
+		}
+	}
+
+	@keyframes rotate {
+		from {
+			transform: rotatez(0deg);
+		}
+
+		to {
+			transform: rotatez(360deg);
+		}
+	}
+
+	.tui-loading-3 {
+		display: inline-block;
+		margin: 0 6px;
+		vertical-align: middle;
+		width: 28rpx;
+		height: 28rpx;
+		background: 0 0;
+		border-radius: 50%;
+		border: 2px solid;
+		border-color: #e5e5e5 #e5e5e5 #e5e5e5 #8f8d8e;
+		animation: tui-rotate 0.7s linear infinite;
+	}
+
+	.tui-loading-3.tui-loading-primary {
+		border-color: #e5e5e5 #e5e5e5 #e5e5e5 #5677fc;
+	}
+
+	.tui-loading-3.tui-loading-green {
+		border-color: #e5e5e5 #e5e5e5 #e5e5e5 #19be6b;
+	}
+
+	.tui-loading-3.tui-loading-orange {
+		border-color: #e5e5e5 #e5e5e5 #e5e5e5 #ff7900;
+	}
+
+	.tui-loading-3.tui-loading-red {
+		border-color: #ededed #ededed #ededed #ed3f14;
+	}
+
+	@-webkit-keyframes tui-rotate {
+		0% {
+			transform: rotate(0);
+		}
+
+		100% {
+			transform: rotate(360deg);
+		}
+	}
+
+	@keyframes tui-rotate {
+		0% {
+			transform: rotate(0);
+		}
+
+		100% {
+			transform: rotate(360deg);
+		}
+	}
+</style>

+ 374 - 0
components/thorui/tui-modal/tui-modal.vue

@@ -0,0 +1,374 @@
+<template>
+	<view @touchmove.stop.prevent>
+		<view class="tui-modal-box" :style="{width:width,padding:padding,borderRadius:radius}" :class="[(fadeIn || show)?'tui-modal-normal':'tui-modal-scale',show?'tui-modal-show':'']">
+			<view v-if="!custom">
+				<view class="tui-modal-title" v-if="title">{{title}}</view>
+				<view class="tui-modal-content" :class="[title?'':'tui-mtop']" :style="{color:color,fontSize:size+'rpx'}">{{content}}</view>
+				<view class="tui-modalBtn-box" :class="[button.length!=2?'tui-flex-column':'']">
+					<block v-for="(item,index) in button" :key="index">
+						<button class="tui-modal-btn" :class="['tui-'+(item.type || 'primary')+(item.plain?'-outline':''),button.length!=2?'tui-btn-width':'',button.length>2?'tui-mbtm':'',shape=='circle'?'tui-circle-btn':'']"
+						 :hover-class="'tui-'+(item.plain?'outline':(item.type || 'primary'))+'-hover'" :data-index="index" @tap="handleClick">{{item.text || "确定"}}</button>
+					</block>
+				</view>
+			</view>
+			<view v-else>
+				<slot></slot>
+			</view>
+		</view>
+		<view class="tui-modal-mask" :class="[show?'tui-mask-show':'']" @tap="handleClickCancel"></view>
+
+	</view>
+</template>
+
+<script>
+	export default {
+		name: "tuiModal",
+		props: {
+			//是否显示
+			show: {
+				type: Boolean,
+				default: false
+			},
+			width: {
+				type: String,
+				default: "84%"
+			},
+			padding: {
+				type: String,
+				default: "40rpx 64rpx"
+			},
+			radius: {
+				type: String,
+				default: "24rpx"
+			},
+			//标题
+			title: {
+				type: String,
+				default: ""
+			},
+			//内容
+			content: {
+				type: String,
+				default: ""
+			},
+			//内容字体颜色
+			color: {
+				type: String,
+				default: "#999"
+			},
+			//内容字体大小 rpx
+			size: {
+				type: Number,
+				default: 28
+			},
+			//形状 circle, square
+			shape: {
+				type: String,
+				default: 'square'
+			},
+			button: {
+				type: Array,
+				default: function() {
+					return [{
+						text: "取消",
+						type: "red",
+						plain: true //是否空心
+					}, {
+						text: "确定",
+						type: "red",
+						plain: false
+					}]
+				}
+			},
+			//点击遮罩 是否可关闭
+			maskClosable: {
+				type: Boolean,
+				default: true
+			},
+			//淡入效果,自定义弹框插入input输入框时传true
+			fadeIn: {
+				type: Boolean,
+				default: false
+			},
+			//自定义弹窗内容
+			custom: {
+				type: Boolean,
+				default: false
+			}
+		},
+		data() {
+			return {
+
+			};
+		},
+		methods: {
+			handleClick(e) {
+				if (!this.show) return;
+				const dataset = e.currentTarget.dataset;
+				this.$emit('click', {
+					index: Number(dataset.index)
+				});
+			},
+			handleClickCancel() {
+				if (!this.maskClosable) return;
+				this.$emit('cancel');
+			}
+		}
+	}
+</script>
+
+<style scoped>
+	.tui-modal-box {
+		position: fixed;
+		left: 50%;
+		top: 50%;
+		margin: auto;
+		background-color: #fff;
+		z-index: 9999998;
+		transition: all 0.3s ease-in-out;
+		opacity: 0;
+		box-sizing: border-box;
+		visibility: hidden;
+	}
+
+	.tui-modal-scale {
+		transform: translate(-50%, -50%) scale(0);
+	}
+
+	.tui-modal-normal {
+		transform: translate(-50%, -50%) scale(1);
+	}
+
+	.tui-modal-show {
+		opacity: 1;
+		visibility: visible;
+	}
+
+	.tui-modal-mask {
+		position: fixed;
+		top: 0;
+		left: 0;
+		right: 0;
+		bottom: 0;
+		background-color: rgba(0, 0, 0, 0.6);
+		z-index: 9999996;
+		transition: all 0.3s ease-in-out;
+		opacity: 0;
+		visibility: hidden;
+	}
+
+	.tui-mask-show {
+		visibility: visible;
+		opacity: 1;
+	}
+
+	.tui-modal-title {
+		text-align: center;
+		font-size: 34rpx;
+		color: #333;
+		padding-top: 20rpx;
+		font-weight: bold;
+	}
+
+	.tui-modal-content {
+		text-align: center;
+		color: #999;
+		font-size: 28rpx;
+		padding-top: 20rpx;
+		padding-bottom: 60rpx;
+	}
+
+	.tui-mtop {
+		margin-top: 30rpx;
+	}
+
+	.tui-mbtm {
+		margin-bottom: 30rpx;
+	}
+
+	.tui-modalBtn-box {
+		width: 100%;
+		display: flex;
+		align-items: center;
+		justify-content: space-between
+	}
+
+	.tui-flex-column {
+		flex-direction: column;
+	}
+
+	.tui-modal-btn {
+		width: 46%;
+		height: 68rpx;
+		line-height: 68rpx;
+		position: relative;
+		border-radius: 10rpx;
+		font-size: 26rpx;
+		overflow: visible;
+		margin-left: 0;
+		margin-right: 0;
+	}
+
+	.tui-modal-btn::after {
+		content: " ";
+		position: absolute;
+		width: 200%;
+		height: 200%;
+		-webkit-transform-origin: 0 0;
+		transform-origin: 0 0;
+		-webkit-transform: scale(0.5, 0.5);
+		transform: scale(0.5, 0.5);
+		left: 0;
+		top: 0;
+		border-radius: 20rpx;
+	}
+
+	.tui-btn-width {
+		width: 80% !important;
+	}
+
+	.tui-primary {
+		background: #5677fc;
+		color: #fff;
+	}
+
+	.tui-primary-hover {
+		background: #4a67d6;
+		color: #e5e5e5;
+	}
+
+	.tui-primary-outline {
+		color: #5677fc;
+		background: transparent;
+	}
+
+	.tui-primary-outline::after {
+		border: 1px solid #5677fc;
+	}
+
+	.tui-danger {
+		background: #ed3f14;
+		color: #fff;
+	}
+
+	.tui-danger-hover {
+		background: #d53912;
+		color: #e5e5e5;
+	}
+
+	.tui-danger-outline {
+		color: #ed3f14;
+		background: transparent;
+	}
+
+	.tui-danger-outline::after {
+		border: 1px solid #ed3f14;
+	}
+
+	.tui-red {
+		background: #e41f19;
+		color: #fff;
+	}
+
+	.tui-red-hover {
+		background: #c51a15;
+		color: #e5e5e5;
+	}
+
+	.tui-red-outline {
+		color: #e41f19;
+		background: transparent;
+	}
+
+	.tui-red-outline::after {
+		border: 1px solid #e41f19;
+	}
+
+	.tui-warning {
+		background: #ff7900;
+		color: #fff;
+	}
+
+	.tui-warning-hover {
+		background: #e56d00;
+		color: #e5e5e5;
+	}
+
+	.tui-warning-outline {
+		color: #ff7900;
+		background: transparent;
+	}
+
+	.tui-warning-outline::after {
+		border: 1px solid #ff7900;
+	}
+
+	.tui-green {
+		background: #19be6b;
+		color: #fff;
+	}
+
+	.tui-green-hover {
+		background: #16ab60;
+		color: #e5e5e5;
+	}
+
+	.tui-green-outline {
+		color: #19be6b;
+		background: transparent;
+	}
+
+	.tui-green-outline::after {
+		border: 1px solid #19be6b;
+	}
+
+	.tui-white {
+		background: #fff;
+		color: #333;
+	}
+
+	.tui-white-hover {
+		background: #f7f7f9;
+		color: #666;
+	}
+
+	.tui-white-outline {
+		color: #333;
+		background: transparent;
+	}
+
+	.tui-white-outline::after {
+		border: 1px solid #333;
+	}
+
+	.tui-gray {
+		background: #ededed;
+		color: #999;
+	}
+
+	.tui-gray-hover {
+		background: #d5d5d5;
+		color: #898989;
+	}
+
+	.tui-gray-outline {
+		color: #999;
+		background: transparent;
+	}
+
+	.tui-gray-outline::after {
+		border: 1px solid #999;
+	}
+
+	.tui-outline-hover {
+		opacity: 0.6;
+	}
+
+	.tui-circle-btn {
+		border-radius: 40rpx !important;
+	}
+
+	.tui-circle-btn::after {
+		border-radius: 80rpx !important;
+	}
+</style>

+ 174 - 0
components/thorui/tui-navigation-bar/tui-navigation-bar.vue

@@ -0,0 +1,174 @@
+<template>
+	<view
+		class="tui-navigation-bar"
+		:class="{ 'tui-bar-line': opcity > 0.85 && splitLine, 'tui-navbar-fixed': isFixed }"
+		:style="{ height: height + 'px', backgroundColor: `rgba(${backgroundColor},${opcity})` }"
+	>
+		<view class="tui-status-bar" :style="{ height: statusBarHeight + 'px' }" v-if="isImmersive"></view>
+		<view class="tui-navigation_bar-title" :style="{ opacity: opcity, color: color, paddingTop: top - statusBarHeight + 'px' }" v-if="title && !isCustom">{{ title }}</view>
+		<slot />
+	</view>
+</template>
+
+<script>
+export default {
+	name: 'tuiNavigationBar',
+	props: {
+		//NavigationBar标题
+		title: {
+			type: String,
+			default: ''
+		},
+		//NavigationBar标题颜色
+		color: {
+			type: String,
+			default: '#fff'
+		},
+		//NavigationBar背景颜色 rgb
+		backgroundColor: {
+			type: String,
+			default: '86,119,252'
+		},
+		//是否需要分割线
+		splitLine: {
+			type: Boolean,
+			default: false
+		},
+		//是否设置不透明度
+		isOpcity: {
+			type: Boolean,
+			default: true
+		},
+		//滚动条滚动距离
+		scrollTop: {
+			type: [Number, String],
+			default: 0
+		},
+		/*
+		 isOpcity 为true时生效
+		 opcity=scrollTop /windowWidth * scrollRatio
+		*/
+		scrollRatio: {
+			type: [Number, String],
+			default: 0.3
+		},
+		//是否自定义header内容
+		isCustom: {
+			type: Boolean,
+			default: false
+		},
+		//是否沉浸式
+		isImmersive: {
+			type: Boolean,
+			default: true
+		},
+		isFixed: {
+			type: Boolean,
+			default: true
+		}
+	},
+	watch: {
+		scrollTop(newValue, oldValue) {
+			if (this.isOpcity) {
+				this.opcityChange();
+			}
+		}
+	},
+	data() {
+		return {
+			width: 375, //header宽度
+			left: 375, //小程序端 左侧距胶囊按钮距离
+			height: 44, //header高度
+			top: 0,
+			scrollH: 1, //滚动总高度,计算opcity
+			opcity: 0, //0-1
+			statusBarHeight: 0 //状态栏高度
+		};
+	},
+	created() {
+		this.opcity = this.isOpcity ? this.opcity : 1;
+		let obj = {};
+		// #ifdef MP-WEIXIN
+		obj = wx.getMenuButtonBoundingClientRect();
+		// #endif
+		// #ifdef MP-BAIDU
+		obj = swan.getMenuButtonBoundingClientRect();
+		// #endif
+		// #ifdef MP-ALIPAY
+		my.hideAddToDesktopMenu();
+		// #endif
+
+		uni.getSystemInfo({
+			success: res => {
+				this.statusBarHeight = res.statusBarHeight;
+				this.width = res.windowWidth;
+				this.left = obj.left || res.windowWidth;
+				if (this.isImmersive) {
+					this.height = obj.top ? obj.top + obj.height + 8 : res.statusBarHeight + 44;
+				}
+				this.scrollH = res.windowWidth * this.scrollRatio;
+				this.top = obj.top ? obj.top + (obj.height - 32) / 2 : res.statusBarHeight + 6;
+				this.$emit('init', {
+					width: this.width,
+					height: this.height,
+					left: obj.left,
+					top: this.top,
+					statusBarHeight: this.statusBarHeight,
+					opcity: this.opcity
+				});
+			}
+		});
+	},
+	methods: {
+		opcityChange() {
+			let scroll = this.scrollTop <= 1 ? 0 : this.scrollTop;
+			let opcity = scroll / this.scrollH;
+			if ((this.opcity >= 1 && opcity >= 1) || (this.opcity == 0 && opcity == 0)) {
+				return;
+			}
+			this.opcity = opcity;
+			this.$emit('change', {
+				opcity: this.opcity
+			});
+		}
+	}
+};
+</script>
+
+<style scoped>
+.tui-navigation-bar {
+	width: 100%;
+}
+.tui-navbar-fixed {
+	position: fixed;
+	left: 0;
+	top: 0;
+	z-index: 9998;
+}
+.tui-status-bar {
+	width: 100%;
+}
+.tui-navigation_bar-title {
+	width: 100%;
+	font-size: 17px;
+	line-height: 17px;
+	/* #ifndef APP-PLUS */
+	font-weight: 500;
+	/* #endif */
+	height: 32px;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+}
+
+.tui-bar-line::after {
+	content: '';
+	position: absolute;
+	border-bottom: 1rpx solid #eaeef1;
+	-webkit-transform: scaleY(0.5);
+	transform: scaleY(0.5);
+	bottom: 0;
+	right: 0;
+	left: 0;
+}
+</style>

+ 103 - 0
components/thorui/tui-no-data/tui-no-data.vue

@@ -0,0 +1,103 @@
+<template>
+	<view class="tui-nodata-box" :class="[fixed?'tui-nodata-fixed':'']">
+		<image v-if="imgUrl" :src="imgUrl" class="tui-tips-icon" :style="{width:imgWidth+'rpx',height:imgHeight+'rpx'}"></image>
+		<view class="tui-tips-content">
+			<slot></slot>
+		</view>
+		<button class="tui-tips-btn" hover-class="tui-tips-btn-hover" :style="{width:btnWidth+'rpx'}" v-if="btnText"  @tap="handleClick">{{btnText}}</button>
+	</view>
+</template>
+
+<script>
+	export default {
+		name: "tuiNoData",
+		props: {
+			//是否垂直居中
+			fixed: {
+				type: Boolean,
+				default: true
+			},
+			//图片地址,没有则不显示
+			imgUrl: {
+				type: String,
+				default: ""
+			},
+			//图片宽度
+			imgWidth: {
+				type: Number,
+				default: 200
+			},
+			//图片高度
+			imgHeight:{
+				type: Number,
+				default: 200
+			},
+			//按钮宽度
+			btnWidth:{
+				type: Number,
+				default: 200
+			},
+			//按钮文字,没有则不显示
+			btnText:{
+				type:String,
+				default: ""
+			}
+		},
+		methods: {
+			handleClick(e) {
+				this.$emit('click', {});
+			}
+		}
+	}
+</script>
+
+<style scoped>
+	.tui-nodata-box {
+		display: flex;
+		flex-direction: column;
+		justify-content: center;
+		align-items: center;
+	}
+
+	.tui-nodata-fixed {
+		width: 90%;
+		position: fixed;
+		left: 50%;
+		top: 50%;
+		-webkit-transform: translate(-50%, -50%);
+		transform: translate(-50%, -50%);
+	}
+
+	.tui-tips-icon {
+		display: block;
+		flex-shrink: 0;
+		width: 280rpx;
+		height: 280rpx;
+		margin-bottom: 40rpx;
+	}
+
+	.tui-tips-content {
+		text-align: center;
+		color: #666666;
+		font-size: 28rpx;
+		padding: 0 50rpx 24rpx 50rpx;
+		box-sizing: border-box;
+		word-break: break-all;
+		word-wrap: break-word;
+	}
+
+	.tui-tips-btn {
+		height: 60rpx;
+		line-height: 60rpx;
+		font-size: 28rpx;
+		background-color: #EB0909;
+		color: #fff;
+		border-radius: 6rpx;
+		margin: 0;
+	}
+
+	.tui-tips-btn-hover {
+		background-color: #c80808;
+		color: #e5e5e5;
+	}
+</style>

+ 115 - 0
components/thorui/tui-nomore/tui-nomore.vue

@@ -0,0 +1,115 @@
+<template>
+	<view class="tui-nomore-class tui-loadmore-none">
+		<view :class="[isDot?'tui-nomore-dot':'tui-nomore']">
+			<view :style="{backgroundColor:backgroundColor}" :class="[isDot?'tui-dot-text':'tui-nomore-text']">{{isDot?dotText:text}}</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		name: "tuiNomore",
+		props: {
+			//当前页面背景颜色
+			backgroundColor: {
+				type: String,
+				default: "#fafafa"
+			},
+			//是否以圆点代替 "没有更多了"
+			isDot: {
+				type: Boolean,
+				default: false
+			},
+			//isDot为false时生效
+			text: {
+				type: String,
+				default: "没有更多了"
+			}
+		},
+		data() {
+			return {
+				dotText: "●"
+			};
+		}
+	}
+</script>
+
+<style scoped>
+	.tui-loadmore-none {
+		width: 50%;
+		margin: 1.5em auto;
+		line-height: 1.5em;
+		font-size: 24rpx;
+		display: flex;
+		justify-content: center;
+	}
+
+	.tui-nomore {
+		width: 100%;
+		height: 100%;
+		position: relative;
+		display: flex;
+		justify-content: center;
+		margin-top: 10rpx;
+		padding-bottom: 6rpx;
+	}
+
+	.tui-nomore::before {
+		content: ' ';
+		position: absolute;
+		border-bottom: 1rpx solid #e5e5e5;
+		-webkit-transform: scaleY(0.5);
+		transform: scaleY(0.5);
+		width: 100%;
+		top: 18rpx;
+		left: 0;
+	}
+
+	.tui-nomore-text {
+		color: #999;
+		font-size: 24rpx;
+		text-align: center;
+		padding: 0 18rpx;
+		height: 36rpx;
+		line-height: 36rpx;
+		position: relative;
+		z-index: 1;
+	}
+
+	.tui-nomore-dot {
+		position: relative;
+		text-align: center;
+		-webkit-display: flex;
+		display: flex;
+		-webkit-justify-content: center;
+		justify-content: center;
+		margin-top: 10rpx;
+		padding-bottom: 6rpx;
+	}
+
+	.tui-nomore-dot::before {
+		content: '';
+		position: absolute;
+		border-bottom: 1rpx solid #e5e5e5;
+		-webkit-transform: scaleY(0.5)  translateX(-50%);
+		transform: scaleY(0.5)  translateX(-50%);
+		width: 360rpx;
+		top: 18rpx;
+		left: 50%;
+	}
+
+	.tui-dot-text {
+		position: relative;
+		color: #e5e5e5;
+		font-size: 10px;
+		text-align: center;
+		width: 50rpx;
+		height: 36rpx;
+		line-height: 36rpx;
+		-webkit-transform: scale(0.8);
+		transform: scale(0.8);
+		-webkit-transform-origin: center center;
+		transform-origin: center center;
+		z-index: 1;
+	}
+</style>

+ 205 - 0
components/thorui/tui-numberbox/tui-numberbox.vue

@@ -0,0 +1,205 @@
+<template>
+	<view class="tui-numberbox">
+		<view class="tui-numbox-icon tui-icon-reduce " :class="[disabled || min>=value?'tui-disabled':'']" @tap="reduce"
+		 :style="{color:iconColor,fontSize:iconSize+'rpx'}"></view>
+		<input type="number" v-model="inputValue" :disabled="disabled" @blur="blur" class="tui-num-input" :style="{color:color,fontSize:size+'rpx',backgroundColor:backgroundColor,height:height+'rpx',minHeight:height+'rpx',width:width+'rpx'}" />
+		<view class="tui-numbox-icon tui-icon-plus" :class="[disabled || value>=max?'tui-disabled':'']" @tap="plus" :style="{color:iconColor,fontSize:iconSize+'rpx'}"></view>
+	</view>
+</template>
+
+<script>
+	export default {
+		name: "tuiNumberbox",
+		props: {
+			value: {
+				type: Number,
+				default: 1
+			},
+			//最小值
+			min: {
+				type: Number,
+				default: 1
+			},
+			//最大值
+			max: {
+				type: Number,
+				default: 99
+			},
+			//迈步大小 1 1.1 10...
+			step: {
+				type: Number,
+				default: 1
+			},
+			//是否禁用操作
+			disabled: {
+				type: Boolean,
+				default: false
+			},
+			//加减图标大小 rpx
+			iconSize: {
+				type: Number,
+				default: 26
+			},
+			iconColor: {
+				type: String,
+				default: "#666666"
+			},
+			//input 高度
+			height: {
+				type: Number,
+				default: 42
+			},
+			//input 宽度
+			width: {
+				type: Number,
+				default: 80
+			},
+			size: {
+				type: Number,
+				default: 28
+			},
+			//input 背景颜色
+			backgroundColor: {
+				type: String,
+				default: "#F5F5F5"
+			},
+			//input 字体颜色
+			color: {
+				type: String,
+				default: "#333"
+			},
+			//索引值,列表中使用
+			index: {
+				type: [Number, String],
+				default: 0
+			},
+			//自定义参数
+			custom: {
+				type: [Number, String],
+				default: 0
+			}
+		},
+		created() {
+			this.inputValue = +this.value
+		},
+		data() {
+			return {
+				inputValue: 0
+			};
+		},
+		watch: {
+			value(val) {
+				this.inputValue = +val
+			}
+		},
+		methods: {
+			getScale() {
+				let scale = 1;
+				//浮点型
+				if (!Number.isInteger(this.step)) {
+					scale = Math.pow(10, (this.step + '').split('.')[1].length)
+				}
+				return scale;
+			},
+			calcNum: function(type) {
+				if (this.disabled) {
+					return
+				}
+				const scale = this.getScale()
+				let num = this.value * scale
+				let step = this.step * scale
+				if (type === 'reduce') {
+					num -= step
+				} else if (type === 'plus') {
+					num += step
+				}
+				let value = num / scale
+				if (type === "plus" && value < this.min) {
+					value = this.min
+				} else if (type === "reduce" && value > this.max) {
+					value = this.max
+				}
+				if (value < this.min || value > this.max) {
+					return
+				}
+				this.handleChange(value, type)
+			},
+			plus: function() {
+				this.calcNum("plus")
+			},
+			reduce: function() {
+				this.calcNum("reduce")
+			},
+			blur: function(e) {
+				let value = e.detail.value
+				if (value) {
+					if (~value.indexOf(".") && Number.isInteger(this.step)) {
+						value = value.split(".")[0]
+					}
+					value = Number(value)
+					if (value > this.max) {
+						value = this.max
+					} else if (value < this.min) {
+						value = this.min
+					}
+				} else {
+					value = this.min
+				}
+				if ((value == this.value && value != this.inputValue) || !e.detail.value) {
+					this.inputValue = value
+				}
+				this.handleChange(value, "blur")
+			},
+			handleChange(value, type) {
+				if (this.disabled) return;
+				this.$emit('change', {
+					value: value,
+					type: type,
+					index: this.index,
+					custom: this.custom
+				})
+			}
+		}
+	}
+</script>
+
+<style scoped>
+	@font-face {
+		font-family: 'numberbox';
+		src: url(data:application/font-woff;charset=utf-8;base64,d09GRgABAAAAAASQAA0AAAAABtwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABGRlRNAAAEdAAAABoAAAAciBpnRUdERUYAAARUAAAAHgAAAB4AKQALT1MvMgAAAZwAAABDAAAAVjxzSINjbWFwAAAB9AAAAEYAAAFK5zLpOGdhc3AAAARMAAAACAAAAAj//wADZ2x5ZgAAAkgAAACHAAAAnIfIEjxoZWFkAAABMAAAAC8AAAA2FZWEOWhoZWEAAAFgAAAAHAAAACQH3gOFaG10eAAAAeAAAAARAAAAEgwAAAFsb2NhAAACPAAAAAwAAAAMADAATm1heHAAAAF8AAAAHwAAACABEAAobmFtZQAAAtAAAAFJAAACiCnmEVVwb3N0AAAEHAAAAC0AAABV/+8iFXjaY2BkYGAA4gVmC5Tj+W2+MnCzMIDATWsFOQT9v5GFgbkeyOVgYAKJAgDrogf+AHjaY2BkYGBu+N/AEMPCAAJAkpEBFbAAAEcKAm142mNgZGBgYGWQYQDRDAxMQMwFhAwM/8F8BgALpAE5AHjaY2BkYWCcwMDKwMDUyXSGgYGhH0IzvmYwYuQAijKwMjNgBQFprikMDs9Yn01kbvjfwBDD3MDQABRmBMkBAOXpDHEAeNpjYYAAFghmZGAAAACdAA4AAAB42mNgYGBmgGAZBkYGEHAB8hjBfBYGDSDNBqQZGZiesT6b+P8/AwOElvwnWQxVDwSMbAxwDiMTkGBiQAWMDMMeAABRZwszAAAAAAAAAAAAAAAwAE542iWKQQrCMBBF5xNpd0pQ7EIoTEnahSCTUNqdWz2A9TrieXKeXCc1qcPn/zfzh0BYv2pVH7oQgbvqdG5Yt/DTrNlPYz+wHvuuqhFSME4sFshTgKUsKfhH5lg8BSul3i5bS3mQdd0RIh2IjnvUrkXDd8zuhuFt86tY9fonIsSYgsXpB+cCGosAeNp9kD1OAzEQhZ/zByQSQiCoXVEA2vyUKRMp9Ailo0g23pBo1155nUg5AS0VB6DlGByAGyDRcgpelkmTImvt6PObmeexAZzjGwr/3yXuhBWO8ShcwREy4Sr1F+Ea+V24jhY+hRvUf4SbuFUD4RYu1BsdVO2Eu5vSbcsKZxgIV3CKJ+Eq9ZVwjfwqXMcVPoQb1L+EmxjjV7iFa2WpDOFhMEFgnEFjig3jAjEcLJIyBtahOfRmEsxMTzd6ETubOBso71dilwMeaDnngCntPbdmvkon/mDLgdSYbh4FS7YpjS4idCgbXyyc1d2oc7D9nu22tNi/a4E1x+xRDWzU/D3bM9JIbAyvkJI18jK3pBJTj2hrrPG7ZynW814IiU68y/SIx5o0dTr3bmniwOLn8owcfbS5kj33qBw+Y1kIeb/dTsQgil2GP5PYcRkAAAB42mNgYoAALjDJyIAOWMGiTIxMjMwiWZmJQJRXVQoigTgjMd9QGIsgAFDsEBsAAAAAAAAB//8AAgABAAAADAAAABYAAAACAAEAAwAEAAEABAAAAAIAAAAAeNpjYGBgZACCq0vUOUD0TWsFORgNADPBBE4AAA==) format('woff');
+		font-weight: normal;
+		font-style: normal;
+	}
+
+	.tui-numbox-icon {
+		font-family: "numberbox" !important;
+		font-style: normal;
+		-webkit-font-smoothing: antialiased;
+		-moz-osx-font-smoothing: grayscale;
+		padding: 10rpx;
+	}
+
+	.tui-icon-reduce:before {
+		content: "\e691";
+	}
+
+	.tui-icon-plus:before {
+		content: "\e605";
+	}
+
+	.tui-numberbox {
+		display: -webkit-inline-flex;
+		display: inline-flex;
+		align-items: center;
+	}
+
+	.tui-num-input {
+		text-align: center;
+		margin: 0 12rpx;
+		font-weight: 400;
+	}
+
+	.tui-disabled {
+		color: #ededed !important;
+	}
+</style>

+ 679 - 0
components/thorui/tui-picture-cropper/tui-picture-cropper.vue

@@ -0,0 +1,679 @@
+<template>
+	<view class="tui-container" @touchmove.stop.prevent="stop">
+		<view
+			class="tui-image-cropper"
+			:change:prop="parse.propsChange"
+			:prop="props"
+			:data-lockRatio="lockRatio"
+			:data-lockWidth="lockWidth"
+			:data-lockHeight="lockHeight"
+			:data-maxWidth="maxWidth"
+			:data-minWidth="minWidth"
+			:data-maxHeight="maxHeight"
+			:data-minHeight="minHeight"
+			:data-width="width"
+			:data-height="height"
+			:data-limitMove="limitMove"
+			:data-windowHeight="sysInfo.windowHeight || 600"
+			:data-windowWidth="sysInfo.windowWidth || 400"
+			:data-imgTop="imgTop"
+			:data-imgLeft="imgLeft"
+			:data-imgWidth="imgWidth"
+			:data-imgHeight="imgHeight"
+			:data-angle="angle"
+			@touchend="parse.cutTouchEnd"
+			@touchstart="parse.cutTouchStart"
+			@touchmove="parse.cutTouchMove"
+		>
+			<view class="tui-content">
+				<view class="tui-content-top tui-bg-transparent" :style="{ transitionProperty: cutAnimation ? '' : 'background' }"></view>
+				<view class="tui-content-middle">
+					<view class="tui-bg-transparent tui-wxs-bg" :style="{ transitionProperty: cutAnimation ? '' : 'background' }"></view>
+					<view class="tui-cropper-box" :style="{ borderColor: borderColor, transitionProperty: cutAnimation ? '' : 'background' }">
+						<view
+							v-for="(item, index) in 4"
+							:key="index"
+							class="tui-edge"
+							:class="[`tui-${index < 2 ? 'top' : 'bottom'}-${index === 0 || index === 2 ? 'left' : 'right'}`]"
+							:style="{
+								width: edgeWidth,
+								height: edgeWidth,
+								borderColor: edgeColor,
+								borderWidth: edgeBorderWidth,
+								left: index === 0 || index === 2 ? `-${edgeOffsets}` : 'auto',
+								right: index === 1 || index === 3 ? `-${edgeOffsets}` : 'auto',
+								top: index < 2 ? `-${edgeOffsets}` : 'auto',
+								bottom: index > 1 ? `-${edgeOffsets}` : 'auto'
+							}"
+						></view>
+					</view>
+					<view class="tui-flex-auto tui-bg-transparent" :style="{ transitionProperty: cutAnimation ? '' : 'background' }"></view>
+				</view>
+				<view class="tui-flex-auto tui-bg-transparent" :style="{ transitionProperty: cutAnimation ? '' : 'background' }"></view>
+			</view>
+			<image
+				@load="imageLoad"
+				@error="imageLoad"
+				@touchstart="parse.touchstart"
+				@touchmove="parse.touchmove"
+				@touchend="parse.touchend"
+				:data-minScale="minScale"
+				:data-maxScale="maxScale"
+				:data-disableRotate="disableRotate"
+				:style="{
+					width: imgWidth ? imgWidth + 'px' : 'auto',
+					height: imgHeight ? imgHeight + 'px' : 'auto',
+					transitionDuration: (cutAnimation ? 0.3 : 0) + 's'
+				}"
+				class="tui-cropper-image"
+				:src="imageUrl"
+				v-if="imageUrl"
+				mode="widthFix"
+			></image>
+		</view>
+		<canvas
+			canvas-id="tui-image-cropper"
+			id="tui-image-cropper"
+			:disable-scroll="true"
+			:style="{ width: CROPPER_WIDTH * scaleRatio + 'px', height: CROPPER_HEIGHT * scaleRatio + 'px' }"
+			class="tui-cropper-canvas"
+		></canvas>
+		<view class="tui-cropper-tabbar" v-if="!custom">
+			<view class="tui-op-btn" @tap.stop="back">取消</view>
+			<image :src="rotateImg" class="tui-rotate-img" @tap="setAngle"></image>
+			<view class="tui-op-btn" @tap.stop="getImage">完成</view>
+		</view>
+	</view>
+</template>
+<script src="./tui-picture-cropper.wxs" module="parse" lang="wxs"></script>
+<script>
+/**
+ * 注意:组件中使用的图片地址,将文件复制到自己项目中
+ * 如果图片位置与组件同级,编译成小程序时图片会丢失
+ * 拷贝static下整个components文件夹
+ *也可直接转成base64(不建议)
+ * */
+export default {
+	name: 'tuiPictureCropper',
+	props: {
+		//图片路径
+		imageUrl: {
+			type: String,
+			default: ''
+		},
+		/*
+					 默认正方形,可修改大小控制比例
+					 裁剪框高度 px
+					*/
+		height: {
+			type: Number,
+			default: 280
+		},
+		//裁剪框宽度 px
+		width: {
+			type: Number,
+			default: 280
+		},
+		//裁剪框最小宽度 px
+		minWidth: {
+			type: Number,
+			default: 100
+		},
+		//裁剪框最小高度 px
+		minHeight: {
+			type: Number,
+			default: 100
+		},
+		//裁剪框最大宽度 px
+		maxWidth: {
+			type: Number,
+			default: 360
+		},
+		//裁剪框最大高度 px
+		maxHeight: {
+			type: Number,
+			default: 360
+		},
+		//裁剪框border颜色
+		borderColor: {
+			type: String,
+			default: 'rgba(255,255,255,0.1)'
+		},
+		//裁剪框边缘线颜色
+		edgeColor: {
+			type: String,
+			default: '#FFFFFF'
+		},
+		//裁剪框边缘线宽度 w=h
+		edgeWidth: {
+			type: String,
+			default: '34rpx'
+		},
+		//裁剪框边缘线border宽度
+		edgeBorderWidth: {
+			type: String,
+			default: '6rpx'
+		},
+		//偏移距离,根据edgeBorderWidth进行调整
+		edgeOffsets: {
+			type: String,
+			default: '6rpx'
+		},
+		/**
+		 * 如果宽度和高度都为true则裁剪框禁止拖动
+		 * 裁剪框宽度锁定
+		 */
+		lockWidth: {
+			type: Boolean,
+			default: false
+		},
+		//裁剪框高度锁定
+		lockHeight: {
+			type: Boolean,
+			default: false
+		},
+		//锁定裁剪框比例(放大或缩小)
+		lockRatio: {
+			type: Boolean,
+			default: false
+		},
+		//生成的图片尺寸相对剪裁框的比例
+		scaleRatio: {
+			type: Number,
+			default: 2
+		},
+		//图片的质量,取值范围为 (0, 1],不在范围内时当作1.0处理
+		quality: {
+			type: Number,
+			default: 0.8
+		},
+		//图片旋转角度
+		rotateAngle: {
+			type: Number,
+			default: 0
+		},
+		//图片最小缩放比
+		minScale: {
+			type: Number,
+			default: 0.5
+		},
+		//图片最大缩放比
+		maxScale: {
+			type: Number,
+			default: 2
+		},
+		//是否禁用触摸旋转(为false则可以触摸转动图片,limitMove为false生效)
+		disableRotate: {
+			type: Boolean,
+			default: true
+		},
+		//是否限制移动范围(剪裁框只能在图片内,为true不可触摸转动图片)
+		limitMove: {
+			type: Boolean,
+			default: true
+		},
+		//自定义操作栏(为true时隐藏底部操作栏)
+		custom: {
+			type: Boolean,
+			default: false
+		},
+		//值发生改变开始裁剪(custom为true时生效)
+		startCutting: {
+			type: [Number, Boolean],
+			default: 0
+		},
+		/**
+		 * 是否返回base64(H5端默认base64)
+		 * 支持平台:App,微信小程序,支付宝小程序,H5(默认url就是base64)
+		 **/
+		isBase64: {
+			type: Boolean,
+			default: false
+		},
+		//裁剪时是否显示loadding
+		loadding: {
+			type: Boolean,
+			default: true
+		},
+		//旋转icon
+		rotateImg: {
+			type: String,
+			default: '/static/components/cropper/img_rotate.png'
+		}
+	},
+	data() {
+		return {
+			TIME_CUT_CENTER: null,
+			CROPPER_WIDTH: 200, //裁剪框宽
+			CROPPER_HEIGHT: 200, //裁剪框高
+			cutX: 0, //画布x轴起点
+			cutY: 0, //画布y轴起点0
+			canvasWidth: 0,
+			canvasHeight: 0,
+			imgWidth: 0, //图片宽度
+			imgHeight: 0, //图片高度
+			scale: 1, //图片缩放比
+			angle: 0, //图片旋转角度
+			cutAnimation: false, //是否开启图片和裁剪框过渡
+			cutAnimationTime: null,
+			imgTop: 0, //图片上边距
+			imgLeft: 0, //图片左边距
+			ctx: null,
+			sysInfo: {},
+			props: '',
+			sizeChange: 0, //2
+			angleChange: 0, //3
+			resetChange: 0, //4
+			centerChange: 0 //5
+		};
+	},
+	watch: {
+		//定义变量然后利用change触发
+		imageUrl(val, oldVal) {
+			this.imageReset();
+			this.showLoading();
+			uni.getImageInfo({
+				src: val,
+				success: res => {
+					//计算图片尺寸
+					this.imgComputeSize(res.width, res.height);
+					if (this.limitMove) {
+						this.angleChange++;
+						this.props = `3,${this.angleChange}`;
+					}
+				},
+				fail: err => {
+					this.imgComputeSize();
+					if (this.limitMove) {
+						this.angleChange++;
+						this.props = `3,${this.angleChange}`;
+					}
+				}
+			});
+		},
+		rotateAngle(val) {
+			this.cutAnimation = true;
+			this.angle = val;
+			this.angleChanged(val);
+		},
+		cutAnimation(val) {
+			//开启过渡260毫秒之后自动关闭
+			clearTimeout(this.cutAnimationTime);
+			if (val) {
+				this.cutAnimationTime = setTimeout(() => {
+					this.cutAnimation = false;
+				}, 260);
+			}
+		},
+		limitMove(val) {
+			if (val) {
+				this.angleChanged(this.angle);
+			}
+		},
+		startCutting(val) {
+			if (this.custom && val) {
+				this.getImage();
+			}
+		}
+	},
+	mounted() {
+		this.sysInfo = uni.getSystemInfoSync();
+		this.imgTop = this.sysInfo.windowHeight / 2;
+		this.imgLeft = this.sysInfo.windowWidth / 2;
+		this.CROPPER_WIDTH = this.width;
+		this.CROPPER_HEIGHT = this.height;
+		this.canvasHeight = this.height;
+		this.canvasWidth = this.width;
+		this.ctx = uni.createCanvasContext('tui-image-cropper', this);
+		//初始化
+		setTimeout(() => {
+			this.props = '1,1';
+		}, 0);
+		setTimeout(() => {
+			this.$emit('ready', {});
+		}, 200);
+	},
+	methods: {
+		//返回裁剪后图片信息
+		getImage() {
+			if (!this.imageUrl) {
+				uni.showToast({
+					title: '请选择图片',
+					icon: 'none'
+				});
+				return;
+			}
+			this.loadding && this.showLoading();
+			let draw = () => {
+				//图片实际大小
+				let imgWidth = this.imgWidth * this.scale * this.scaleRatio;
+				let imgHeight = this.imgHeight * this.scale * this.scaleRatio;
+				//canvas和图片的相对距离
+				let xpos = this.imgLeft - this.cutX;
+				let ypos = this.imgTop - this.cutY;
+				//旋转画布
+				this.ctx.translate(xpos * this.scaleRatio, ypos * this.scaleRatio);
+				this.ctx.rotate((this.angle * Math.PI) / 180);
+				this.ctx.drawImage(this.imageUrl, -imgWidth / 2, -imgHeight / 2, imgWidth, imgHeight);
+				this.ctx.draw(false, () => {
+					let params = {
+						width: this.canvasWidth * this.scaleRatio,
+						height: Math.round(this.canvasHeight * this.scaleRatio),
+						destWidth: this.canvasWidth * this.scaleRatio,
+						destHeight: Math.round(this.canvasHeight) * this.scaleRatio,
+						fileType: 'png',
+						quality: this.quality
+					};
+					let data = {
+						url: '',
+						base64: '',
+						width: this.canvasWidth * this.scaleRatio,
+						height: this.canvasHeight * this.scaleRatio
+					};
+					// #ifdef MP-ALIPAY
+
+					if (this.isBase64) {
+						this.ctx.toDataURL(params).then(dataURL => {
+							data.base64 = dataURL;
+							this.loadding && uni.hideLoading();
+							this.$emit('cropper', data);
+						});
+					} else {
+						this.ctx.toTempFilePath({
+							...params,
+							success: res => {
+								data.url = res.tempFilePath;
+								this.loadding && uni.hideLoading();
+								this.$emit('cropper', data);
+							}
+						});
+					}
+					// #endif
+
+					// #ifndef MP-ALIPAY
+					// #ifdef MP-BAIDU || MP-TOUTIAO || H5
+					this.isBase64 = false;
+					// #endif
+					if (this.isBase64) {
+						uni.canvasGetImageData({
+							canvasId: 'tui-image-cropper',
+							x: 0,
+							y: 0,
+							width: this.canvasWidth * this.scaleRatio,
+							height: Math.round(this.canvasHeight * this.scaleRatio),
+							success: res => {
+								const arrayBuffer = new Uint8Array(res.data);
+								const base64 = uni.arrayBufferToBase64(arrayBuffer);
+								data.base64 = base64;
+								this.loadding && uni.hideLoading();
+								this.$emit('cropper', data);
+							}
+						});
+					} else {
+						uni.canvasToTempFilePath(
+							{
+								...params,
+								canvasId: 'tui-image-cropper',
+								success: res => {
+									data.url = res.tempFilePath;
+									// #ifdef H5
+									data.base64 = res.tempFilePath;
+									// #endif
+									this.loadding && uni.hideLoading();
+									this.$emit('cropper', data);
+								},
+								fail(res) {
+									console.log(res);
+								}
+							},
+							this
+						);
+					}
+					// #endif
+				});
+			};
+			if (this.CROPPER_WIDTH != this.canvasWidth || this.CROPPER_HEIGHT != this.canvasHeight) {
+				this.CROPPER_WIDTH = this.canvasWidth;
+				this.CROPPER_HEIGHT = this.canvasHeight;
+				this.$nextTick(() => {
+					this.ctx.draw();
+					setTimeout(() => {
+						draw();
+					}, 100);
+				});
+			} else {
+				draw();
+			}
+		},
+		change(e) {
+			this.cutX = e.cutX || 0;
+			this.cutY = e.cutY || 0;
+			this.canvasWidth = e.canvasWidth || 100;
+			this.canvasHeight = e.canvasHeight || 100;
+			this.imgWidth = e.imgWidth || 100;
+			this.imgHeight = e.imgHeight || 100;
+			this.scale = e.scale || 1;
+			this.angle = e.angle || 0;
+			this.imgTop = e.imgTop || 0;
+			this.imgLeft = e.imgLeft || 0;
+		},
+		imageReset() {
+			this.scale = 1;
+			this.angle = 0;
+			let sys = this.sysInfo.windowHeight || uni.getSystemInfoSync();
+			this.imgTop = sys.windowHeight / 2;
+			this.imgLeft = sys.windowWidth / 2;
+			this.resetChange++;
+			this.props = `4,${this.resetChange}`;
+			//初始化旋转角度 0deg
+			this.$emit('initAngle', {});
+		},
+		imageLoad(e) {
+			this.imageReset();
+			uni.hideLoading();
+			this.$emit('imageLoad', {});
+		},
+
+		imgComputeSize(width, height) {
+			//默认按图片最小边 = 对应裁剪框尺寸
+			let imgWidth = width,
+				imgHeight = height;
+			if (imgWidth && imgHeight) {
+				if (imgWidth / imgHeight > this.width / this.height) {
+					imgHeight = this.height;
+					imgWidth = (width / height) * imgHeight;
+				} else {
+					imgWidth = this.width;
+					imgHeight = (height / width) * imgWidth;
+				}
+			} else {
+				let sys = this.sysInfo || uni.getSystemInfoSync();
+				imgWidth = sys.windowWidth;
+				imgHeight = 0;
+			}
+			this.imgWidth = imgWidth;
+			this.imgHeight = imgHeight;
+			this.sizeChange++;
+			this.props = `2,${this.sizeChange}`;
+		},
+		moveStop() {
+			clearTimeout(this.TIME_CUT_CENTER);
+			this.TIME_CUT_CENTER = setTimeout(() => {
+				if (!this.cutAnimation) {
+					this.cutAnimation = true;
+				}
+				this.centerChange++;
+				this.props = `5,${this.centerChange}`;
+			}, 666);
+		},
+		moveDuring() {
+			clearTimeout(this.TIME_CUT_CENTER);
+		},
+		showLoading() {
+			uni.showLoading({
+				title: '请稍候...',
+				mask: true
+			});
+		},
+		stop() {},
+		back() {
+			uni.navigateBack();
+		},
+		angleChanged(val) {
+			this.moveStop();
+			if (this.limitMove && val % 90) {
+				this.angle = Math.round(val / 90) * 90;
+			}
+			this.angleChange++;
+			this.props = `3,${this.angleChange}`;
+		},
+		setAngle() {
+			this.cutAnimation = true;
+			this.angle = this.angle + 90;
+			this.angleChanged(this.angle);
+		}
+	}
+};
+</script>
+
+<style scoped>
+.tui-container {
+	width: 100vw;
+	height: 100vh;
+	background-color: rgba(0, 0, 0, 0.6);
+	position: fixed;
+	top: 0;
+	left: 0;
+	z-index: 1;
+}
+
+.tui-image-cropper {
+	width: 100vw;
+	height: 100vh;
+	position: absolute;
+}
+
+.tui-content {
+	width: 100vw;
+	height: 100vh;
+	position: absolute;
+	z-index: 9;
+	display: flex;
+	flex-direction: column;
+	pointer-events: none;
+}
+
+.tui-bg-transparent {
+	background-color: rgba(0, 0, 0, 0.6);
+	transition-duration: 0.3s;
+}
+
+.tui-content-top {
+	pointer-events: none;
+}
+
+.tui-content-middle {
+	width: 100%;
+	height: 200px;
+	display: flex;
+	box-sizing: border-box;
+}
+
+.tui-cropper-box {
+	position: relative;
+	/* transition-duration: 0.2s; */
+	border-style: solid;
+	border-width: 1rpx;
+	box-sizing: border-box;
+}
+
+.tui-flex-auto {
+	flex: auto;
+}
+
+.tui-cropper-image {
+	width: 100%;
+	border-style: none;
+	position: absolute;
+	top: 0;
+	left: 0;
+	z-index: 2;
+	-webkit-backface-visibility: hidden;
+	backface-visibility: hidden;
+	transform-origin: center;
+}
+
+.tui-cropper-canvas {
+	position: fixed;
+	z-index: 10;
+	left: -2000px;
+	top: -2000px;
+	pointer-events: none;
+}
+
+.tui-edge {
+	border-style: solid;
+	pointer-events: auto;
+	position: absolute;
+	box-sizing: border-box;
+}
+
+.tui-top-left {
+	border-bottom-width: 0 !important;
+	border-right-width: 0 !important;
+}
+
+.tui-top-right {
+	border-bottom-width: 0 !important;
+	border-left-width: 0 !important;
+}
+
+.tui-bottom-left {
+	border-top-width: 0 !important;
+	border-right-width: 0 !important;
+}
+
+.tui-bottom-right {
+	border-top-width: 0 !important;
+	border-left-width: 0 !important;
+}
+
+.tui-cropper-tabbar {
+	width: 100%;
+	height: 120rpx;
+	padding: 0 40rpx;
+	box-sizing: border-box;
+	position: fixed;
+	left: 0;
+	bottom: 0;
+	z-index: 99;
+	display: flex;
+	align-items: center;
+	justify-content: space-between;
+	color: #ffffff;
+	font-size: 32rpx;
+}
+
+.tui-cropper-tabbar::after {
+	content: ' ';
+	position: absolute;
+	top: 0;
+	right: 0;
+	left: 0;
+	border-top: 1rpx solid rgba(255, 255, 255, 0.2);
+	-webkit-transform: scaleY(0.5) translateZ(0);
+	transform: scaleY(0.5) translateZ(0);
+	transform-origin: 0 100%;
+}
+
+.tui-op-btn {
+	height: 80rpx;
+	display: flex;
+	align-items: center;
+}
+
+.tui-rotate-img {
+	width: 44rpx;
+	height: 44rpx;
+}
+</style>

+ 560 - 0
components/thorui/tui-picture-cropper/tui-picture-cropper.wxs

@@ -0,0 +1,560 @@
+var cropper = {
+	CUT_START: null,
+	cutX: 0, //画布x轴起点
+	cutY: 0, //画布y轴起点0
+	touchRelative: [{
+		x: 0,
+		y: 0
+	}], //手指或鼠标和图片中心的相对位置
+	flagCutTouch: false, //是否是拖动裁剪框
+	hypotenuseLength: 0, //双指触摸时斜边长度
+	flagEndTouch: false, //是否结束触摸
+	canvasWidth: 0,
+	canvasHeight: 0,
+	imgWidth: 0, //图片宽度
+	imgHeight: 0, //图片高度
+	scale: 1, //图片缩放比
+	angle: 0, //图片旋转角度
+	imgTop: 0, //图片上边距
+	imgLeft: 0, //图片左边距
+	//是否限制移动范围(剪裁框只能在图片内,为true不可触摸转动图片)
+	limitMove: true,
+	minHeight: 0,
+	maxHeight: 0,
+	minWidth: 0,
+	maxWidth: 0,
+	windowHeight: 0,
+	windowWidth: 0,
+	init: true
+}
+
+function bool(str) {
+	return str === 'true' || str == true ? true : false
+}
+
+function touchstart(e, ins) {
+	//var instance = e.instance;
+	// var state = instance.getState();
+	var touch = e.touches || e.changedTouches;
+	cropper.flagEndTouch = false;
+	if (touch.length == 1) {
+		cropper.touchRelative[0] = {
+			x: touch[0].pageX - cropper.imgLeft,
+			y: touch[0].pageY - cropper.imgTop
+		};
+	} else {
+		var width = Math.abs(touch[0].pageX - touch[1].pageX);
+		var height = Math.abs(touch[0].pageY - touch[1].pageY);
+		cropper.touchRelative = [{
+				x: touch[0].pageX - cropper.imgLeft,
+				y: touch[0].pageY - cropper.imgTop
+			},
+			{
+				x: touch[1].pageX - cropper.imgLeft,
+				y: touch[1].pageY - cropper.imgTop
+			}
+		];
+		cropper.hypotenuseLength = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2));
+	}
+
+}
+
+function moveDuring(ins) {
+	if (!ins) return;
+	ins.callMethod('moveDuring')
+}
+
+function moveStop(ins) {
+	if (!ins) return;
+	ins.callMethod('moveStop')
+};
+
+function setCutCenter(ins) {
+	var cutY = (cropper.windowHeight - cropper.canvasHeight) * 0.5;
+	var cutX = (cropper.windowWidth - cropper.canvasWidth) * 0.5;
+	//顺序不能变
+	cropper.imgTop = cropper.imgTop - cropper.cutY + cutY;
+	cropper.cutY = cutY; //截取的框上边距
+	cropper.imgLeft = cropper.imgLeft - cropper.cutX + cutX;
+	cropper.cutX = cutX; //截取的框左边距
+	styleUpdate(ins)
+	cutDetectionPosition(ins)
+	imgTransform(ins)
+	updateData(ins)
+}
+
+function touchmove(e, ins) {
+	var touch = e.touches || e.changedTouches;
+	if (cropper.flagEndTouch) return;
+	moveDuring(ins);
+	if (e.touches.length == 1) {
+		var left = touch[0].pageX - cropper.touchRelative[0].x,
+			top = touch[0].pageY - cropper.touchRelative[0].y;
+		cropper.imgLeft = left;
+		cropper.imgTop = top;
+		imgTransform(ins);
+		imgMarginDetectionPosition(ins);
+	} else {
+		var res = e.instance.getDataset();
+		var minScale = +res.minscale;
+		var maxScale = +res.maxscale;
+		var disableRotate = bool(res.disablerotate)
+		var width = Math.abs(touch[0].pageX - touch[1].pageX),
+			height = Math.abs(touch[0].pageY - touch[1].pageY),
+			hypotenuse = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2)),
+			scale = cropper.scale * (hypotenuse / cropper.hypotenuseLength),
+			current_deg = 0;
+		scale = scale <= minScale ? minScale : scale;
+		scale = scale >= maxScale ? maxScale : scale;
+		cropper.scale = scale;
+		imgMarginDetectionScale(ins, true);
+		var touchRelative = [{
+				x: touch[0].pageX - cropper.imgLeft,
+				y: touch[0].pageY - cropper.imgTop
+			},
+			{
+				x: touch[1].pageX - cropper.imgLeft,
+				y: touch[1].pageY - cropper.imgTop
+			}
+		];
+		if (!disableRotate) {
+			var first_atan = (180 / Math.PI) * Math.atan2(touchRelative[0].y, touchRelative[0].x);
+			var first_atan_old = (180 / Math.PI) * Math.atan2(cropper.touchRelative[0].y, cropper.touchRelative[0].x);
+			var second_atan = (180 / Math.PI) * Math.atan2(touchRelative[1].y, touchRelative[1].x);
+			var second_atan_old = (180 / Math.PI) * Math.atan2(cropper.touchRelative[1].y, cropper.touchRelative[1].x);
+			var first_deg = first_atan - first_atan_old,
+				second_deg = second_atan - second_atan_old;
+			if (first_deg != 0) {
+				current_deg = first_deg;
+			} else if (second_deg != 0) {
+				current_deg = second_deg;
+			}
+		}
+		cropper.touchRelative = touchRelative;
+		cropper.hypotenuseLength = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2));
+		//更新视图
+		cropper.angle = cropper.angle + current_deg;
+		imgTransform(ins);
+	}
+}
+
+function touchend(e, ins) {
+	cropper.flagEndTouch = true;
+	moveStop(ins);
+	updateData(ins)
+}
+
+
+function cutTouchStart(e, ins) {
+	var touch = e.touches || e.changedTouches;
+	var currentX = touch[0].pageX;
+	var currentY = touch[0].pageY;
+
+	/*
+	 * (右下-1 右上-2 左上-3 左下-4)
+	 * left_x [3,4]
+	 * top_y [2,3]
+	 * right_x [1,2]
+	 * bottom_y [1,4]
+	 */
+	var left_x1 = cropper.cutX - 30;
+	var left_x2 = cropper.cutX + 30;
+
+	var top_y1 = cropper.cutY - 30;
+	var top_y2 = cropper.cutY + 30;
+
+	var right_x1 = cropper.cutX + cropper.canvasWidth - 30;
+	var right_x2 = cropper.cutX + cropper.canvasWidth + 30;
+
+	var bottom_y1 = cropper.cutY + cropper.canvasHeight - 30;
+	var bottom_y2 = cropper.cutY + cropper.canvasHeight + 30;
+
+	if (currentX > right_x1 && currentX < right_x2 && currentY > bottom_y1 && currentY < bottom_y2) {
+		moveDuring();
+		cropper.flagCutTouch = true;
+		cropper.flagEndTouch = true;
+		cropper.CUT_START = {
+			width: cropper.canvasWidth,
+			height: cropper.canvasHeight,
+			x: currentX,
+			y: currentY,
+			corner: 1
+		};
+	} else if (currentX > right_x1 && currentX < right_x2 && currentY > top_y1 && currentY < top_y2) {
+		moveDuring();
+		cropper.flagCutTouch = true;
+		cropper.flagEndTouch = true;
+		cropper.CUT_START = {
+			width: cropper.canvasWidth,
+			height: cropper.canvasHeight,
+			x: currentX,
+			y: currentY,
+			cutY: cropper.cutY,
+			cutX: cropper.cutX,
+			corner: 2
+		};
+	} else if (currentX > left_x1 && currentX < left_x2 && currentY > top_y1 && currentY < top_y2) {
+		moveDuring();
+		cropper.flagCutTouch = true;
+		cropper.flagEndTouch = true;
+		cropper.CUT_START = {
+			width: cropper.canvasWidth,
+			height: cropper.canvasHeight,
+			cutY: cropper.cutY,
+			cutX: cropper.cutX,
+			x: currentX,
+			y: currentY,
+			corner: 3
+		};
+	} else if (currentX > left_x1 && currentX < left_x2 && currentY > bottom_y1 && currentY < bottom_y2) {
+		moveDuring();
+		cropper.flagCutTouch = true;
+		cropper.flagEndTouch = true;
+		cropper.CUT_START = {
+			width: cropper.canvasWidth,
+			height: cropper.canvasHeight,
+			cutY: cropper.cutY,
+			cutX: cropper.cutX,
+			x: currentX,
+			y: currentY,
+			corner: 4
+		};
+	}
+}
+
+function cutTouchMove(e, ins) {
+	if (!cropper.CUT_START || cropper.CUT_START === 'null') return;
+	if (cropper.flagCutTouch) {
+		var touch = e.touches || e.changedTouches;
+		var res = e.instance.getDataset();
+		var lockRatio = bool(res.lockratio);
+		var lockWidth = bool(res.lockwidth);
+		var lockHeight = bool(res.lockheight);
+		if (lockRatio && (lockWidth || lockHeight)) return;
+		var width = cropper.canvasWidth,
+			height = cropper.canvasHeight,
+			cutY = cropper.cutY,
+			cutX = cropper.cutX;
+
+		function size_correct() {
+			width = width <= cropper.maxWidth ? (width >= cropper.minWidth ? width : cropper.minWidth) : cropper.maxWidth;
+			height = height <= cropper.maxHeight ? (height >= cropper.minHeight ? height : cropper.minHeight) : cropper.maxHeight;
+		}
+
+		function size_inspect() {
+			if ((width > cropper.maxWidth || width < cropper.minWidth || height > cropper.maxHeight || height < cropper.minHeight) &&
+				lockRatio) {
+				size_correct();
+				return false;
+			} else {
+				size_correct();
+				return true;
+			}
+		};
+		height = cropper.CUT_START.height + (cropper.CUT_START.corner > 1 && cropper.CUT_START.corner < 4 ? 1 : -1) * (
+			cropper.CUT_START.y - touch[0].pageY);
+		switch (cropper.CUT_START.corner) {
+			case 1:
+				width = cropper.CUT_START.width - cropper.CUT_START.x + touch[0].pageX;
+				if (lockRatio) {
+					height = width / (cropper.canvasWidth / cropper.canvasHeight);
+				}
+				if (!size_inspect()) return;
+				break;
+			case 2:
+				width = cropper.CUT_START.width - cropper.CUT_START.x + touch[0].pageX;
+				if (lockRatio) {
+					height = width / (cropper.canvasWidth / cropper.canvasHeight);
+				}
+				if (!size_inspect()) return;
+				cutY = cropper.CUT_START.cutY - (height - cropper.CUT_START.height);
+				break;
+			case 3:
+				width = cropper.CUT_START.width + cropper.CUT_START.x - touch[0].pageX;
+				if (lockRatio) {
+					height = width / (cropper.canvasWidth / cropper.canvasHeight);
+				}
+				if (!size_inspect()) return;
+				cutY = cropper.CUT_START.cutY - (height - cropper.CUT_START.height);
+				cutX = cropper.CUT_START.cutX - (width - cropper.CUT_START.width);
+				break;
+			case 4:
+				width = cropper.CUT_START.width + cropper.CUT_START.x - touch[0].pageX;
+				if (lockRatio) {
+					height = width / (cropper.canvasWidth / cropper.canvasHeight);
+				}
+				if (!size_inspect()) return;
+				cutX = cropper.CUT_START.cutX - (width - cropper.CUT_START.width);
+				break;
+			default:
+				break;
+		}
+		if (!lockWidth && !lockHeight) {
+			cropper.canvasWidth = width;
+			cropper.cutX = cutX;
+			cropper.canvasHeight = height;
+			cropper.cutY = cutY;
+			canvasHeight(ins);
+			canvasWidth(ins);
+		} else if (!lockWidth) {
+			cropper.canvasWidth = width;
+			cropper.cutX = cutX;
+			canvasWidth(ins);
+		} else if (!lockHeight) {
+			cropper.canvasHeight = height;
+			cropper.cutY = cutY;
+			canvasHeight(ins);
+		}
+		styleUpdate(ins)
+		imgMarginDetectionScale(ins);
+	}
+}
+
+//检测剪裁框位置是否在允许的范围内(屏幕内)
+function cutDetectionPosition(ins) {
+	var windowHeight = cropper.windowHeight,
+		windowWidth = cropper.windowWidth;
+
+	function cutDetectionPositionTop() {
+		//检测上边距是否在范围内
+		if (cropper.cutY < 0) {
+			cropper.cutY = 0;
+		}
+		if (cropper.cutY > windowHeight - cropper.canvasHeight) {
+			cropper.cutY = windowHeight - cropper.canvasHeight;
+		}
+	}
+
+	function cutDetectionPositionLeft() {
+		//检测左边距是否在范围内
+		if (cropper.cutX < 0) {
+			cropper.cutX = 0;
+		}
+		if (cropper.cutX > windowWidth - cropper.canvasWidth) {
+			cropper.cutX = windowWidth - cropper.canvasWidth;
+		}
+	}
+	//裁剪框坐标处理(如果只写一个参数则另一个默认为0,都不写默认居中)
+	if (cropper.cutY == null && cropper.cutX == null) {
+		var cutY = (windowHeight - cropper.canvasHeight) * 0.5;
+		var cutX = (windowWidth - cropper.canvasWidth) * 0.5;
+		cropper.cutY = cutY; //截取的框上边距
+		cropper.cutX = cutX; //截取的框左边距
+	} else if (cropper.cutY != null && cropper.cutX != null) {
+		cutDetectionPositionTop();
+		cutDetectionPositionLeft();
+	} else if (cropper.cutY != null && cropper.cutX == null) {
+		cutDetectionPositionTop();
+		cropper.cutX = (windowWidth - cropper.canvasWidth) / 2;
+	} else if (cropper.cutY == null && cropper.cutX != null) {
+		cutDetectionPositionLeft();
+		cropper.cutY = (windowHeight - cropper.canvasHeight) / 2;
+	}
+
+	styleUpdate(ins)
+}
+
+/**
+ * 图片边缘检测-缩放
+ */
+function imgMarginDetectionScale(ins, delay) {
+	if (!cropper.limitMove) return;
+	var scale = cropper.scale;
+	var imgWidth = cropper.imgWidth;
+	var imgHeight = cropper.imgHeight;
+	if ((cropper.angle / 90) % 2) {
+		imgWidth = cropper.imgHeight;
+		imgHeight = cropper.imgWidth;
+	}
+	if (imgWidth * scale < cropper.canvasWidth) {
+		scale = cropper.canvasWidth / imgWidth;
+	}
+	if (imgHeight * scale < cropper.canvasHeight) {
+		scale = Math.max(scale, cropper.canvasHeight / imgHeight);
+	}
+	imgMarginDetectionPosition(ins, scale, delay);
+}
+/**
+ * 图片边缘检测-位置
+ */
+function imgMarginDetectionPosition(ins, scale, delay) {
+	if (!cropper.limitMove) return;
+	var left = cropper.imgLeft;
+	var top = cropper.imgTop;
+	scale = scale || cropper.scale;
+	var imgWidth = cropper.imgWidth;
+	var imgHeight = cropper.imgHeight;
+	if ((cropper.angle / 90) % 2) {
+		imgWidth = cropper.imgHeight;
+		imgHeight = cropper.imgWidth;
+	}
+	left = cropper.cutX + (imgWidth * scale) / 2 >= left ? left : cropper.cutX + (imgWidth * scale) / 2;
+	left = cropper.cutX + cropper.canvasWidth - (imgWidth * scale) / 2 <= left ? left : cropper.cutX + cropper.canvasWidth -
+		(imgWidth * scale) / 2;
+	top = cropper.cutY + (imgHeight * scale) / 2 >= top ? top : cropper.cutY + (imgHeight * scale) / 2;
+	top = cropper.cutY + cropper.canvasHeight - (imgHeight * scale) / 2 <= top ? top : cropper.cutY + cropper.canvasHeight -
+		(imgHeight * scale) / 2;
+
+	cropper.imgLeft = left;
+	cropper.imgTop = top;
+	cropper.scale = scale;
+	styleUpdate(ins)
+	if (!delay || delay === 'null') {
+		imgTransform(ins);
+	}
+}
+
+
+function cutTouchEnd(e, ins) {
+	moveStop(ins);
+	cropper.flagCutTouch = false;
+	updateData(ins)
+}
+
+
+//改变截取框大小
+function computeCutSize(ins) {
+	if (cropper.canvasWidth > cropper.windowWidth) {
+		cropper.canvasWidth = cropper.windowWidth;
+		// canvasWidth(ins)
+	} else if (cropper.canvasWidth + cropper.cutX > cropper.windowWidth) {
+		cropper.cutX = cropper.windowWidth - cropper.cutX;
+	}
+	if (cropper.canvasHeight > cropper.windowHeight) {
+		cropper.canvasHeight = cropper.windowHeight;
+		// canvasHeight(ins)
+	} else if (cropper.canvasHeight + cropper.cutY > cropper.windowHeight) {
+		cropper.cutY = cropper.windowHeight - cropper.cutY;
+	}
+	// styleUpdate(ins)
+}
+
+function styleUpdate(ins) {
+	if (!ins) return;
+	ins.selectComponent('.tui-cropper-box').setStyle({
+		'width': cropper.canvasWidth + 'px',
+		'height': cropper.canvasHeight + 'px'
+	})
+	ins.selectComponent('.tui-content-middle').setStyle({
+		'height': cropper.canvasHeight + 'px'
+	})
+	ins.selectComponent('.tui-content-top').setStyle({
+		'height': cropper.cutY + 'px'
+	})
+	ins.selectComponent('.tui-wxs-bg').setStyle({
+		'width': cropper.cutX + 'px'
+	})
+
+}
+
+function imgTransform(ins) {
+	var owner = ins.selectComponent('.tui-cropper-image')
+	if (!owner) return
+	var x = cropper.imgLeft - cropper.imgWidth / 2;
+	var y = cropper.imgTop - cropper.imgHeight / 2;
+	owner.setStyle({
+		'transform': 'translate3d(' + x + 'px,' + y + 'px,0) scale(' + cropper.scale + ') rotate(' + cropper.angle + 'deg)'
+	})
+}
+
+function imageReset(ins) {
+	cropper.scale = 1;
+	cropper.angle = 0;
+	imgTransform(ins);
+}
+//监听截取框宽高变化
+function canvasWidth(ins) {
+	if (cropper.canvasWidth < cropper.minWidth) {
+		cropper.canvasWidth = cropper.minWidth;
+	}
+	if (!ins) return;
+	computeCutSize(ins);
+}
+
+function canvasHeight(ins) {
+	if (cropper.canvasHeight < cropper.minHeight) {
+		cropper.canvasHeight = cropper.minHeight;
+	}
+	if (!ins) return;
+	computeCutSize(ins);
+}
+
+function updateData(ins) {
+	if (!ins) return;
+	ins.callMethod('change', {
+		cutX: cropper.cutX,
+		cutY: cropper.cutY,
+		canvasWidth: cropper.canvasWidth,
+		canvasHeight: cropper.canvasHeight,
+		imgWidth: cropper.imgWidth,
+		imgHeight: cropper.imgHeight,
+		scale: cropper.scale,
+		angle: cropper.angle,
+		imgTop: cropper.imgTop,
+		imgLeft: cropper.imgLeft
+	})
+}
+
+function propsChange(prop, oldProp, ownerInstance, ins) {
+	if (prop && prop !== 'null') {
+		var params = prop.split(',')
+		var type = +params[0]
+		var dataset = ins.getDataset();
+		if (cropper.init || type == 4) {
+			cropper.maxHeight = +dataset.maxheight;
+			cropper.minHeight = +dataset.minheight;
+			cropper.maxWidth = +dataset.maxwidth;
+			cropper.minWidth = +dataset.minwidth;
+			cropper.canvasWidth = +dataset.width;
+			cropper.canvasHeight = +dataset.height;
+			cropper.imgTop = dataset.windowheight / 2;
+			cropper.imgLeft = dataset.windowwidth / 2;
+			cropper.imgWidth = +dataset.imgwidth;
+			cropper.imgHeight = +dataset.imgheight;
+			cropper.windowHeight = +dataset.windowheight;
+			cropper.windowWidth = +dataset.windowwidth;
+			cropper.init = false
+		} else if (type == 2) {
+			cropper.imgWidth = +dataset.imgwidth;
+			cropper.imgHeight = +dataset.imgheight;
+		}
+		cropper.limitMove = bool(dataset.limitmove);
+		cropper.angle = +dataset.angle;
+		if (type == 3) {
+			imgTransform(ownerInstance);
+		}
+		switch (type) {
+			case 1:
+				setCutCenter(ownerInstance);
+				//设置裁剪框大小>设置图片尺寸>绘制canvas
+				computeCutSize(ownerInstance);
+				//检查裁剪框是否在范围内
+				cutDetectionPosition(ownerInstance);
+				break;
+			case 2:
+				setCutCenter(ownerInstance);
+				break;
+			case 3:
+				imgMarginDetectionScale(ownerInstance)
+				break;
+			case 4:
+				imageReset(ownerInstance);
+				break;
+			case 5:
+				setCutCenter(ownerInstance);
+				break;
+			default:
+				break;
+		}
+	}
+}
+
+module.exports = {
+	touchstart: touchstart,
+	touchmove: touchmove,
+	touchend: touchend,
+	cutTouchStart: cutTouchStart,
+	cutTouchMove: cutTouchMove,
+	cutTouchEnd: cutTouchEnd,
+	propsChange: propsChange
+}

+ 154 - 0
components/thorui/tui-rate/tui-rate.vue

@@ -0,0 +1,154 @@
+<template>
+	<view class="tui-rate-class tui-rate-box" @touchmove="touchMove">
+		<block v-for="(item,index) in quantity" :key="index">
+			<view class="tui-icon tui-relative" :class="['tui-icon-collection'+(hollow && (current<=index || (disabled && current<=index+1))?'':'-fill')]"
+			 :data-index="index" @tap="handleTap" :style="{fontSize:size+'px',color:(current>index+1 || (!disabled && current>index))?active:normal}">
+				<view class="tui-icon tui-icon-main tui-icon-collection-fill" v-if="disabled && current==index+1" :style="{fontSize:size+'px',color:active,width:percent+'%'}"></view>
+			</view>
+		</block>
+	</view>
+</template>
+
+<script>
+	export default {
+		name: "tuiRate",
+		props: {
+			//数量
+			quantity: {
+				type: Number,
+				default: 5
+			},
+			//当前选中
+			current: {
+				type: Number,
+				default: 0
+			},
+			//当前选中星星分数(大于0,小于等于1的数)
+			score: {
+				type: [Number, String],
+				default: 1
+			},
+			//禁用点击
+			disabled: {
+				type: Boolean,
+				default: false
+			},
+			//大小
+			size: {
+				type: Number,
+				default: 20
+			},
+			//未选中颜色
+			normal: {
+				type: String,
+				default: "#b2b2b2"
+			},
+			//选中颜色
+			active: {
+				type: String,
+				default: "#e41f19"
+			},
+			//未选中是否为空心
+			hollow: {
+				type: Boolean,
+				default: false
+			}
+		},
+		data() {
+			return {
+				pageX: 0,
+				percent: 0
+			};
+		},
+		created() {
+			this.percent = Number(this.score || 0) * 100
+		},
+		watch: {
+			score(newVal, oldVal) {
+				this.percent = Number(newVal || 0) * 100
+			}
+		},
+		methods: {
+			handleTap(e) {
+				if (this.disabled) {
+					return;
+				}
+				const index = e.currentTarget.dataset.index;
+				this.$emit('change', {
+					index: Number(index) + 1
+				})
+			},
+			touchMove(e) {
+				if (this.disabled) {
+					return;
+				}
+				if (!e.changedTouches[0]) {
+					return;
+				}
+				const movePageX = e.changedTouches[0].pageX;
+				const distance = movePageX - this.pageX;
+
+				if (distance <= 0) {
+					return;
+				}
+				let index = Math.ceil(distance / this.size);
+				index = index > this.count ? this.count : index;
+				this.$emit('change', {
+					index: index
+				})
+			}
+		},
+		mounted() {
+			const className = '.tui-rate-box';
+			let query = uni.createSelectorQuery().in(this)
+			query.select(className).boundingClientRect((res) => {
+				this.pageX = res.left || 0
+			}).exec()
+		}
+	}
+</script>
+
+<style scoped>
+	@font-face {
+		font-family: 'rateFont';
+		src: url(data:application/font-woff;charset=utf-8;base64,d09GRgABAAAAAAT4AA0AAAAAB4wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABGRlRNAAAE3AAAABoAAAAciBprQUdERUYAAAS8AAAAHgAAAB4AKQALT1MvMgAAAaAAAABDAAAAVj1YSN1jbWFwAAAB+AAAAEIAAAFCAA/qlmdhc3AAAAS0AAAACAAAAAj//wADZ2x5ZgAAAkgAAADwAAABZLMTdXtoZWFkAAABMAAAADAAAAA2FZKISmhoZWEAAAFgAAAAHQAAACQHYgOFaG10eAAAAeQAAAARAAAAEgx6AHpsb2NhAAACPAAAAAwAAAAMAEYAsm1heHAAAAGAAAAAHgAAACABEQBPbmFtZQAAAzgAAAFJAAACiCnmEVVwb3N0AAAEhAAAAC0AAABHLO3vkXjaY2BkYGAA4t2/VF7G89t8ZeBmYQCBm9ZKMnC6ikGMuYXpP5DLwcAEEgUAHPQJOXjaY2BkYGBu+N/AEMPCAALMLQyMDKiABQBQwgLwAAAAeNpjYGRgYGBlcGZgYgABEMkFhAwM/8F8BgAPigFhAAB42mNgZGFgnMDAysDA1Ml0hoGBoR9CM75mMGLkAIoysDIzYAUBaa4pDA7PXj17zdzwv4EhhrmBoQEozAiSAwD/YA2wAHjaY2GAABYIrmKoAgACggEBAAAAeNpjYGBgZoBgGQZGBhCwAfIYwXwWBgUgzQKEQP6z1///A8lX//9LSkJVMjCyMcCYDIxMQIKJARUwMgx7AAA/9QiLAAAAAAAAAAAAAABGALJ42mNgZKhiEGNuYfrPoMnAwGimps+ox6jPqKbEz8jHCMLyjHJAmk1czMie0cxInlHMDChrZs6cJyaosI+NlzmU34I/lImPdb+CoHgXCyujIosYtzTfKlYBtlWyuqwKjKwsjNvFTdlkGDnZ1srKrmXjZJRhMxVvZxFgA+rgYI9iYoriV1TYzybAwsDABHeLBIMT0DUg29VBTjEHucvcjtGeUVyOUZ6JaFcybefnZ5HuFdEX6ZVm5uMvniemxuXmzqUmNs+FeOfHCeiKzfPi4vKaJ6YrUCDOIiM8YYKwDIu4OMRbrOtkZdex4vMWACzGM5B42n2QPU4DMRCFn/MHJBJCIKhdUQDa/JQpEyn0CKWjSDbekGjXXnmdSDkBLRUHoOUYHIAbINFyCl6WSZMia+3o85uZ57EBnOMbCv/fJe6EFY7xKFzBETLhKvUX4Rr5XbiOFj6FG9R/hJu4VQPhFi7UGx1U7YS7m9JtywpnGAhXcIon4Sr1lXCN/CpcxxU+hBvUv4SbGONXuIVrZakM4WEwQWCcQWOKDeMCMRwskjIG1qE59GYSzExPN3oRO5s4GyjvV2KXAx5oOeeAKe09t2a+Sif+YMuB1JhuHgVLtimNLiJ0KBtfLJzV3ahzsP2e7ba02L9rgTXH7FENbNT8Pdsz0khsDK+QkjXyMrekElOPaGus8btnKdbzXgiJTrzL9IjHmjR1OvduaeLA4ufyjBx9tLmSPfeoHD5jWQh5v91OxCCKXYY/k9hxGQAAAHjaY2BigAAuMMnIgA5YwaJMjEyMzPzJ+Tk5qcklmfl58WmZOTlcCD4Ak9QKlAAAAAAAAAH//wACAAEAAAAMAAAAFgAAAAIAAQADAAQAAQAEAAAAAgAAAAB42mNgYGBkAIKrS9Q5QPRNayUZGA0AM8UETgAA) format('woff');
+		font-weight: normal;
+		font-style: normal;
+	}
+
+	.tui-icon {
+		font-family: "rateFont" !important;
+		font-style: normal;
+		-webkit-font-smoothing: antialiased;
+		-moz-osx-font-smoothing: grayscale;
+		display: block;
+	}
+
+	.tui-relative {
+		position: relative;
+	}
+
+	.tui-icon-main {
+		position: absolute;
+		height: 100%;
+		left: 0;
+		top: 0;
+		overflow: hidden;
+	}
+
+	.tui-icon-collection-fill:before {
+		content: "\e6ea";
+	}
+
+	.tui-icon-collection:before {
+		content: "\e6eb";
+	}
+
+	.tui-rate-box {
+		display: -webkit-inline-flex;
+		display: inline-flex;
+		align-items: center;
+		margin: 0;
+		padding: 0;
+	}
+</style>

+ 307 - 0
components/thorui/tui-round-progress/tui-round-progress.vue

@@ -0,0 +1,307 @@
+<template>
+	<view class="tui-circular-container" :style="{ width: diam + 'px', height: (height || diam) + 'px' }">
+		<canvas
+			:start="percent"
+			:change:start="parse.initDraw"
+			:data-width="diam"
+			:data-height="height"
+			:data-lineWidth="lineWidth"
+			:data-lineCap="lineCap"
+			:data-fontSize="fontSize"
+			:data-fontColor="fontColor"
+			:data-fontShow="fontShow"
+			:data-percentText="percentText"
+			:data-defaultShow="defaultShow"
+			:data-defaultColor="defaultColor"
+			:data-progressColor="progressColor"
+			:data-gradualColor="gradualColor"
+			:data-sAngle="sAngle"
+			:data-counterclockwise="counterclockwise"
+			:data-multiple="multiple"
+			:data-speed="speed"
+			:data-activeMode="activeMode"
+			:data-cid="progressCanvasId"
+			:canvas-id="progressCanvasId"
+			:class="[progressCanvasId]"
+			:style="{ width: diam + 'px', height: (height || diam) + 'px' }"
+		></canvas>
+		<slot></slot>
+	</view>
+</template>
+<script module="parse" lang="renderjs">
+ export default {
+	methods:{
+		format(str){
+			if(!str) return str;
+			return str.replace(/\"/g, "");
+		},
+		bool(str){
+			return str==='true' || str==true ? true:false
+		},
+		//初始化绘制
+		  initDraw(percentage, oldPercentage, owner, ins) {
+			 let state=ins.getState();
+			 let res=ins.getDataset();
+			 const activeMode=this.format(res.activemode);
+			 let start = activeMode === 'backwards' ? 0 : (state.startPercentage || 0);
+			 if(!state.progressContext || !state.canvas){
+				const width = res.width;
+				const height = res.height==0?res.width:res.height;
+				let ele=`.${res.cid}>canvas`
+				const canvas = document.querySelectorAll(this.format(ele))[0];
+				const ctx = canvas.getContext('2d');
+				// const dpr =uni.getSystemInfoSync().pixelRatio;
+				// canvas.style.width=width+'px';
+				// canvas.style.height=height+'px';
+				// canvas.width = width * dpr;
+				// canvas.height = height * dpr;
+				// ctx.scale(dpr, dpr);
+				state.progressContext=ctx;
+				state.canvas=canvas;
+				this.drawProgressCircular(start, ctx, canvas,percentage,res,state,owner);
+			 }else{
+		         this.drawProgressCircular(start, state.progressContext, state.canvas,percentage,res,state,owner);
+			 }
+		  },
+		  //默认(背景)圆环
+		  drawDefaultCircular(ctx, canvas,res) {
+		    //终止弧度
+			let sangle=Number(res.sangle) * Math.PI
+		    let eAngle = Math.PI * (res.height!=0 ? 1 : 2) + sangle;
+		    this.drawArc(ctx, eAngle,this.format(res.defaultcolor),res);
+		  },
+		  drawPercentage(ctx, percentage,res) {
+		    ctx.save(); //save和restore可以保证样式属性只运用于该段canvas元素
+		    ctx.beginPath();
+		    ctx.fillStyle = this.format(res.fontcolor);
+		    ctx.font = res.fontsize + "px Arial"; //设置字体大小和字体
+		    ctx.textAlign = "center";
+		    ctx.textBaseline = "middle";
+		    let radius = res.width / 2;
+			let percenttext=this.format(res.percenttext)
+		    if (!percenttext) {
+			  let multiple=Number(res.multiple)
+		      percentage = this.bool(res.counterclockwise) ? 100 - percentage * multiple : percentage * multiple;
+		      percentage = percentage.toFixed(0) + "%"
+		    } else {
+		      percentage = percenttext
+		    }
+		    ctx.fillText(percentage, radius, radius);
+		    ctx.stroke();
+		    ctx.restore();
+		  },
+		  //进度圆环
+		  drawProgressCircular(startPercentage, ctx, canvas,percentage,res,state,owner) {
+		    if (!ctx || !canvas) return;
+			let that=this
+		    let gradient = ctx.createLinearGradient(0, 0, Number(res.width), 0);
+		    gradient.addColorStop(0, this.format(res.progresscolor));
+			let gradualColor=this.format(res.gradualcolor)
+		    if (gradualColor) {
+		      gradient.addColorStop('1', gradualColor);
+		    }
+		    let requestId = null
+		    let renderLoop = () => {
+		      drawFrame((res) => {
+		        if (res) {
+		          requestId = requestAnimationFrame(renderLoop)
+		        } else {
+		          cancelAnimationFrame(requestId)
+		          requestId = null;
+		          renderLoop = null;
+		        }
+		      })
+		    }
+		    requestId = requestAnimationFrame(renderLoop)
+
+		    function drawFrame(callback) {
+		      ctx.clearRect(0, 0, canvas.width, canvas.height);
+		      if (that.bool(res.defaultshow)) {
+		        that.drawDefaultCircular(ctx, canvas,res)
+		      }
+		      if (that.bool(res.fontshow)) {
+		        that.drawPercentage(ctx, startPercentage,res);
+		      }
+		      if (percentage === 0 || (that.bool(res.counterclockwise) && startPercentage === 100)) return;
+		      let sangle=Number(res.sangle) * Math.PI
+			  let eAngle = ((2 * Math.PI) / 100) * startPercentage + sangle;
+			 that.drawArc(ctx, eAngle, gradient,res);
+			  owner.callMethod('change', {
+				percentage:startPercentage
+			 })
+		      if (startPercentage >= percentage) {
+				state.startPercentage=startPercentage;
+				owner.callMethod('end', {
+					canvasId: that.format(res.canvasid)
+				})
+		        cancelAnimationFrame(requestId)
+		        callback && callback(false)
+		        return;
+		      }
+		      let num = startPercentage + Number(res.speed)
+		      startPercentage = num > percentage ? percentage : num;
+		      callback && callback(true)
+		    }
+
+		  },
+		  //创建弧线
+		  drawArc(ctx, eAngle, strokeStyle,res) {
+		    ctx.save();
+		    ctx.beginPath();
+		    ctx.lineCap = this.format(res.linecap);
+		    ctx.lineWidth =Number(res.linewidth);
+		    ctx.strokeStyle = strokeStyle;
+		    let radius = res.width / 2; //x=y
+			let sangle=Number(res.sangle) * Math.PI
+		    ctx.arc(radius, radius, radius - res.linewidth, sangle, eAngle,this.bool(res.counterclockwise));
+		    ctx.stroke();
+		    ctx.closePath();
+		    ctx.restore();
+		  }
+	}
+}
+</script>
+<script>
+export default {
+	name: 'tuiRoundProgress',
+	props: {
+		/*
+		  传值需使用rpx进行转换保证各终端兼容
+		  px = rpx / 750 * wx.getSystemInfoSync().windowWidth
+		  圆形进度条(画布)宽度,直径 [px]
+		*/
+		diam: {
+			type: Number,
+			default: 60
+		},
+		//圆形进度条(画布)高度,默认取diam值[当画半弧时传值,height有值时则取height]
+		height: {
+			type: Number,
+			default: 0
+		},
+		//进度条线条宽度[px]
+		lineWidth: {
+			type: Number,
+			default: 4
+		},
+		/*
+			 线条的端点样式
+			 butt:向线条的每个末端添加平直的边缘
+			 round	向线条的每个末端添加圆形线帽
+			 square	向线条的每个末端添加正方形线帽
+			*/
+		lineCap: {
+			type: String,
+			default: 'round'
+		},
+		//圆环进度字体大小 [px]
+		fontSize: {
+			type: Number,
+			default: 12
+		},
+		//圆环进度字体颜色
+		fontColor: {
+			type: String,
+			default: '#5677fc'
+		},
+		//是否显示进度文字
+		fontShow: {
+			type: Boolean,
+			default: true
+		},
+		/*
+			 自定义显示文字[默认为空,显示百分比,fontShow=true时生效]
+			 可以使用 slot自定义显示内容
+			*/
+		percentText: {
+			type: String,
+			default: ''
+		},
+		//是否显示默认(背景)进度条
+		defaultShow: {
+			type: Boolean,
+			default: true
+		},
+		//默认进度条颜色
+		defaultColor: {
+			type: String,
+			default: '#CCC'
+		},
+		//进度条颜色
+		progressColor: {
+			type: String,
+			default: '#5677fc'
+		},
+		//进度条渐变颜色[结合progressColor使用,默认为空]
+		gradualColor: {
+			type: String,
+			default: ''
+		},
+		//起始弧度,单位弧度 实际  Math.PI * sAngle
+		sAngle: {
+			type: Number,
+			default: -0.5
+		},
+		//指定弧度的方向是逆时针还是顺时针。默认是false,即顺时针
+		counterclockwise: {
+			type: Boolean,
+			default: false
+		},
+		//进度百分比 [10% 传值 10]
+		percentage: {
+			type: Number,
+			default: 0
+		},
+		//进度百分比缩放倍数[使用半弧为100%时,则可传2]
+		multiple: {
+			type: Number,
+			default: 1
+		},
+		//动画执行速度,值越大动画越快(0.1~100)
+		speed: {
+			type: [Number, String],
+			default: 1
+		},
+		//backwards: 动画从头播;forwards:动画从上次结束点接着播
+		activeMode: {
+			type: String,
+			default: 'backwards'
+		}
+	},
+	watch: {
+		percentage(val) {
+			this.percent = val;
+		}
+	},
+	mounted() {
+		setTimeout(() => {
+			this.percent = this.percentage;
+		}, 50);
+	},
+	data() {
+		return {
+			percent: -1,
+			progressCanvasId: this.getCanvasId()
+		};
+	},
+	methods: {
+		getCanvasId() {
+			return 'tui' + new Date().getTime() + (Math.random() * 100000).toFixed(0);
+		},
+		change(e) {
+			//绘制进度
+			this.$emit('change', e);
+		},
+		end(e) {
+			//绘制完成
+			this.$emit('end', e);
+		}
+	}
+};
+</script>
+
+<style scoped>
+.tui-circular-container {
+	position: relative;
+}
+</style>

+ 178 - 0
components/thorui/tui-scroll-top/tui-scroll-top.vue

@@ -0,0 +1,178 @@
+<template>
+	<view class="tui-scroll-top_box" v-show="isIndex || isShare || (visible && toggle)" :style="{ bottom: bottom + 'rpx', right: right + 'rpx' }">
+		<view class="tui-scroll-top_item" v-if="isIndex" @tap.stop="goIndex">
+			<image class="tui-scroll-top_img" :src="indexIcon"></image>
+			<view class="tui-scroll-top_text">首页</view>
+		</view>
+		<button open-type="share" class="tui-share-btn" v-if="isShare && !customShare">
+			<view class="tui-scroll-top_item" :class="{ 'tui-scroll-item_top': isIndex }"><image class="tui-scroll-top_img" :src="shareIcon"></image></view>
+		</button>
+		<view class="tui-scroll-top_item" :class="{ 'tui-scroll-item_top': isIndex }" v-if="isShare && customShare" @tap.stop="share">
+			<image class="tui-scroll-top_img" :src="shareIcon"></image>
+		</view>
+		<view class="tui-scroll-top_item" :class="{ 'tui-scroll-item_top': isIndex || isShare }" v-show="visible && toggle" @tap.stop="goTop">
+			<image class="tui-scroll-top_img" :src="topIcon"></image>
+			<view class="tui-scroll-top_text tui-color-white">顶部</view>
+		</view>
+	</view>
+</template>
+
+<script>
+/**
+ * 注意:组件中使用的图片地址,将文件复制到自己项目中
+ * 如果图片位置与组件同级,编译成小程序时图片会丢失
+ * 拷贝static下整个components文件夹
+ * 也可直接转成base64(不建议)
+ * */
+export default {
+	name: 'tuiScrollTop',
+	props: {
+		//回顶部按钮距离底部距离 rpx
+		bottom: {
+			type: Number,
+			default: 180
+		},
+		//回顶部按钮距离右侧距离 rpx
+		right: {
+			type: Number,
+			default: 25
+		},
+		//距离顶部多少距离显示 px
+		top: {
+			type: Number,
+			default: 200
+		},
+		//滚动距离
+		scrollTop: {
+			type: Number
+		},
+		//回顶部滚动时间
+		duration: {
+			type: Number,
+			default: 120
+		},
+		//是否显示返回首页按钮
+		isIndex: {
+			type: Boolean,
+			default: false
+		},
+		//是否显示分享图标
+		isShare: {
+			type: Boolean,
+			default: false
+		},
+		//自定义分享(小程序可使用button=>open-type="share")
+		customShare: {
+			type: Boolean,
+			default: false
+		},
+		//回顶部icon
+		topIcon: {
+			type: String,
+			default: '/static/components/scroll-top/icon_top_3x.png'
+		},
+		//回首页icon
+		indexIcon: {
+			type: String,
+			default: '/static/components/scroll-top/icon_index_3x.png'
+		},
+		//分享icon
+		shareIcon: {
+			type: String,
+			default: '/static/components/scroll-top/icon_share_3x.png'
+		}
+	},
+	watch: {
+		scrollTop(newValue, oldValue) {
+			this.change();
+		}
+	},
+	data() {
+		return {
+			//判断是否显示
+			visible: false,
+			//控制显示,主要解决调用api滚到顶部fixed元素抖动的问题
+			toggle: true
+		};
+	},
+	methods: {
+		goTop: function() {
+			this.toggle = false;
+			uni.pageScrollTo({
+				scrollTop: 0,
+				duration: this.duration
+			});
+			setTimeout(() => {
+				this.toggle = true;
+			}, 220);
+		},
+		goIndex: function() {
+			this.$emit('index', {});
+		},
+		share() {
+			this.$emit('share', {});
+		},
+		change() {
+			let show = this.scrollTop > this.top;
+			if ((show && this.visible) || (!show && !this.visible)) {
+				return;
+			}
+			this.visible = show;
+		}
+	}
+};
+</script>
+
+<style scoped>
+.tui-scroll-top_box {
+	width: 80rpx;
+	height: 270rpx;
+	position: fixed;
+	z-index: 9999;
+	right: 30rpx;
+	bottom: 30rpx;
+	font-weight: 400;
+}
+
+.tui-scroll-top_item {
+	width: 80rpx;
+	height: 80rpx;
+	position: relative;
+}
+
+.tui-scroll-item_top {
+	margin-top: 30rpx;
+}
+
+.tui-scroll-top_img {
+	width: 80rpx;
+	height: 80rpx;
+	display: block;
+}
+
+.tui-scroll-top_text {
+	width: 80rpx;
+	text-align: center;
+	font-size: 24rpx;
+	line-height: 24rpx;
+	transform: scale(0.92);
+	transform-origin: center center;
+	position: absolute;
+	left: 0;
+	bottom: 15rpx;
+}
+
+.tui-color-white {
+	color: #fff;
+}
+.tui-share-btn {
+	background: transparent !important;
+	padding: 0;
+	margin: 0;
+	display: inline;
+	border: 0;
+}
+.tui-share-btn::after {
+	border: 0;
+}
+</style>

+ 230 - 0
components/thorui/tui-skeleton/tui-skeleton.vue

@@ -0,0 +1,230 @@
+<template>
+	<view class="tui-skeleton-cmomon tui-skeleton-box" :style="{width: winWidth+'px', height:winHeight+'px', backgroundColor:backgroundColor}">
+		<view class="tui-skeleton-cmomon" v-for="(item,index) in skeletonElements" :key="index" :style="{width: item.width+'px', height:item.height+'px', left: item.left+'px', top: item.top+'px',backgroundColor: skeletonBgColor,borderRadius:getRadius(item.skeletonType,borderRadius)}"></view>
+		<view class="tui-loading" :class="[getLoadingType(loadingType)]" v-if="isLoading"></view>
+	</view>
+</template>
+
+<script>
+	export default {
+		name: "tuiSkeleton",
+		props: {
+			//选择器(外层容器)
+			selector: {
+				type: String,
+				default: "tui-skeleton"
+			},
+			//外层容器背景颜色
+			backgroundColor: {
+				type: String,
+				default: "#fff"
+			},
+			//骨架元素背景颜色
+			skeletonBgColor: {
+				type: String,
+				default: "#e9e9e9"
+			},
+			//骨架元素类型:矩形,圆形,带圆角矩形["rect","circular","fillet"]
+			//默认所有,根据页面情况进行传值
+			//页面对应元素class为:tui-skeleton-rect,tui-skeleton-circular,tui-skeleton-fillet
+			//如果传入的值不在下列数组中,则为自定义class值,默认按矩形渲染
+			skeletonType: {
+				type: Array,
+				default () {
+					return ["rect", "circular", "fillet"]
+				}
+			},
+			//圆角值,skeletonType=fillet时生效
+			borderRadius: {
+				type: String,
+				default: "16rpx"
+			},
+			//骨架屏预生成数据:提前生成好的数据,当传入该属性值时,则不会再次查找子节点信息
+			preloadData: {
+				type: Array,
+				default () {
+					return []
+				}
+			},
+			//是否需要loading
+			isLoading: {
+				type: Boolean,
+				default: true
+			},
+			//loading类型[1-10]
+			loadingType: {
+				type: Number,
+				default: 1
+			}
+		},
+		created() {
+			const res = uni.getSystemInfoSync();
+			this.winWidth = res.windowWidth;
+			this.winHeight = res.windowHeight;
+			//如果有预生成数据,则直接使用
+			this.isPreload(true)
+		},
+		mounted() {
+			this.$nextTick(() => {
+				this.nodesRef(`.${this.selector}`).then((res) => {
+					if(res && res[0]){
+						this.winHeight = res[0].height + Math.abs(res[0].top)
+					}
+				});
+				!this.isPreload() && this.selectorQuery()
+			})
+
+		},
+		data() {
+			return {
+				winWidth: 375,
+				winHeight: 800,
+				skeletonElements: []
+			};
+		},
+		methods: {
+			getLoadingType: function(type) {
+				let value = 1
+				if (type && type > 0 && type < 11) {
+					value = type
+				}
+				return 'tui-loading-' + value
+			},
+			getRadius: function(type, val) {
+				let radius = "0"
+				if (type == "circular") {
+					radius = "50%"
+				} else if (type == "fillet") {
+					radius = val
+				}
+				return radius;
+			},
+			isPreload(init) {
+				let preloadData = this.preloadData || []
+				if (preloadData.length) {
+					init && (this.skeletonElements = preloadData)
+					return true
+				}
+				return false
+			},
+			async selectorQuery() {
+				let skeletonType = this.skeletonType || []
+				let nodes = []
+				for (let item of skeletonType) {
+					let className = `.${item}`
+					if (~"rect_circular_fillet".indexOf(item)) {
+						className = `.${this.selector}-${item}`
+					}
+					await this.nodesRef(className).then((res) => {
+						res.map(d => {
+							d.skeletonType = item
+						})
+						nodes = nodes.concat(res)
+					})
+				}
+				this.skeletonElements = nodes
+			},
+			async nodesRef(className) {
+				return await new Promise((resolve, reject) => {
+					uni.createSelectorQuery().selectAll(className).boundingClientRect((res) => {
+						if (res) {
+							resolve(res);
+						} else {
+							reject(res)
+						}
+					}).exec();
+				})
+			}
+		}
+	}
+</script>
+
+<style scoped>
+	.tui-skeleton-cmomon {
+		position: absolute;
+		z-index: 99999;
+		overflow: hidden;
+	}
+
+	.tui-skeleton-box {
+		left: 0;
+		top: 0;
+	}
+
+	.tui-loading {
+		display: inline-block;
+		vertical-align: middle;
+		width: 40rpx;
+		height: 40rpx;
+		background: 0 0;
+		border-radius: 50%;
+		border: 2px solid;
+		animation: tui-rotate 0.7s linear infinite;
+		position: fixed;
+		z-index: 999999;
+		left: 50%;
+		top: 50%;
+		margin-left: -20rpx;
+		margin-top: -20rpx;
+	}
+
+	.tui-loading-1 {
+		border-color: #e5e5e5 #e5e5e5 #e5e5e5 #5677fc;
+	}
+
+	.tui-loading-2 {
+		border-color: #e5e5e5 #e5e5e5 #e5e5e5 #8f8d8e;
+	}
+
+	.tui-loading-3 {
+		border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) #fff;
+	}
+
+	.tui-loading-4 {
+		border-color: #e5e5e5 #e5e5e5 #e5e5e5 #35b06a;
+	}
+
+	.tui-loading-5 {
+		border-color: #e5e5e5 #e5e5e5 #e5e5e5 #fc872d;
+	}
+
+	.tui-loading-6 {
+		border-color: #e5e5e5 #e5e5e5 #e5e5e5 #eb0909;
+	}
+
+	.tui-loading-7 {
+		border-color: #5677fc transparent #5677fc transparent;
+	}
+
+	.tui-loading-8 {
+		border-color: #35b06a transparent #35b06a transparent;
+	}
+
+	.tui-loading-9 {
+		border-color: #fc872d transparent #fc872d transparent;
+	}
+
+	.tui-loading-10 {
+		border-color: #eb0909 transparent #eb0909 transparent;
+	}
+
+	@-webkit-keyframes tui-rotate {
+		0% {
+			transform: rotate(0);
+		}
+
+		100% {
+			transform: rotate(360deg);
+		}
+	}
+
+	@keyframes tui-rotate {
+		0% {
+			transform: rotate(0);
+		}
+
+		100% {
+			transform: rotate(360deg);
+		}
+	}
+</style>

+ 187 - 0
components/thorui/tui-steps/tui-steps.vue

@@ -0,0 +1,187 @@
+<template>
+	<view class="tui-steps-box" :class="{'tui-steps-column':direction==='column'}">
+		<view class="tui-step-item" :style="{width:direction==='column'?'100%':spacing}" :class="[direction==='row'?'tui-step-horizontal':'tui-step-vertical']"
+		 v-for="(item,index) in items" :key="index">
+			<view class="tui-step-item-ico" :style="{width:direction==='column'?'36rpx':'100%'}">
+				<view class="tui-step-ico" :class="[direction==='column'?'tui-step-column_ico':'tui-step-row_ico']" :style="{width:type==2 || activeSteps===index?'36rpx':'16rpx',height:type==2 || activeSteps===index?'36rpx':'16rpx',backgroundColor:index<=activeSteps?activeColor:(type==2?'#fff':deactiveColor),borderColor:index<=activeSteps?activeColor:deactiveColor}">
+					<!-- <icon type="success_no_circle" :size="12"  color="#fff"></icon> -->
+					<text v-if="activeSteps!==index" :style="{color:index<=activeSteps?'#fff':''}">{{type==1?'':index+1}}</text>
+					<tui-icon name="check" :size="16" color="#fff" v-if="activeSteps===index"></tui-icon>
+				</view>
+				<view class="tui-step-line" :class="['tui-step-'+direction+'_line']" :style="{backgroundColor:index<=activeSteps-1?activeColor:deactiveColor}"
+				 v-if="index!=items.length-1"></view>
+			</view>
+			<view class="tui-step-item-main" :class="['tui-step-'+direction+'_item_main']">
+				<view class="tui-step-item-title" :style="{color:index<=activeSteps?activeColor:deactiveColor,fontSize:titleSize+'rpx',lineHeight:titleSize+'rpx',fontWeight:bold?'bold':'normal'}">
+					{{item.title}}
+				</view>
+				<view class="tui-step-item-content" :style="{color:index<=activeSteps?activeColor:deactiveColor,fontSize:descSize+'rpx'}">
+					{{item.desc}}
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	//如果自定义传入图标内容,则需要拆分组件
+	export default {
+		name:"tuiSteps",
+		props: {
+			// 1-默认步骤 2-数字步骤
+			type: {
+				type: Number,
+				default: 1
+			},
+			spacing: {
+				type: String,
+				default: '160rpx'
+			},
+			// 方向 row column
+			direction: {
+				type: String,
+				default: 'row'
+			},
+			// 激活状态成功颜色
+			activeColor: {
+				type: String,
+				default: '#5677fc'
+			},
+			// 未激活状态颜色
+			deactiveColor: {
+				type: String,
+				default: '#999999'
+			},
+			//title字体大小
+			titleSize: {
+				type: Number,
+				default: 28
+			},
+			//title是否粗体
+			bold: {
+				type: Boolean,
+				default: false
+			},
+			//desc字体大小
+			descSize: {
+				type: Number,
+				default: 24
+			},
+			// 当前步骤
+			activeSteps: {
+				type: Number,
+				default: -1
+			},
+			/**
+			 * [{
+					title: "标题",
+					desc: "描述"
+				}]
+			 * */
+			items: {
+				type: Array,
+				default () {
+					return []
+				}
+			}
+		},
+		data() {
+			return {
+
+			};
+		}
+	}
+</script>
+
+<style scoped>
+	.tui-steps-box {
+		width: 100%;
+		display: flex;
+		justify-content: center;
+	}
+
+	.tui-steps-column {
+		flex-direction: column;
+	}
+
+	.tui-step-ico {
+		border-radius: 40rpx;
+		position: relative;
+		z-index: 3;
+		margin: 0 auto;
+		border-width: 1rpx;
+		border-style: solid;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+
+	}
+
+	.tui-step-row_ico {
+		top: 50%;
+		transform: translateY(-50%);
+	}
+
+	.tui-step-column_ico {
+		top: 0;
+	}
+
+
+	.tui-step-line {
+		position: absolute;
+		left: 50%;
+		top: 20rpx;
+		width: 100%;
+		height: 1rpx;
+	}
+
+	.tui-step-row_item_main {
+		text-align: center
+	}
+
+	.tui-step-item {
+		font-size: 24rpx;
+		position: relative;
+		box-sizing: border-box;
+	}
+
+	.tui-step-item-ico {
+		height: 36rpx;
+		line-height: 36rpx;
+		text-align: center;
+	}
+
+	.tui-step-item-main {
+		margin-top: 16rpx;
+		clear: both
+	}
+
+	.tui-step-item-title {
+		word-break: break-all;
+	}
+
+	.tui-step-item-content {
+		margin-top: 8rpx;
+		word-break: break-all;
+	}
+
+	.tui-step-vertical {
+		width: 100%;
+		display: flex;
+		padding-bottom: 60rpx
+	}
+
+	.tui-step-column_item_main {
+		margin-top: 0;
+		padding-left: 20rpx;
+	}
+
+	.tui-step-column_line {
+		position: absolute;
+		height: 100%;
+		top: 0;
+		left: 18rpx;
+		margin: 0;
+		width: 1rpx
+	}
+</style>

+ 124 - 0
components/thorui/tui-sticky-wxs/tui-sticky-wxs.vue

@@ -0,0 +1,124 @@
+<template>
+	<view class="tui-sticky-class" :change:prop="parse.stickyChange" :prop="scrollTop" :data-top="top" :data-height="height"
+	 :data-stickytop="stickyTop" :data-container="container" :data-isNativeHeader="isNativeHeader" :data-index="index">
+		<!--sticky 容器-->
+		<view class="tui-sticky-seat" :style="{ height: stickyHeight, backgroundColor: backgroundColor }"></view>
+		<view class="tui-sticky-bar">
+			<slot name="header"></slot>
+		</view>
+		<!--sticky 容器-->
+		<!--内容-->
+		<slot name="content"></slot>
+	</view>
+</template>
+<script src="./tui-sticky.wxs" module="parse" lang="wxs"></script>
+<script>
+	export default {
+		name: 'tuiStickyWxs',
+		props: {
+			scrollTop: {
+				type: [Number, String],
+				value: 0
+			},
+			//吸顶时与顶部的距离,单位px
+			stickyTop: {
+				type: [Number, String],
+				// #ifndef H5
+				default: 0,
+				// #endif
+				// #ifdef H5
+				default: 44
+				// #endif
+			},
+			//是否指定容器,即内容放置插槽content内
+			container: {
+				type: Boolean,
+				default: false
+			},
+			//是否是原生自带header
+			isNativeHeader: {
+				type: Boolean,
+				default: true
+			},
+			//吸顶容器 高度 rpx
+			stickyHeight: {
+				type: String,
+				default: 'auto'
+			},
+			//占位容器背景颜色
+			backgroundColor: {
+				type: String,
+				default: 'transparent'
+			},
+			//是否重新计算[有异步加载时使用,设置大于0的数]
+			recalc: {
+				type: Number,
+				default: 0
+			},
+			//列表中的索引值
+			index: {
+				type: [Number, String],
+				default: 0
+			}
+		},
+		watch: {
+			recalc(newValue, oldValue) {
+				this.updateScrollChange(() => {
+					//更新prop scrollTop值(+0.1即可),触发change事件
+					this.$emit("prop",{})
+				});
+			}
+		},
+		mounted() {
+			setTimeout(() => {
+				this.updateScrollChange();
+			}, 20);
+		},
+		data() {
+			return {
+				timer: null,
+				top: 0,
+				height: 0
+			};
+		},
+		methods: {
+			updateScrollChange(callback) {
+				if (this.timer) {
+					clearTimeout(this.timer);
+					this.timer = null;
+				}
+				this.timer = setTimeout(() => {
+					const className = '.tui-sticky-class';
+					const query = uni.createSelectorQuery().in(this);
+					query
+						.select(className)
+						.boundingClientRect(res => {
+							if (res) {
+								this.top = res.top + (this.scrollTop || 0);
+								this.height = res.height;
+								callback && callback();
+								this.$emit('change', {
+									index: Number(this.index),
+									top: this.top
+								});
+							}
+						})
+						.exec();
+				}, 0);
+			}
+		}
+	};
+</script>
+
+<style scoped>
+	.tui-sticky-fixed {
+		width: 100%;
+		position: fixed;
+		left: 0;
+		z-index: 998;
+	}
+
+	.tui-sticky-seat {
+		display: none;
+	}
+</style>

+ 44 - 0
components/thorui/tui-sticky-wxs/tui-sticky.wxs

@@ -0,0 +1,44 @@
+var stickyChange = function (scrollTop, oldScrollTop, ownerInstance, ins) {
+  if(!oldScrollTop) return false;
+  var dataset = ins.getDataset()
+  var top =+dataset.top;
+  var height = + dataset.height;
+  var stickyTop = +dataset.stickytop;
+  var isNativeHeader=dataset.isnativeheader;
+  var isFixed = false;
+  var distance=stickyTop
+  // #ifdef H5
+  if (isNativeHeader) {
+  	distance = distance - 44
+  	distance = distance < 0 ? 0 : distance
+  }
+  // #endif
+  if (dataset.container) {
+    isFixed = (scrollTop + distance >= top && scrollTop + distance < top + height) ? true : false
+  } else {
+    isFixed = scrollTop + distance >= top ? true : false
+  }
+  if (isFixed) {
+    ownerInstance.selectComponent('.tui-sticky-bar').setStyle({
+      "top": stickyTop + 'px'
+    }).addClass('tui-sticky-fixed')
+    ownerInstance.selectComponent('.tui-sticky-seat').setStyle({
+      "display": 'block'
+    })
+  } else {
+    ownerInstance.selectComponent('.tui-sticky-bar').setStyle({
+      "top": 'auto'
+    }).removeClass('tui-sticky-fixed')
+    ownerInstance.selectComponent('.tui-sticky-seat').setStyle({
+      "display": 'none'
+    })
+  }
+  ownerInstance.triggerEvent("sticky", {
+    isFixed: isFixed,
+    index: parseInt(dataset.index)
+  })
+}
+
+module.exports = {
+  stickyChange: stickyChange
+}

+ 152 - 0
components/thorui/tui-sticky/tui-sticky.vue

@@ -0,0 +1,152 @@
+<template>
+	<view class="tui-sticky-class">
+		<!--sticky 容器-->
+		<view :style="{height: stickyHeight,backgroundColor:backgroundColor}" v-if="isFixed"></view>
+		<view :class="{'tui-sticky-fixed':isFixed}" :style="{top:isFixed?stickyTop+'px':'auto'}">
+			<slot name="header"></slot>
+		</view>
+		<!--sticky 容器-->
+		<!--内容-->
+		<slot name="content"></slot>
+	</view>
+</template>
+
+<script>
+	export default {
+		name: "tuiSticky",
+		props: {
+			scrollTop: {
+				type: Number
+			},
+			//吸顶时与顶部的距离,单位px
+			stickyTop: {
+				type: [Number, String]
+				// #ifndef H5
+				,default: 0
+				// #endif
+				// #ifdef H5
+				,default: 44
+				// #endif
+			},
+			//是否指定容器,即内容放置插槽content内
+			container: {
+				type: Boolean,
+				default: false
+			},
+			//是否是原生自带header
+			isNativeHeader: {
+				type: Boolean,
+				default: true
+			},
+			//吸顶容器 高度 rpx
+			stickyHeight: {
+				type: String,
+				default: "auto"
+			},
+			//占位容器背景颜色
+			backgroundColor: {
+				type: String,
+				default: "transparent"
+			},
+			//是否重新计算[有异步加载时使用,设置大于0的数]
+			recalc: {
+				type: Number,
+				default: 0
+			},
+			//列表中的索引值
+			index: {
+				type: [Number, String],
+				default: 0
+			}
+		},
+		watch: {
+			scrollTop(newValue, oldValue) {
+				if (this.initialize != 0) {
+					this.updateScrollChange(() => {
+						this.updateStickyChange();
+						this.initialize = 0
+					});
+				} else {
+					this.updateStickyChange();
+				}
+			},
+			recalc(newValue, oldValue) {
+				this.updateScrollChange(() => {
+					this.updateStickyChange();
+					this.initialize = 0;
+				});
+			}
+		},
+		created() {
+			this.initialize = this.recalc
+		},
+		mounted() {
+			setTimeout(() => {
+				this.updateScrollChange();
+			}, 20)
+		},
+		data() {
+			return {
+				timer: null,
+				top: 0,
+				height: 0,
+				isFixed: false,
+				initialize: 0 //重新初始化
+			};
+		},
+		methods: {
+			updateStickyChange() {
+				const top = this.top;
+				const height = this.height;
+				const scrollTop = this.scrollTop
+				let stickyTop = this.stickyTop
+				// #ifdef H5
+				if (this.isNativeHeader) {
+					stickyTop = stickyTop - 44
+					stickyTop = stickyTop < 0 ? 0 : stickyTop
+				}
+				// #endif
+				if (this.container) {
+					this.isFixed = (scrollTop + stickyTop >= top && scrollTop + stickyTop < top + height) ? true : false
+				} else {
+					this.isFixed = scrollTop + stickyTop >= top ? true : false
+				}
+				//是否吸顶
+				this.$emit("sticky", {
+					isFixed: this.isFixed,
+					index: this.index
+				})
+			},
+			updateScrollChange(callback) {
+				if (this.timer) {
+					clearTimeout(this.timer)
+					this.timer = null
+				}
+				this.timer = setTimeout(() => {
+					const className = '.tui-sticky-class';
+					const query = uni.createSelectorQuery().in(this);
+					query.select(className).boundingClientRect((res) => {
+						if (res) {
+							this.top = res.top + (this.scrollTop || 0);
+							this.height = res.height;
+							callback && callback();
+							this.$emit("change", {
+								index: Number(this.index),
+								top: this.top
+							})
+						}
+					}).exec()
+				}, 0)
+			}
+		}
+	}
+</script>
+
+<style scoped>
+	.tui-sticky-fixed {
+		width: 100%;
+		position: fixed;
+		left: 0;
+		z-index: 888;
+	}
+</style>

+ 275 - 0
components/thorui/tui-swipe-action/tui-swipe-action.vue

@@ -0,0 +1,275 @@
+<template>
+	<view class="tui-swipeout-wrap">
+		<view class="tui-swipeout-item" :class="[isShowBtn ? 'swipe-action-show' : '']" @touchstart="handlerTouchstart"
+		 @touchmove="handlerTouchmove" @touchend="handlerTouchend" :style="{ transform: 'translate(' + position.pageX + 'px,0)' }">
+			<view class="tui-swipeout-content">
+				<slot name="content"></slot>
+			</view>
+			<view class="tui-swipeout-button-right-group" v-if="actions.length > 0" @touchend.stop="loop">
+				<view class="tui-swipeout-button-right-item" v-for="(item, index) in actions" :key="index" :style="{ backgroundColor: item.background || '#f7f7f7', color: item.color, width: item.width + 'px' }"
+				 :data-index="index" @tap="handlerButton">
+					<image :src="item.icon" v-if="item.icon" :style="{ width: px(item.imgWidth), height: px(item.imgHeight) }"></image>
+					<text :style="{ fontSize: px(item.fontsize) }">{{ item.name }}</text>
+				</view>
+			</view>
+			<!--actions长度设置为0,可直接传按钮进来-->
+			<view class="tui-swipeout-button-right-group" @touchend.stop="loop" @tap="handlerParentButton" v-if="actions.length === 0"
+			 :style="{ width: operateWidth + 'px', right: '-' + operateWidth + 'px' }">
+				<slot name="button"></slot>
+			</view>
+		</view>
+		<view v-if="isShowBtn && showMask" class="swipe-action_mask" @tap.stop="closeButtonGroup" @touchstart.stop.prevent="closeButtonGroup" />
+	</view>
+</template>
+
+<script>
+	export default {
+		name: 'tuiSwipeAction',
+		props: {
+			// name: '删除',
+			// color: '#fff',
+			// fontsize: 32,//单位rpx
+			// width: 80, //单位px
+			// icon: 'like.png',//此处为图片地址
+			// background: '#ed3f14'
+			actions: {
+				type: Array,
+				default () {
+					return [];
+				}
+			},
+			//点击按钮时是否自动关闭
+			closable: {
+				type: Boolean,
+				default: true
+			},
+			//设为false,可以滑动多行不关闭菜单
+			showMask: {
+				type: Boolean,
+				default: true
+			},
+			operateWidth: {
+				type: Number,
+				default: 80
+			},
+			params: {
+				type: Object,
+				default () {
+					return {};
+				}
+			},
+			//禁止滑动
+			forbid: {
+				type: Boolean,
+				default: false
+			},
+			//手动开关
+			open: {
+				type: Boolean,
+				default: false
+			}
+		},
+		watch: {
+			actions(newValue, oldValue) {
+				this.updateButtonSize();
+			},
+			open(newValue) {
+				this.manualSwitch(newValue);
+			}
+		},
+		data() {
+			return {
+				//start position
+				tStart: {
+					pageX: 0,
+					pageY: 0
+				},
+				//限制滑动距离
+				limitMove: 0,
+				//move position
+				position: {
+					pageX: 0,
+					pageY: 0
+				},
+				isShowBtn: false
+			};
+		},
+		mounted() {
+			this.updateButtonSize();
+		},
+		methods: {
+			swipeDirection(x1, x2, y1, y2) {
+				return Math.abs(x1 - x2) >= Math.abs(y1 - y2) ? (x1 - x2 > 0 ? 'Left' : 'Right') : y1 - y2 > 0 ? 'Up' : 'Down';
+			},
+			//阻止事件冒泡
+			loop() {},
+			updateButtonSize() {
+				const actions = this.actions;
+				if (actions.length > 0) {
+					const query = uni.createSelectorQuery().in(this);
+					let limitMovePosition = 0;
+					actions.forEach(item => {
+						limitMovePosition += item.width || 0;
+					});
+					this.limitMove = limitMovePosition;
+				} else {
+					this.limitMove = this.operateWidth;
+				}
+			},
+			handlerTouchstart(event) {
+				if (this.forbid) return;
+				const touches = event.touches ? event.touches[0] : {};
+				const tStart = this.tStart;
+				if (touches) {
+					for (let i in tStart) {
+						if (touches[i]) {
+							tStart[i] = touches[i];
+						}
+					}
+				}
+			},
+			swipper(touches) {
+				const start = this.tStart;
+				const spacing = {
+					pageX: touches.pageX - start.pageX,
+					pageY: touches.pageY - start.pageY
+				};
+				if (this.limitMove < Math.abs(spacing.pageX)) {
+					spacing.pageX = -this.limitMove;
+				}
+				this.position = spacing;
+			},
+			handlerTouchmove(event) {
+				if (this.forbid) return;
+				const start = this.tStart;
+				const touches = event.touches ? event.touches[0] : {};
+				if (touches) {
+					const direction = this.swipeDirection(start.pageX, touches.pageX, start.pageY, touches.pageY);
+					if (direction === 'Left' && Math.abs(this.position.pageX) !== this.limitMove) {
+						this.swipper(touches);
+					}
+				}
+			},
+			handlerTouchend(event) {
+				if (this.forbid) return;
+				const start = this.tStart;
+				const touches = event.changedTouches ? event.changedTouches[0] : {};
+				if (touches) {
+					const direction = this.swipeDirection(start.pageX, touches.pageX, start.pageY, touches.pageY);
+					const spacing = {
+						pageX: touches.pageX - start.pageX,
+						pageY: touches.pageY - start.pageY
+					};
+					if (Math.abs(spacing.pageX) >= 40 && direction === 'Left') {
+						spacing.pageX = spacing.pageX < 0 ? -this.limitMove : this.limitMove;
+						this.isShowBtn = true;
+					} else {
+						spacing.pageX = 0;
+					}
+					this.position = spacing;
+				}
+			},
+			handlerButton(event) {
+				if (this.closable) {
+					this.closeButtonGroup();
+				}
+				const dataset = event.currentTarget.dataset;
+				this.$emit('click', {
+					index: Number(dataset.index),
+					item: this.params
+				});
+			},
+			closeButtonGroup() {
+				this.position = {
+					pageX: 0,
+					pageY: 0
+				};
+				this.isShowBtn = false;
+			},
+			//控制自定义按钮菜单
+			handlerParentButton(event) {
+				if (this.closable) {
+					this.closeButtonGroup();
+				}
+			},
+			manualSwitch(isOpen) {
+				let x = 0;
+				if (isOpen) {
+					if (this.actions.length === 0) {
+						x = this.operateWidth;
+					} else {
+						let width = 0;
+						this.actions.forEach(item => {
+							width += item.width;
+						});
+						x = width;
+					}
+				}
+				this.position = {
+					pageX: -x,
+					pageY: 0
+				};
+			},
+			px(num) {
+				return uni.upx2px(num) + 'px';
+			}
+		}
+	};
+</script>
+
+<style scoped>
+	.tui-swipeout-wrap {
+		background-color: #fff;
+		position: relative;
+		overflow: hidden;
+	}
+
+	.swipe-action-show {
+		position: relative;
+		z-index: 998;
+	}
+
+	.tui-swipeout-item {
+		width: 100%;
+		/* padding: 15px 20px; */
+		box-sizing: border-box;
+		transition: transform 0.2s ease;
+		font-size: 14px;
+	}
+
+	.tui-swipeout-content {
+		white-space: nowrap;
+		overflow: hidden;
+	}
+
+	.tui-swipeout-button-right-group {
+		position: absolute;
+		right: -100%;
+		top: 0;
+		height: 100%;
+		z-index: 1;
+		width: 100%;
+	}
+
+	.tui-swipeout-button-right-item {
+		height: 100%;
+		float: left;
+		white-space: nowrap;
+		box-sizing: border-box;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		text-align: center;
+	}
+
+	.swipe-action_mask {
+		display: block;
+		opacity: 0;
+		position: fixed;
+		z-index: 997;
+		top: 0;
+		left: 0;
+		width: 100%;
+		height: 100%;
+	}
+</style>

+ 262 - 0
components/thorui/tui-tabbar/tui-tabbar.vue

@@ -0,0 +1,262 @@
+<template>
+	<view class="tui-tabbar" :class="{ 'tui-tabbar-fixed': isFixed, 'tui-unlined': unlined }" :style="{ background: backgroundColor }">
+		<block v-for="(item, index) in tabBar" :key="index">
+			<view
+				class="tui-tabbar-item"
+				:class="{ 'tui-item-hump': item.hump }"
+				:style="{ backgroundColor: item.hump ? backgroundColor : 'none' }"
+				@tap="tabbarSwitch(index, item.hump, item.pagePath, item.verify)"
+			>
+				<view class="tui-icon-box" :class="{ 'tui-tabbar-hump': item.hump }">
+					<image :src="current == index ? item.selectedIconPath : item.iconPath" :class="[item.hump ? '' : 'tui-tabbar-icon']"></image>
+					<view :class="[item.isDot ? 'tui-badge-dot' : 'tui-badge']" :style="{ color: badgeColor, backgroundColor: badgeBgColor }" v-if="item.num">
+						{{ item.isDot ? '' : item.num }}
+					</view>
+				</view>
+				<view class="tui-text-scale" :class="{ 'tui-text-hump': item.hump }" :style="{ color: current == index ? selectedColor : color }">{{ item.text }}</view>
+			</view>
+		</block>
+		<view :class="{ 'tui-hump-box': hump }" v-if="hump && !unlined"></view>
+	</view>
+</template>
+
+<script>
+export default {
+	name: 'tuiTabbar',
+	props: {
+		//当前索引
+		current: {
+			type: Number,
+			default: 0
+		},
+		//字体颜色
+		color: {
+			type: String,
+			default: '#666'
+		},
+		//字体选中颜色
+		selectedColor: {
+			type: String,
+			default: '#5677FC'
+		},
+		//背景颜色
+		backgroundColor: {
+			type: String,
+			default: '#FFFFFF'
+		},
+		//是否需要中间凸起按钮
+		hump: {
+			type: Boolean,
+			default: false
+		},
+		//固定在底部
+		isFixed: {
+			type: Boolean,
+			default: true
+		},
+		//tabbar
+		// "pagePath": "/pages/my/my", 页面路径
+		// "text": "thor", 标题
+		// "iconPath": "thor_gray.png", 图标地址
+		// "selectedIconPath": "thor_active.png", 选中图标地址
+		// "hump": true, 是否为凸起图标
+		// "num": 2,   角标数量
+		// "isDot": true,  角标是否为圆点
+		// "verify": true  是否验证  (如登录)
+		tabBar: {
+			type: Array,
+			default() {
+				return [];
+			}
+		},
+		//角标字体颜色
+		badgeColor: {
+			type: String,
+			default: '#fff'
+		},
+		//角标背景颜色
+		badgeBgColor: {
+			type: String,
+			default: '#F74D54'
+		},
+		unlined: {
+			type: Boolean,
+			default: false
+		}
+	},
+	watch: {
+		current() {}
+	},
+	data() {
+		return {};
+	},
+	methods: {
+		tabbarSwitch(index, hump, pagePath, verify) {
+			this.$emit('click', {
+				index: index,
+				hump: hump,
+				pagePath: pagePath,
+				verify: verify
+			});
+		}
+	}
+};
+</script>
+
+<style scoped>
+.tui-tabbar {
+	width: 100%;
+	height: 100rpx;
+	display: flex;
+	align-items: center;
+	justify-content: space-between;
+	position: relative;
+}
+
+.tui-tabbar-fixed {
+	position: fixed;
+	z-index: 9999;
+	left: 0;
+	bottom: 0;
+	padding-bottom: constant(safe-area-inset-bottom);
+	padding-bottom: env(safe-area-inset-bottom);
+	box-sizing: content-box !important;
+}
+
+.tui-tabbar::before {
+	content: ' ';
+	width: 100%;
+	border-top: 1px solid #b2b2b2;
+	position: absolute;
+	top: 0;
+	left: 0;
+	transform: scaleY(0.5) translateZ(0);
+	transform-origin: 0 0;
+	display: block;
+	z-index: 3;
+}
+
+.tui-tabbar-item {
+	height: 100%;
+	flex: 1;
+	display: flex;
+	text-align: center;
+	align-items: center;
+	flex-direction: column;
+	justify-content: space-between;
+	position: relative;
+	padding: 10rpx 0;
+	box-sizing: border-box;
+	z-index: 5;
+}
+
+.tui-icon-box {
+	position: relative;
+}
+
+.tui-item-hump {
+	height: 98rpx;
+}
+
+.tui-tabbar-icon {
+	width: 52rpx;
+	height: 52rpx;
+	display: block;
+}
+
+.tui-hump-box {
+	width: 120rpx;
+	height: 120rpx;
+	background-color: #ffffff;
+	position: absolute;
+	left: 50%;
+	transform: translateX(-50%);
+	top: -50rpx;
+	border-radius: 50%;
+	z-index: 4;
+}
+
+.tui-hump-box::after {
+	content: ' ';
+	height: 200%;
+	width: 200%;
+	border: 1px solid #b2b2b2;
+	position: absolute;
+	top: 0;
+	left: 0;
+	transform: scale(0.5) translateZ(0);
+	transform-origin: 0 0;
+	border-radius: 120rpx;
+	box-sizing: border-box;
+	display: block;
+}
+
+.tui-unlined::after {
+	height: 0 !important;
+}
+
+.tui-tabbar-hump {
+	width: 100rpx;
+	height: 100rpx;
+	position: absolute;
+	left: 50%;
+	-webkit-transform: translateX(-50%) rotate(0deg);
+	transform: translateX(-50%) rotate(0deg);
+	top: -40rpx;
+	-webkit-transition: all 0.2s linear;
+	transition: all 0.2s linear;
+	border-radius: 50%;
+	z-index: 5;
+}
+
+.tui-tabbar-hump image {
+	width: 100rpx;
+	height: 100rpx;
+	display: block;
+}
+
+.tui-hump-active {
+	-webkit-transform: translateX(-50%) rotate(135deg);
+	transform: translateX(-50%) rotate(135deg);
+}
+
+.tui-text-scale {
+	font-weight: bold;
+	transform: scale(0.8);
+	font-size: 25rpx;
+	line-height: 28rpx;
+	transform-origin: center 100%;
+}
+
+.tui-text-hump {
+	position: absolute;
+	left: 50%;
+	bottom: 10rpx;
+	transform: scale(0.8) translateX(-50%);
+	transform-origin: 0 100%;
+}
+
+.tui-badge {
+	position: absolute;
+	font-size: 24rpx;
+	height: 32rpx;
+	min-width: 20rpx;
+	padding: 0 6rpx;
+	border-radius: 40rpx;
+	right: 0;
+	top: -5rpx;
+	transform: translateX(70%);
+	display: flex;
+	align-items: center;
+	justify-content: center;
+}
+
+.tui-badge-dot {
+	position: absolute;
+	height: 16rpx;
+	width: 16rpx;
+	border-radius: 50%;
+	right: -4rpx;
+	top: -4rpx;
+}
+</style>

+ 232 - 0
components/thorui/tui-tabs/tui-tabs.vue

@@ -0,0 +1,232 @@
+<template>
+	<view class="tui-tabs-view" :class="[isFixed?'tui-tabs-fixed':'tui-tabs-relative',unlined?'tui-unlined':'']" :style="{height:height+'rpx',padding:`0 ${padding}rpx`,background:backgroundColor,top:isFixed?top+'px':'auto'}">
+		<view v-for="(item,index) in tabs" :key="index" class="tui-tabs-item" :style="{width:itemWidth}" @tap.stop="swichTabs(index)">
+			<view class="tui-tabs-title" :class="{'tui-tabs-active':currentTab==index,'tui-tabs-disabled':item.disabled}" :style="{color:currentTab==index?selectedColor:color,fontSize:size+'rpx',lineHeight:size+'rpx',fontWeight:bold && currentTab==index?'bold':'normal'}">{{item.name}}</view>
+		</view>
+		<view class="tui-tabs-slider" :style="{transform:'translateX('+scrollLeft+'px)',width:sliderWidth+'rpx',height:
+	sliderHeight+'rpx',borderRadius:sliderRadius,bottom:bottom,background:sliderBgColor,marginBottom:bottom=='50%'?('-'+sliderHeight/2+'rpx'):0}"></view>
+	</view>
+</template>
+
+<script>
+	export default {
+		name: "tuiTabs",
+		props: {
+			//标签页
+			tabs: {
+				type: Array,
+				default () {
+					return []
+				}
+			},
+			//rpx
+			height: {
+				type: Number,
+				default: 80
+			},
+			//rpx 只对左右padding起作用,上下为0
+			padding: {
+				type: Number,
+				default: 30
+			},
+			//背景色
+			backgroundColor: {
+				type: String,
+				default: "#FFFFFF"
+			},
+			//是否固定
+			isFixed: {
+				type: Boolean,
+				default: false
+			},
+			//px
+			top: {
+				type: Number
+					// #ifndef H5
+					,
+				default: 0
+					// #endif
+					// #ifdef H5
+					,
+				default: 44
+				// #endif
+			},
+			//是否去掉底部线条
+			unlined: {
+				type: Boolean,
+				default: false
+			},
+			//当前选项卡
+			currentTab: {
+				type: Number,
+				default: 0
+			},
+			//滑块宽度
+			sliderWidth: {
+				type: Number,
+				default: 68
+			},
+			//滑块高度
+			sliderHeight: {
+				type: Number,
+				default: 6
+			},
+			//滑块背景颜色
+			sliderBgColor: {
+				type: String,
+				default: "#5677fc"
+			},
+			sliderRadius: {
+				type: String,
+				default: "50rpx"
+			},
+			//滑块bottom
+			bottom: {
+				type: String,
+				default: "0"
+			},
+			//标签页宽度
+			itemWidth: {
+				type: String,
+				default: "25%"
+			},
+			//字体颜色
+			color: {
+				type: String,
+				default: "#666"
+			},
+			//选中后字体颜色
+			selectedColor: {
+				type: String,
+				default: "#5677fc"
+			},
+			//字体大小
+			size: {
+				type: Number,
+				default: 28
+			},
+			//选中后 是否加粗 ,未选中则无效
+			bold: {
+				type: Boolean,
+				default: false
+			}
+		},
+		watch: {
+			currentTab() {
+				this.checkCor();
+			},
+			tabs() {
+				this.checkCor();
+			}
+		},
+		created() {
+			setTimeout(() => {
+				// 高度自适应
+				uni.getSystemInfo({
+					success: (res) => {
+						this.winWidth = res.windowWidth;
+						this.checkCor()
+					}
+				});
+			}, 50);
+		},
+		data() {
+			return {
+				winWidth: 0,
+				scrollLeft: 0
+			};
+		},
+		methods: {
+			checkCor: function() {
+				let tabsNum = this.tabs.length
+				let padding = this.winWidth / 750 * this.padding
+				let width = this.winWidth - padding * 2
+				let left = (width / tabsNum - (this.winWidth / 750 * this.sliderWidth)) / 2 + padding
+				let scrollLeft = left
+				if (this.currentTab > 0) {
+					scrollLeft = scrollLeft + (width / tabsNum) * this.currentTab
+				}
+				this.scrollLeft = scrollLeft
+			},
+			// 点击标题切换当前页时改变样式
+			swichTabs: function(index) {
+				let item = this.tabs[index]
+				if (item && item.disabled) return;
+				if (this.currentTab == index) {
+					return false;
+				} else {
+					this.$emit("change", {
+						index: Number(index)
+					})
+				}
+			}
+		}
+	}
+</script>
+
+<style scoped>
+	.tui-tabs-view {
+		width: 100%;
+		box-sizing: border-box;
+		display: flex;
+		align-items: center;
+		justify-content: space-between;
+		z-index: 9999;
+	}
+
+	.tui-tabs-relative {
+		position: relative;
+	}
+
+	.tui-tabs-fixed {
+		position: fixed;
+		left: 0;
+	}
+
+	.tui-tabs-fixed::before,
+	.tui-tabs-relative::before {
+		content: '';
+		position: absolute;
+		border-bottom: 1rpx solid #eaeef1;
+		-webkit-transform: scaleY(0.5) translateZ(0);
+		transform: scaleY(0.5) translateZ(0);
+		transform-origin: 0 100%;
+		bottom: 0;
+		right: 0;
+		left: 0;
+	}
+
+	.tui-unlined::before {
+		border-bottom: 0 !important
+	}
+
+	.tui-tabs-item {
+		display: flex;
+		align-items: center;
+		justify-content: center;
+	}
+
+	.tui-tabs-disabled {
+		opacity: .6;
+	}
+
+	.tui-tabs-title {
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		position: relative;
+		z-index: 2;
+	}
+
+	.tui-tabs-active {
+		transition: all 0.15s ease-in-out;
+	}
+
+	.tui-tabs-slider {
+		position: absolute;
+		left: 0;
+		transition: all 0.15s ease-in-out;
+		z-index: 0;
+		transform: translateZ(0);
+	}
+</style>

+ 358 - 0
components/thorui/tui-tag/tui-tag.vue

@@ -0,0 +1,358 @@
+<template>
+	<view
+		class="tui-tag"
+		:hover-class="hover ? 'tui-tag-opcity' : ''"
+		:hover-stay-time="150"
+		:class="[originLeft ? 'tui-origin-left' : '', originRight ? 'tui-origin-right' : '', getClassName(shape, plain), getTypeClass(type, plain)]"
+		:style="{ transform: `scale(${scaleMultiple})`, padding: padding, margin: margin, fontSize: size, lineHeight: size }"
+		@tap="handleClick"
+	>
+		<slot></slot>
+	</view>
+</template>
+
+<script>
+export default {
+	name: 'tuiTag',
+	props: {
+		type: {
+			type: String,
+			default: 'primary'
+		},
+		//padding
+		padding: {
+			type: String,
+			default: '16rpx 26rpx'
+		},
+		margin: {
+			type: String,
+			default: '0'
+		},
+		//文字大小 rpx
+		size: {
+			type: String,
+			default: '28rpx'
+		},
+		// circle, square,circleLeft,circleRight
+		shape: {
+			type: String,
+			default: 'square'
+		},
+		//是否空心
+		plain: {
+			type: Boolean,
+			default: false
+		},
+		//点击效果
+		hover: {
+			type: Boolean,
+			default: false
+		},
+		//缩放倍数
+		scaleMultiple: {
+			type: Number,
+			default: 1
+		},
+		originLeft: {
+			type: Boolean,
+			default: false
+		},
+		originRight: {
+			type: Boolean,
+			default: false
+		},
+		index: {
+			type: Number,
+			default: 0
+		}
+	},
+	methods: {
+		handleClick() {
+			this.$emit('click', { index: this.index });
+		},
+		getTypeClass: function(type, plain) {
+			return plain ? 'tui-' + type + '-outline' : 'tui-' + type;
+		},
+		getClassName: function(shape, plain) {
+			//circle, square,circleLeft,circleRight
+			var className = plain ? 'tui-tag-outline ' : '';
+			if (shape != 'square') {
+				if (shape == 'circle') {
+					className = className + (plain ? 'tui-tag-outline-fillet' : 'tui-tag-fillet');
+				} else if (shape == 'circleLeft') {
+					className = className + 'tui-tag-fillet-left';
+				} else if (shape == 'circleRight') {
+					className = className + 'tui-tag-fillet-right';
+				}
+			}
+			return className;
+		}
+	}
+};
+</script>
+
+<style scoped>
+/* color start*/
+
+.tui-primary {
+	background-color: #5677fc !important;
+	color: #fff;
+}
+
+.tui-light-primary {
+	background-color: #5c8dff !important;
+	color: #fff;
+}
+
+.tui-dark-primary {
+	background-color: #4a67d6 !important;
+	color: #fff;
+}
+
+.tui-dLight-primary {
+	background-color: #4e77d9 !important;
+	color: #fff;
+}
+
+.tui-danger {
+	background-color: #ed3f14 !important;
+	color: #fff;
+}
+
+.tui-red {
+	background-color: #ff201f !important;
+	color: #fff;
+}
+
+.tui-warning {
+	background-color: #ff7900 !important;
+	color: #fff;
+}
+
+.tui-green {
+	background-color: #19be6b !important;
+	color: #fff;
+}
+
+.tui-high-green {
+	background-color: #52dcae !important;
+	color: #52dcae;
+}
+
+.tui-black {
+	background-color: #000 !important;
+	color: #fff;
+}
+
+.tui-white {
+	background-color: #fff !important;
+	color: #333 !important;
+}
+
+.tui-translucent {
+	background-color: rgba(0, 0, 0, 0.7);
+}
+
+.tui-light-black {
+	background-color: #333 !important;
+}
+
+.tui-gray {
+	background-color: #ededed !important;
+}
+
+.tui-phcolor-gray {
+	background-color: #ccc !important;
+}
+
+.tui-divider-gray {
+	background-color: #eaeef1 !important;
+}
+
+.tui-btn-gray {
+	background-color: #ededed !important;
+	color: #999 !important;
+}
+
+.tui-hover-gray {
+	background-color: #f7f7f9 !important;
+}
+
+.tui-bg-gray {
+	background-color: #fafafa !important;
+}
+
+.tui-light-blue {
+	background-color: #ecf6fd;
+	color: #4dabeb !important;
+}
+
+.tui-light-brownish {
+	background-color: #fcebef;
+	color: #8a5966 !important;
+}
+
+.tui-light-orange {
+	background-color: #fef5eb;
+	color: #faa851 !important;
+}
+
+.tui-light-green {
+	background-color: #e8f6e8;
+	color: #44cf85 !important;
+}
+
+.tui-primary-outline::after {
+	border: 1px solid #5677fc !important;
+}
+
+.tui-primary-outline {
+	color: #5677fc !important;
+	background-color: none;
+}
+
+.tui-danger-outline {
+	color: #ed3f14 !important;
+	background-color: none;
+}
+
+.tui-danger-outline::after {
+	border: 1px solid #ed3f14 !important;
+}
+
+.tui-red-outline {
+	color: #ff201f !important;
+	background-color: none;
+}
+
+.tui-red-outline::after {
+	border: 1px solid #ff201f !important;
+}
+
+.tui-warning-outline {
+	color: #ff7900 !important;
+	background-color: none;
+}
+
+.tui-warning-outline::after {
+	border: 1px solid #ff7900 !important;
+}
+
+.tui-green-outline {
+	color: #44cf85 !important;
+	background-color: none;
+}
+
+.tui-green-outline::after {
+	border: 1px solid #44cf85 !important;
+}
+
+.tui-high-green-outline {
+	color: #52dcae !important;
+	background-color: none;
+}
+
+.tui-high-green-outline::after {
+	border: 1px solid #52dcae !important;
+}
+
+.tui-gray-outline {
+	color: #999 !important;
+	background-color: none;
+}
+
+.tui-gray-outline::after {
+	border: 1px solid #ccc !important;
+}
+
+.tui-black-outline {
+	color: #333 !important;
+	background-color: none;
+}
+
+.tui-black-outline::after {
+	border: 1px solid #333 !important;
+}
+
+.tui-white-outline {
+	color: #fff !important;
+	background-color: none;
+}
+
+.tui-white-outline::after {
+	border: 1px solid #fff !important;
+}
+
+/* color end*/
+
+/* tag start*/
+
+.tui-tag {
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	border-radius: 6rpx;
+	flex-shrink: 0;
+}
+
+.tui-tag-outline {
+	position: relative;
+	background-color: none;
+	color: #5677fc;
+}
+
+.tui-tag-outline::after {
+	content: '';
+	position: absolute;
+	width: 200%;
+	height: 200%;
+	-webkit-transform-origin: 0 0;
+	transform-origin: 0 0;
+	-webkit-transform: scale(0.5, 0.5);
+	transform: scale(0.5, 0.5);
+	-webkit-box-sizing: border-box;
+	box-sizing: border-box;
+	left: 0;
+	top: 0;
+	border-radius: 12rpx;
+}
+
+.tui-tag-fillet {
+	border-radius: 50rpx;
+}
+
+.tui-white.tui-tag-fillet::after {
+	border-radius: 80rpx;
+}
+
+.tui-tag-outline-fillet::after {
+	border-radius: 80rpx;
+}
+
+.tui-tag-fillet-left {
+	border-radius: 50rpx 0 0 50rpx;
+}
+
+.tui-tag-fillet-right {
+	border-radius: 0 50rpx 50rpx 0;
+}
+
+.tui-tag-fillet-left.tui-tag-outline::after {
+	border-radius: 100rpx 0 0 100rpx;
+}
+
+.tui-tag-fillet-right.tui-tag-outline::after {
+	border-radius: 0 100rpx 100rpx 0;
+}
+
+/* tag end*/
+.tui-origin-left {
+	transform-origin: 0 center;
+}
+.tui-origin-right {
+	transform-origin: 100% center;
+}
+.tui-tag-opcity {
+	opacity: 0.5;
+}
+</style>

+ 38 - 0
components/thorui/tui-time-axis/tui-time-axis.vue

@@ -0,0 +1,38 @@
+<template>
+	<view class="tui-timeaxis-class tui-time-axis">
+		<slot></slot>
+	</view>
+</template>
+
+<script>
+	export default {
+		name:"tuiTimeAxis",
+		data() {
+			return {
+
+			};
+		}
+	}
+</script>
+
+<style scoped>
+	.tui-time-axis {
+		padding-left: 20px;
+		box-sizing: border-box;
+		position: relative;
+	}
+
+	.tui-time-axis::before {
+		content: " ";
+		position: absolute;
+		left: 0;
+		top: 0;
+		width: 1px;
+		bottom: 0;
+		border-left: 1px solid #ddd;
+		-webkit-transform-origin: 0 0;
+		transform-origin: 0 0;
+		-webkit-transform: scaleX(0.5);
+		transform: scaleX(0.5);
+	}
+</style>

+ 50 - 0
components/thorui/tui-timeaxis-item/tui-timeaxis-item.vue

@@ -0,0 +1,50 @@
+<template>
+	<view class="tui-timeaxis-item">
+		<slot name="content"></slot>
+		<view class="tui-timeaxis-node" :style="{backgroundColor:backgroundColor}">
+			<slot name="node"></slot>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		name: "tuiTimeaxisItem",
+		props: {
+			//节点背景色
+			backgroundColor: {
+				type: String,
+				default: "#fafafa"
+			}
+		},
+		data() {
+			return {
+
+			};
+		}
+	}
+</script>
+
+<style scoped>
+	.tui-timeaxis-item {
+		position: relative;
+		width: 100%;
+		display: flex;
+		flex-direction: column;
+		margin-bottom: 25px;
+	}
+
+	.tui-timeaxis-node {
+		position: absolute;
+		top: 0;
+		left: -20px;
+		transform-origin: 0;
+		transform: translateX(-50%);
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		z-index: 99;
+		background-color: #fafafa;
+		font-size: 24rpx;
+	}
+</style>

+ 161 - 0
components/thorui/tui-tips/tui-tips.vue

@@ -0,0 +1,161 @@
+<template>
+	<block v-if="position=='top'">
+		<view class='tui-tips-class tui-toptips' :class="['tui-'+type,show?'tui-top-show':'']">{{msg}}</view>
+	</block>
+	<block v-else>
+		<view class='tui-tips-class tui-toast' :class="[position=='center'?'tui-centertips':'tui-bottomtips',show?'tui-toast-show':'']">
+			<view class="tui-tips-content" :class="['tui-'+type]">
+				{{msg}}
+			</view>
+		</view>
+	</block>
+</template>
+
+<script>
+	export default {
+		name: "tuiTips",
+		props: {
+			//top bottom center
+			position: {
+				type: String,
+				default: "top"
+			}
+		},
+		data() {
+			return {
+				timer: null,
+				show: false,
+				msg: "无法连接到服务器~",
+				//translucent,primary,green,warning,danger
+				type: "translucent"
+			};
+		},
+		methods: {
+			showTips: function(options) {
+				const {
+					type = 'translucent',
+						duration = 2000
+				} = options;
+				clearTimeout(this.timer);
+				this.show = true;
+				// this.duration = duration < 2000 ? 2000 : duration;
+				this.type = type;
+				this.msg = options.msg;
+				this.timer = setTimeout(() => {
+					this.show = false;
+					clearTimeout(this.timer);
+					this.timer = null;
+				}, duration);
+			}
+		}
+	}
+</script>
+
+<style scoped>
+	/*顶部消息提醒 start*/
+	.tui-toptips {
+		width: 100%;
+		padding: 18rpx 30rpx;
+		box-sizing: border-box;
+		position: fixed;
+		z-index: 9999;
+		color: #fff;
+		font-size: 30rpx;
+		left: 0;
+		top: 0;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		word-break: break-all;
+		opacity: 0;
+		transform: translateZ(0) translateY(-100%);
+		transition: all 0.3s ease-in-out;
+	}
+
+	.tui-top-show {
+		transform: translateZ(0) translateY(0);
+		opacity: 1;
+	}
+
+	/*顶部消息提醒 end*/
+
+	/*toast消息提醒 start*/
+
+	/*注意问题:
+ 1、fixed 元素宽度无法自适应,所以增加了子元素
+ 2、fixed 和 display冲突导致动画效果消失,暂时使用visibility替代
+*/
+	.tui-toast {
+		width: 80%;
+		box-sizing: border-box;
+		color: #fff;
+		font-size: 28rpx;
+		position: fixed;
+		visibility: hidden;
+		opacity: 0;
+		left: 50%;
+		transition: all 0.3s ease-in-out;
+		z-index: 9999;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+	}
+
+	.tui-toast-show {
+		visibility: visible;
+		opacity: 1;
+	}
+
+	.tui-tips-content {
+		word-wrap: break-word;
+		word-break: break-all;
+		border-radius: 8rpx;
+		padding: 18rpx 30rpx;
+	}
+
+	/*底部消息提醒 start*/
+	.tui-bottomtips {
+		bottom: 120rpx;
+		-webkit-transform: translateX(-50%);
+		transform: translateX(-50%);
+	}
+
+	/*底部消息提醒 end*/
+
+	/*居中消息提醒 start*/
+	.tui-centertips {
+		top: 50%;
+		-webkit-transform: translate(-50%, -50%);
+		transform: translate(-50%, -50%);
+	}
+
+	/*居中消息提醒 end*/
+
+	/*toast消息提醒 end*/
+
+	/*背景颜色 start*/
+
+	.tui-primary {
+		background-color: #5677fc;
+	}
+
+	.tui-green {
+		background-color: #19be6b;
+	}
+
+	.tui-warning {
+		background-color: #ff7900;
+	}
+
+	.tui-danger {
+		background-color: #ed3f14;
+	}
+
+	.tui-translucent {
+		background-color: rgba(0, 0, 0, 0.7);
+	}
+
+	/*背景颜色 end*/
+
+	/*消息提醒 end*/
+</style>

+ 118 - 0
components/thorui/tui-toast/tui-toast.vue

@@ -0,0 +1,118 @@
+<template>
+	<view class="tui-toast" :class="[visible?'tui-toast-show':'',content?'tui-toast-padding':'',icon?'':'tui-unicon-padding']" :style="{width:getWidth(icon,content)}">
+		<image :src="imgUrl" class="tui-toast-img" v-if="icon"></image>
+		<view class="tui-toast-text" :class="[icon?'':'tui-unicon']">{{title}}</view>
+		<view class="tui-toast-text tui-content-ptop" v-if="content && icon">{{content}}</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		name: "tuiToast",
+		props: {
+		},
+		data() {
+			return {
+				timer: null,
+				//是否显示
+				visible: false,
+				//显示标题
+				title: "操作成功",
+				//显示内容
+				content: "",
+				//是否有icon
+				icon:false,
+				imgUrl:""
+			};
+		},
+		methods: {
+			show: function(options) {
+				let {
+					duration = 2000,
+					icon=false
+				} = options;
+				clearTimeout(this.timer);
+				this.visible = true;
+				this.title = options.title || "";
+				this.content = options.content || "";
+				this.icon=icon;
+				if(icon && options.imgUrl){
+					this.imgUrl=options.imgUrl
+				}
+				this.timer = setTimeout(() => {
+					this.visible = false;
+					clearTimeout(this.timer);
+					this.timer = null;
+				}, duration);
+			},
+			getWidth(icon,content){
+				let width="auto";
+				if(icon){
+					width=content?'420rpx':'360rpx'
+				}
+				return width
+			}
+		}
+	}
+</script>
+
+<style scoped>
+	.tui-toast {
+		background-color: rgba(0, 0, 0, 0.75);
+		border-radius: 10rpx;
+		position: fixed;
+		visibility: hidden;
+		opacity: 0;
+		left: 50%;
+		top: 48%;
+		-webkit-transform: translate(-50%, -50%);
+		transform: translate(-50%, -50%);
+		transition:  0.3s ease-in-out;
+		transition-property:opacity,visibility;
+		z-index: 9999;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		flex-direction: column;
+		padding: 60rpx 20rpx 54rpx 20rpx;
+		box-sizing: border-box;
+	}
+
+	.tui-toast-padding {
+		padding-top: 50rpx !important;
+		padding-bottom: 50rpx !important;
+	}
+	.tui-unicon-padding {
+		padding: 24rpx 40rpx !important;
+		word-break: break-all;
+	}
+
+	.tui-toast-show {
+		visibility: visible;
+		opacity: 1;
+	}
+
+
+	.tui-toast-img {
+		width: 120rpx;
+		height: 120rpx;
+		display: block;
+		margin-bottom: 28rpx;
+	}
+
+	.tui-toast-text {
+		font-size: 30rpx;
+		line-height: 30rpx;
+		font-weight: 400;
+		color: #fff;
+		text-align: center;
+	}
+	.tui-unicon{
+		line-height: 40rpx !important;
+		font-size: 32rpx !important;
+	}
+	.tui-content-ptop {
+		padding-top: 10rpx;
+		font-size: 26rpx !important;
+	}
+</style>

+ 104 - 0
components/thorui/tui-top-dropdown/tui-top-dropdown.vue

@@ -0,0 +1,104 @@
+<template>
+	<view>
+		<view
+			class="tui-top-dropdown tui-dropdown-box"
+			:class="[show ? 'tui-dropdown-show' : '']"
+			:style="{
+				height: height ? px(height) : 'auto',
+				backgroundColor: backgroundColor,
+				paddingBottom: px(paddingbtm),
+				transform: 'translateZ(0) translateY(' + (show ? px(translatey) : '-100%') + ')'
+			}"
+		>
+			<slot></slot>
+		</view>
+		<view @touchmove.stop.prevent class="tui-dropdown-mask" :class="[mask && show ? 'tui-mask-show' : '']" @tap="handleClose"></view>
+	</view>
+</template>
+
+<script>
+export default {
+	name: 'tuiTopDropdown',
+	props: {
+		//是否需要mask
+		mask: {
+			type: Boolean,
+			default: true
+		},
+		//控制显示
+		show: {
+			type: Boolean,
+			default: false
+		},
+		//背景颜色
+		backgroundColor: {
+			type: String,
+			default: '#f2f2f2'
+		},
+		//padding-bottom  rpx
+		paddingbtm: {
+			type: Number,
+			default: 0
+		},
+		//高度 rpx
+		height: {
+			type: Number,
+			default: 580
+		},
+		//移动距离 需要计算
+		translatey: {
+			type: Number,
+			default: 0
+		}
+	},
+	methods: {
+		handleClose() {
+			if (!this.show) {
+				return;
+			}
+			this.$emit('close', {});
+		},
+		px(num) {
+			return uni.upx2px(num) + 'px';
+		}
+	}
+};
+</script>
+
+<style scoped>
+.tui-dropdown-box {
+	width: 100%;
+	position: fixed;
+	box-sizing: border-box;
+	border-bottom-right-radius: 24rpx;
+	border-bottom-left-radius: 24rpx;
+	transform: translateZ(0);
+	overflow: hidden;
+	/* visibility: hidden; */
+	transition: all 0.3s ease-in-out;
+	z-index: 996;
+	top: 0;
+}
+
+.tui-dropdown-show {
+	/* visibility: visible; */
+}
+
+.tui-dropdown-mask {
+	position: fixed;
+	top: 0;
+	left: 0;
+	right: 0;
+	bottom: 0;
+	background-color: rgba(0, 0, 0, 0.6);
+	z-index: 986;
+	transition: all 0.3s ease-in-out;
+	opacity: 0;
+	visibility: hidden;
+}
+
+.tui-mask-show {
+	opacity: 1;
+	visibility: visible;
+}
+</style>

+ 341 - 0
components/thorui/tui-upload/tui-upload.vue

@@ -0,0 +1,341 @@
+<template>
+	<view class="tui-container">
+		<view class="tui-upload-box">
+			<view class="tui-image-item" v-for="(item,index) in imageList" :key="index">
+				<image :src="item" class="tui-item-img" @tap.stop="previewImage(index)" mode="aspectFill"></image>
+				<view v-if="!forbidDel" class="tui-img-del" @tap.stop="delImage(index)"></view>
+				<view v-if="statusArr[index]!=1" class="tui-upload-mask">
+					<view class="tui-upload-loading" v-if="statusArr[index]==2"></view>
+					<text class="tui-tips">{{statusArr[index]==2?'上传中...':'上传失败'}}</text>
+					<view class="tui-mask-btn" v-if="statusArr[index]==3" @tap.stop="reUpLoad(index)" hover-class="tui-btn-hover"
+					 :hover-stay-time="150">重新上传</view>
+				</view>
+			</view>
+			<view v-if="isShowAdd" class="tui-upload-add" @tap="chooseImage">
+				<view class="tui-upload-icon tui-icon-plus"></view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		name: 'tuiUpload',
+		props: {
+			//初始化图片路径
+			value: {
+				type: Array,
+				default () {
+					return []
+				}
+			},
+			//禁用删除
+			forbidDel: {
+				type: Boolean,
+				default: false
+			},
+			//禁用添加
+			forbidAdd: {
+				type: Boolean,
+				default: false
+			},
+			//服务器地址
+			serverUrl: {
+				type: String,
+				default: ""
+			},
+			//限制数
+			limit: {
+				type: Number,
+				default: 9
+			},
+			//项目名,默认为 file
+			fileKeyName: {
+				type: String,
+				default: "file"
+			}
+		},
+		data() {
+			return {
+				//图片地址
+				imageList: [],
+				//上传状态:1-上传成功 2-上传中 3-上传失败
+				statusArr: []
+			}
+		},
+		created() {
+			this.imageList = [...this.value];
+			for (let item of this.imageList) {
+				this.statusArr.push("1")
+			}
+		},
+		computed: {
+			isShowAdd() {
+				let isShow = true;
+				if (this.forbidAdd || (this.limit && this.imageList.length >= this.limit)) {
+					isShow = false;
+				}
+				return isShow
+			}
+		},
+		methods: {
+			// 重新上传
+			reUpLoad(index) {
+				this.$set(this.statusArr, index, "2")
+				this.change()
+				this.uploadImage(index, this.imageList[index]).then(() => {
+					this.change()
+				}).catch(() => {
+					this.change()
+				})
+			},
+			change() {
+				let status = ~this.statusArr.indexOf("2") ? 2 : 1
+				if (status != 2 && ~this.statusArr.indexOf("3")) {
+					// 上传失败
+					status = 3
+				}
+				this.$emit('complete', {
+					status: status,
+					imgArr: this.imageList
+				})
+			},
+			chooseImage: function() {
+				let _this = this;
+				uni.chooseImage({
+					count: _this.limit - _this.imageList.length,
+					success: function(e) {
+						let imageArr = [];
+						for (let i = 0; i < e.tempFilePaths.length; i++) {
+							let len = _this.imageList.length;
+							if (len >= _this.limit) {
+								uni.showToast({
+									title: `最多可上传${_this.limit}张图片`,
+									icon: "none"
+								});
+								break;
+							}
+							let path = e.tempFilePaths[i]
+							imageArr.push(path)
+							_this.imageList.push(path)
+							_this.statusArr.push("2")
+						}
+						_this.change()
+
+						let start = _this.imageList.length - imageArr.length
+						for (let j = 0; j < imageArr.length; j++) {
+							let index = start + j
+							//服务器地址
+							if (_this.serverUrl) {
+								_this.uploadImage(index, imageArr[j]).then(() => {
+									_this.change()
+								}).catch(() => {
+									_this.change()
+								})
+							} else {
+								//无服务器地址则直接返回成功
+								_this.$set(_this.statusArr, index, "1")
+								_this.change()
+							}
+						}
+					}
+				})
+			},
+			uploadImage: function(index, url) {
+				let _this = this;
+				return new Promise((resolve, reject) => {
+					uni.uploadFile({
+						url: this.serverUrl,
+						name: this.fileKeyName,
+						header: {
+							//设置请求头
+						},
+						formData: {},
+						filePath: url,
+						success: function(res) {
+							console.log(res)
+							if (res.statusCode == 200) {
+								//返回结果 此处需要按接口实际返回进行修改
+								let d = JSON.parse(res.data.replace(/\ufeff/g, "") || "{}")
+								//判断code,以实际接口规范判断
+								if (d.code % 100 === 0) {
+									// 上传成功 d.url 为上传后图片地址,以实际接口返回为准
+									d.url && (_this.imageList[index] = d.url)
+									_this.$set(_this.statusArr, index, d.url ? "1" : "3")
+								} else {
+									// 上传失败
+									_this.$set(_this.statusArr, index, "3")
+								}
+								resolve(index)
+							} else {
+								_this.$set(_this.statusArr, index, "3")
+								reject(index)
+							}
+						},
+						fail: function(res) {
+							_this.$set(_this.statusArr, index, "3")
+							reject(index)
+						}
+					})
+				})
+
+			},
+			delImage: function(index) {
+				this.imageList.splice(index, 1)
+				this.statusArr.splice(index, 1)
+				this.$emit("remove", {
+					index: index
+				})
+				this.change()
+			},
+			previewImage: function(index) {
+				if (!this.imageList.length) return;
+				uni.previewImage({
+					current: this.imageList[index],
+					loop: true,
+					urls: this.imageList
+				})
+			}
+		}
+	}
+</script>
+
+<style scoped>
+	@font-face {
+		font-family: 'tuiUpload';
+		src: url(data:application/font-woff;charset=utf-8;base64,d09GRgABAAAAAATcAA0AAAAAByQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABGRlRNAAAEwAAAABoAAAAciR52BUdERUYAAASgAAAAHgAAAB4AKQALT1MvMgAAAaAAAABCAAAAVjxvR/tjbWFwAAAB+AAAAEUAAAFK5ibpuGdhc3AAAASYAAAACAAAAAj//wADZ2x5ZgAAAkwAAADXAAABAAmNjcZoZWFkAAABMAAAAC8AAAA2FpiS+WhoZWEAAAFgAAAAHQAAACQH3QOFaG10eAAAAeQAAAARAAAAEgwAACBsb2NhAAACQAAAAAwAAAAMAEoAgG1heHAAAAGAAAAAHwAAACABEgA2bmFtZQAAAyQAAAFJAAACiCnmEVVwb3N0AAAEcAAAACgAAAA6OMUs4HjaY2BkYGAAYo3boY/i+W2+MnCzMIDAzb3qdQj6fwPzf+YGIJeDgQkkCgA/KAtvAHjaY2BkYGBu+N/AEMPCAALM/xkYGVABCwBZ4wNrAAAAeNpjYGRgYGBl0GJgZgABJiDmAkIGhv9gPgMADTABSQB42mNgZGFgnMDAysDA1Ml0hoGBoR9CM75mMGLkAIoysDIzYAUBaa4pDA7PGJ9xMjf8b2CIYW5gaAAKM4LkANt9C+UAAHjaY2GAABYIVmBgAAAA+gAtAAAAeNpjYGBgZoBgGQZGBhBwAfIYwXwWBg0gzQakGRmYnjE+4/z/n4EBQksxSf6GqgcCRjYGOIeRCUgwMaACRoZhDwCiLwmoAAAAAAAAAAAAAAAASgCAeNpdjkFKw0AARf/vkIR0BkPayWRKQZtYY90ohJju2kOIbtz0KD1HVm50UfEmWXoAr9ADOHFARHHzeY//Fx8Ci+FJfIgdJFa4AhgiMshbrCuIsLxhFJZVs+Vl1bT1GddtbXTC3OhohN4dg4BJ3zMJAnccyfm468ZzHXddrH9ZKbHzdf9n/vkY/xv9sPQXgGEvBrHHwst5kTbXLE+YpYVPkxepPmW94W16UbdNJd6f3SAzo5W7m1jaKd+8ZZIvk5nlKw9SK6Wle7BLS3f/bTzQLmfAF2T1NsQAeNp9kD1OAzEQhZ/zByQSQiCoXVEA2vyUKRMp9Ailo0g23pBo1155nUg5AS0VB6DlGByAGyDRcgpelkmTImvt6PObmeexAZzjGwr/3yXuhBWO8ShcwREy4Sr1F+Ea+V24jhY+hRvUf4SbuFUD4RYu1BsdVO2Eu5vSbcsKZxgIV3CKJ+Eq9ZVwjfwqXMcVPoQb1L+EmxjjV7iFa2WpDOFhMEFgnEFjig3jAjEcLJIyBtahOfRmEsxMTzd6ETubOBso71dilwMeaDnngCntPbdmvkon/mDLgdSYbh4FS7YpjS4idCgbXyyc1d2oc7D9nu22tNi/a4E1x+xRDWzU/D3bM9JIbAyvkJI18jK3pBJTj2hrrPG7ZynW814IiU68y/SIx5o0dTr3bmniwOLn8owcfbS5kj33qBw+Y1kIeb/dTsQgil2GP5PYcRkAAAB42mNgYoAALjDJyIAOWMGiTIxMjMxsKak5qSWpbFmZiRmJ+QAmgAUIAAAAAf//AAIAAQAAAAwAAAAWAAAAAgABAAMABAABAAQAAAACAAAAAHjaY2BgYGQAgqtL1DlA9M296nUwGgA+8QYgAAA=) format('woff');
+		font-weight: normal;
+		font-style: normal;
+	}
+
+	.tui-upload-icon {
+		font-family: "tuiUpload" !important;
+		font-style: normal;
+		-webkit-font-smoothing: antialiased;
+		-moz-osx-font-smoothing: grayscale;
+		padding: 10rpx;
+	}
+
+	.tui-icon-delete:before {
+		content: "\e601";
+	}
+
+	.tui-icon-plus:before {
+		content: "\e609";
+	}
+
+	.tui-upload-box {
+		width: 100%;
+		display: flex;
+		flex-wrap: wrap;
+	}
+
+	.tui-upload-add {
+		width: 220rpx;
+		height: 220rpx;
+		font-size: 68rpx;
+		font-weight: 100;
+		color: #888;
+		background-color: #F7F7F7;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		padding: 0;
+	}
+
+	.tui-image-item {
+		width: 220rpx;
+		height: 220rpx;
+		position: relative;
+		margin-right: 20rpx;
+		margin-bottom: 20rpx;
+	}
+
+	.tui-image-item:nth-of-type(3n) {
+		margin-right: 0;
+	}
+
+	.tui-item-img {
+		width: 220rpx;
+		height: 220rpx;
+		display: block;
+	}
+
+	.tui-img-del {
+		width: 36rpx;
+		height: 36rpx;
+		position: absolute;
+		right: -12rpx;
+		top: -12rpx;
+		background-color: #EB0909;
+		border-radius: 50%;
+		color: white;
+		font-size: 34rpx;
+		z-index: 999;
+	}
+
+	.tui-img-del::before {
+		content: '';
+		width: 16rpx;
+		height: 1px;
+		position: absolute;
+		left: 10rpx;
+		top: 18rpx;
+		background-color: #fff;
+	}
+
+	.tui-upload-mask {
+		width: 100%;
+		height: 100%;
+		position: absolute;
+		left: 0;
+		top: 0;
+		display: flex;
+		flex-direction: column;
+		align-items: center;
+		justify-content: space-around;
+		padding: 40rpx 0;
+		box-sizing: border-box;
+		background-color: rgba(0, 0, 0, 0.6);
+	}
+
+	.tui-upload-loading {
+		width: 28rpx;
+		height: 28rpx;
+		border-radius: 50%;
+		border: 2px solid;
+		border-color: #B2B2B2 #B2B2B2 #B2B2B2 #fff;
+		animation: tui-rotate 0.7s linear infinite;
+	}
+
+	@keyframes tui-rotate {
+		0% {
+			transform: rotate(0);
+		}
+
+		100% {
+			transform: rotate(360deg);
+		}
+	}
+
+	.tui-tips {
+		font-size: 26rpx;
+		color: #fff;
+	}
+
+	.tui-mask-btn {
+		padding: 4rpx 16rpx;
+		border-radius: 40rpx;
+		text-align: center;
+		font-size: 24rpx;
+		color: #fff;
+		border: 1rpx solid #fff;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+	}
+
+	.tui-btn-hover {
+		opacity: 0.8;
+	}
+</style>

+ 6 - 0
pages.json

@@ -553,6 +553,12 @@
 			}
 		]
 	},
+	"easycom": {
+		"autoscan": true,
+		"custom": {
+			"tui-(.*)": "@/components/thorui/tui-$1/tui-$1.vue"
+		}
+	},
 	"globalStyle": {
 		"navigationBarTextStyle": "black",
 		"navigationBarTitleText": "采美采购商城",

+ 3 - 4
pages/goods/cart.vue

@@ -128,7 +128,6 @@
 	import headerNavbar from "@/components/cm-module/headerNavbar/headerNavbar.vue" //顶部自定义胶囊'
 	import modalLayer from "@/components/modal-layer"
 	import { mapState,mapMutations } from 'vuex';
-	import { shoppingCartUpdate,shoppingCartDelete } from "@/api/cart.js" 
 	
 	export default{
 		components:{
@@ -533,7 +532,7 @@
 			},
 			updateShoppogNum(pros){//加减购物车商品更新到后台
 				let params ={userID:this.userID,productID:pros.productID,productCount:pros.productCount}
-				shoppingCartUpdate(params).then(response =>{
+				this.ProductService.shoppingCartUpdate(params).then(response =>{
 					this.isshowDelbtn = false;
 					this.initGetCartGoodsList();
 				}).catch(error =>{
@@ -656,7 +655,7 @@
 					return
 				}else{			
 					this.$util.modal('','确定删除选中的商品吗?','确定','取消',true,() =>{
-						shoppingCartDelete({userID:this.userID,productIDs:this.delGoodsList}).then(response =>{
+						this.ProductService.shoppingCartDelete({userID:this.userID,productIDs:this.delGoodsList}).then(response =>{
 							this.$util.msg('删除成功',2000);
 							setTimeout(()=>{
 								this.isshowDelbtn = false;
@@ -671,7 +670,7 @@
 			deletefailureList(){
 				this.failureList.forEach(failure=>{ this.delGoodsList += failure.productID+',' })
 				this.$util.modal('','确定清空全部失效商品吗?','确定','取消',true,() =>{
-					shoppingCartDelete({userID:this.userID,productIDs:this.delGoodsList}).then(response =>{
+					this.ProductService.shoppingCartDelete({userID:this.userID,productIDs:this.delGoodsList}).then(response =>{
 						this.$util.msg('删除成功',2000);
 						setTimeout(()=>{
 							this.isshowDelbtn = false;

+ 86 - 81
pages/goods/product.vue

@@ -32,21 +32,17 @@
 						<view class="p-title tui-skeleton-fillet">
 							{{product.name == undefined ? '' : product.name}}
 						</view>
-						<view class="productRemarks" v-if="product.productRemarks!=''&&product.productRemarks!=null">
-							<view class="">{{product.productRemarks}}</view>
+						<view class="productRemarks"  v-if="product.productRemarks!=''&&product.productRemarks!=null">
+							<text class="text">{{product.productRemarks}}</text>
 						</view>
 						<view class="wrap-label" v-if="product.tagsList.length>0">
 							<view class="label-a tui-skeleton-fillet" v-for="(label,index) in  product.tagsList" :key="index">{{label}}</view>
 						</view>
 						<view class="wrap-top-price" v-if="!goodsData.isNoneDisabled">
-							<view class="cm-price">
-								<cm-price v-if="isRequest"
-										  :product="product"
-										  :userIdentity="userIdentity"
-										  :ladderPriceList="ladderPriceList"
-										  :retailPrice="retailPrice"
-										  :minBuyNumber="minBuyNumber"/>
-							</view>
+							<cm-price v-if="isRequest"
+									  :product="product"
+									  :userIdentity="userIdentity"
+									  :ladderPriceList="ladderPriceList"/>
 						</view>
 					</view>
 					<view class="attributes">
@@ -583,9 +579,23 @@
 		background-color: #F7F7F7;
 	}
 	.productRemarks{
-		color: #FF2A2A;
-		font-size: 24rpx;
-		margin-bottom: 16rp
+		height: 36rpx;
+		margin: 8rpx 0;
+		width: 100%;
+		float: left;
+		.text{
+			display: inline-block;
+			padding: 0 18rpx;
+			line-height: 32rpx;
+			font-size: 20rpx;
+			color: #FF2A2A;
+			text-align: center;
+			border-radius: 4rpx;
+			background: #FFFFFF;
+			border: 1px solid #FF2A2A;
+			float: left;
+			opacity: 0.8;
+		}
 	}
 	.banner-section{
 		width: 100%;
@@ -645,7 +655,7 @@
 			padding: 0 24rpx;
 			height: auto;
 			float: left;
-			padding-bottom: 24rpx;
+			padding-bottom:20rpx;
 			border-bottom: 1px solid #F8F8F8;
 			&.none{
 				.p-title{
@@ -665,52 +675,10 @@
 				-webkit-line-clamp: 2;
 				overflow: hidden;
 			}
-			.wrap-main-act{
-				width: 100%;
-				height: 56rpx;
-				float: left;
-				margin-top: 14rpx;
-				.floor-item-act{
-					height: 56rpx;
-					text-align: center;
-					box-sizing: border-box;
-					float: left;
-					padding: 0 0 20rpx 0;
-					.floor-tags{
-						float: right;
-						height: 36rpx;
-						border-radius: 4rpx;
-						background-color: rgba(225, 86, 22, 0.1);
-						line-height: 36rpx;
-						color: $color-system;
-						text-align: center;
-						display: inline-block;
-						padding:0 16rpx;
-						font-size: $font-size-20;
-					}
-				}
-				.floor-item-btn{
-					float: right;
-					height: 56rpx;
-					.btn{
-						line-height: 56rpx;
-						padding: 0 20rpx;
-						height: 56rpx;
-						background: $btn-confirm;
-						color: #FFFFFF;
-						font-size: $font-size-20;
-						border-radius: 4rpx;
-					}
-				}
-			}
-			.wrap-main-pricenone{
+			.wrap-main-text{
 				line-height: 44rpx;
 				color: #FF2A2A;
 				font-size: $font-size-26;
-				font-weight: normal;
-				float: left;
-			}
-			.wrap-main-text{
 				display: block;
 				float: left;
 				font-weight: normal;
@@ -728,26 +696,7 @@
 				text-align: center;
 				font-size: $font-size-24;
 			}
-			.p-price{
-				height: 44rpx;
-				line-height: 44rpx;
-				float: left;
-				color: #FF2A2A;
-				&.none{
-					text-decoration: line-through;
-					color: #999999;
-					
-				}
-				.txt{
-					margin:0 2rpx;
-				}
-				.txt.sm{
-					font-size: $font-size-24;
-				}
-				.txt.big{
-					font-size: $font-size-32;
-				}
-			}
+			
 			.p-price-none{
 				height: 44rpx;
 				line-height: 44rpx;
@@ -772,8 +721,8 @@
 				}
 			}
 			.p-login{
-				height: 44rpx;
-				line-height: 44rpx;
+				height: 56rpx;
+				line-height: 56rpx;
 				color: $color-system;
 				font-size: $font-size-24;
 				&.grade{
@@ -818,7 +767,6 @@
 			float: left;
 			width: 100%;
 			box-sizing: border-box;
-			margin-top: 10rpx;
 			.label-a{
 				padding: 0 18rpx;
 				line-height: 32rpx;
@@ -827,7 +775,7 @@
 				text-align: center;
 				border-radius: 16rpx;
 				background:#FFFFFF;
-				margin: 0 22rpx 22rpx 0;
+				margin: 0 20rpx 15rpx 0;
 				display: inline-block;
 				border: 1px solid #EDEDED;
 			}
@@ -836,6 +784,63 @@
 			float: left;
 			width: 100%;
 			box-sizing: border-box;
+			.wrap-main-item{
+				width: 100%;
+				height: 56rpx;
+				.p-price{
+					height: 56rpx;
+					line-height: 56rpx;
+					float: left;
+					color: #FF2A2A;
+					&.none{
+						text-decoration: line-through;
+						color: #999999;
+					}
+					.txt{
+						margin:0 2rpx;
+					}
+					.txt.sm{
+						font-size: $font-size-24;
+					}
+					.txt.big{
+						font-size: $font-size-32;
+					}
+				}
+			}
+			.floor-item-act{
+				height: 56rpx;
+				text-align: center;
+				box-sizing: border-box;
+				float: left;
+				padding:10rpx 0;
+				margin-left: 10rpx;
+				.floor-tags{
+					float: right;
+					height: 36rpx;
+					border-radius: 4rpx;
+					background-color: rgba(225, 86, 22, 0.1);
+					line-height: 36rpx;
+					color: $color-system;
+					text-align: center;
+					display: inline-block;
+					padding:0 16rpx;
+					font-size: $font-size-20;
+				}
+			}
+			.floor-item-btn{
+				float: right;
+				height: 40rpx;
+				margin-top: 8rpx;
+				.btn{
+					line-height: 40rpx;
+					padding: 0 20rpx;
+					height: 40rpx;
+					background: $btn-confirm;
+					color: #FFFFFF;
+					font-size: $font-size-20;
+					border-radius: 4rpx;
+				}
+			}
 		}
 		.wrap-info{
 			float: left;

+ 39 - 41
pages/tabBar/cart/cart.vue

@@ -39,7 +39,7 @@
 													{{pros.price | NumFormat}}
 												</view>
 												<view class="count" v-if="pros.validFlag == '2'"  :class="[isshowDelbtn ? 'none':'show']">
-													<text class="count-tips" v-if="pros.number<pros.minBuyNumber">起订量:{{pros.minBuyNumber}}</text>
+													<text class="count-tips" v-if="pros.number<pros.min">起订量:{{pros.min}}</text>
 													<text class="count-tips step" v-if="pros.isStep">购买量必须为起订量的整数倍</text>
 													<view class="number-box">
 														<view  class="iconfont icon-jianhao" :class="[pros.validFlag == '3'?'disabled':'']" @click="changeCountSub(item,pros)"></view>
@@ -53,7 +53,7 @@
 								</view>
 							</view>	
 							<view class="goods-pros-b" :class="[isshowDelbtn ? 'none':'show']" >
-								<view class="sum">合计:<text class="money"><text class="money-sign">¥</text>{{item.totalPrice.toFixed(2)}}</text></view>
+								<view class="sum">合计:<text class="money"><text class="money-sign">¥</text>{{item.totalPrice | NumFormat}}</text></view>
 							</view>
 						</view>
 					</view>	
@@ -126,7 +126,6 @@
 	import tuiNomore from "@/components/tui-components/nomore/nomore"
 	import modalLayer from "@/components/modal-layer"
 	import { mapState,mapMutations } from 'vuex';
-	import { shoppingCartUpdate,shoppingCartDelete } from "@/api/cart.js" 
 	
 	export default{
 		components:{
@@ -169,16 +168,14 @@
 		onLoad(){
 			this.setScrollHeight();
 		},
-		filters: {
-			NumFormat:function(text) {//处理金额
-				return Number(text).toFixed(2);
-			},
-		},
 		computed: {
 			...mapState(['hasLogin','userInfo'])
 		},
-		filters:{//单件商品的价格 × 数量
-		    totalprice(val,count){
+		filters:{
+			NumFormat(value) {//处理金额
+				return Number(value).toFixed(2);
+			},
+		    totalprice(val,count){//单件商品的价格 × 数量
 		        return (val * count).toFixed(2)
 		    }
 		},	
@@ -228,15 +225,15 @@
 					if(data.list && data.list.length > 0 ){
 						this.goodsList = data.list;
 						this.goodsList.forEach((item,index) => {
-							let productsListLength = item.productsList.length,
+							let productsListLength = item.cartList.length,
 								invalidLength = 0;
-							item.productsList.forEach(pros => {
+							item.cartList.forEach(pros => {
 								pros.shopID = item.shopID;
 								pros.isStep = false
 								if(pros.validFlag == '3' ) {invalidLength++;}
 								if(pros.step === 2){ 
-									if(pros.number % pros.minBuyNumber != 0){
-										pros.number = pros.minBuyNumber
+									if(pros.number % pros.min != 0){
+										pros.number = pros.min
 										this.totalShopPeice()
 										this.updateShoppogNum(pros)
 									}
@@ -275,7 +272,7 @@
 				this.updateCheckAllBtn();
 			},
 			updateProductCheckedAllBtn(item) {// 单独每个供应商的勾选判断
-				let productsList = item.productsList,
+				let productsList = item.cartList,
 					productsCheckedLength = 0,
 					disabledLength = 0;
 				if(this.isshowDelbtn) {
@@ -320,7 +317,7 @@
 				this.updateCheckAllBtn();
 			},
 			setProductChecked(item) {
-				let products = item.productsList;
+				let products = item.cartList;
 				products.forEach(pros=>{
 					if(item.checked) {
 						if(pros.validFlag =='3'){
@@ -366,7 +363,7 @@
 				let validList=[];
 				let productsList=[];
 				this.goodsList.map((item,index)=>{
-					productsList=item.productsList;
+					productsList=item.cartList;
 					prosPrice=productsList.reduce((p,e)=>p+e.retailPrice*e.number,0);
 					productsList.forEach(pros =>{
 						if(pros.validFlag =='3'){
@@ -386,7 +383,7 @@
 				let validList =[];
 				let productsList =[];
 			    this.goodsList.forEach((item,index)=>{
-					productsList=item.productsList;
+					productsList=item.cartList;
 					productsList.forEach(pros=>{	
 						if(pros.productsChecked){
 							prosPrice+=pros.retailPrice*pros.number;
@@ -408,7 +405,7 @@
 				let validList =[];
 				let productsList =[];
 			    this.goodsList.forEach(item=>{
-					productsList = item.productsList
+					productsList = item.cartList
 					productsList.forEach(pros=>{
 						if(pros.productsChecked){
 							prosAllCount+=parseInt(pros.number);
@@ -428,7 +425,7 @@
 			changeCountAdd(item,pros){//商品数量加加
 				if(pros.step === 2){
 					pros.isStep = false
-					pros.number += pros.minBuyNumber
+					pros.number += pros.min
 					this.processActivityPrice(pros)
 					this.isStock = false
 				}else{
@@ -440,14 +437,14 @@
 				this.totalShopPeice();
 			},
 			changeCountSub(item,pros){//商品数量减减
-				if(pros.number<=pros.minBuyNumber){
-					pros.number= pros.minBuyNumber
-					this.$util.msg(`该商品最小起订量为${pros.minBuyNumber}`,2000);
+				if(pros.number<=pros.min){
+					pros.number= pros.min
+					this.$util.msg(`该商品最小起订量为${pros.min}`,2000);
 					return
 				}else{
 					if(pros.step === 2){
 						pros.isStep = false
-						pros.number -= pros.minBuyNumber
+						pros.number -= pros.min
 					}else{
 						pros.number--
 					}
@@ -463,13 +460,13 @@
 				
 				let _value = Number(e.detail.value);
 				if(!this.$api.isNumber(_value)){
-					pros.number = pros.minBuyNumber
-				}else if(_value < pros.minBuyNumber){	
-					this.$util.msg(`该商品最小起订量为${pros.minBuyNumber}`,2000);
-					pros.number = pros.minBuyNumber
-				}else if(_value % pros.minBuyNumber != 0){
+					pros.number = pros.min
+				}else if(_value < pros.min){	
+					this.$util.msg(`该商品最小起订量为${pros.min}`,2000);
+					pros.number = pros.min
+				}else if(_value % pros.min != 0){
 					pros.isStep = true
-					pros.number = pros.minBuyNumber
+					pros.number = pros.min
 				}else{
 					pros.isStep = false
 					pros.number = e.detail.value
@@ -491,8 +488,9 @@
 				}
 			},
 			updateShoppogNum(pros){//加减购物车商品更新到后台
-				let params ={userID:this.userID,productID:pros.productID,productCount:pros.number}
-				shoppingCartUpdate(params).then(response =>{
+				console.log(pros)
+				let params ={userID:this.userID,productID:pros.productId,productCount:pros.number}
+				this.ProductService.shoppingCartUpdate(params).then(response =>{
 					this.isshowDelbtn = false;
 					this.initGetCartGoodsList();
 				}).catch(error =>{
@@ -502,7 +500,7 @@
 			toConfirmation(){//跳转确认订单页面
 				let setGoodsList=[];
 				this.goodsList.forEach(res=>{
-					let products = res.productsList
+					let products = res.cartList
 					products.forEach(pros=>{
 						if(pros.productsChecked){
 						    setGoodsList.push(pros.productID)
@@ -515,9 +513,9 @@
 				}else{
 					this.isNoConfim = false
 					this.goodsList.forEach(el=>{
-						el.productsList.forEach(pros=>{
+						el.cartList.forEach(pros=>{
 							if(pros.productsChecked){
-							   if(pros.number<pros.minBuyNumber){
+							   if(pros.number<pros.min){
 								   this.isNoConfim = true
 							   }
 							}
@@ -529,7 +527,7 @@
 					}
 					let productID = '';
 					this.goodsList.forEach(el=>{//获取勾选的商品ID拼接字符串逗号隔开,最后一个逗号去掉
-						el.productsList.forEach(pros=>{
+						el.cartList.forEach(pros=>{
 							if(pros.productsChecked){
 							   productID += pros.productID+','
 							}
@@ -552,7 +550,7 @@
 				} else {
 					this.goodsList.forEach(item => {
 						if(item.checked) {
-							item.productsList.forEach(pros => {
+							item.cartList.forEach(pros => {
 								pros.productsChecked = true;
 							})
 						}
@@ -577,7 +575,7 @@
 						if(item.isDisabled) {
 							item.checked = false;
 						}
-						item.productsList.forEach(pros => {
+						item.cartList.forEach(pros => {
 							if(pros.validFlag == '3') {
 								pros.productsChecked = false;
 							}
@@ -598,7 +596,7 @@
 			deleteList(){//删除购物车商品	
 				this.delGoodsList=[];
 				this.goodsList.forEach(delitem=>{
-					let products = delitem.productsList
+					let products = delitem.cartList
 					products.forEach(pros=>{
 						if(pros.productsChecked){
 						    this.delGoodsList += pros.productID+','
@@ -615,7 +613,7 @@
 					return
 				}else{			
 					this.$util.modal('','确定删除选中的商品吗?','确定','取消',true,() =>{
-						shoppingCartDelete({userID:this.userID,productIDs:this.delGoodsList}).then(response =>{
+						this.ProductService.shoppingCartDelete({userID:this.userID,productIDs:this.delGoodsList}).then(response =>{
 							this.$util.msg('删除成功',2000);
 							setTimeout(()=>{
 								this.isshowDelbtn = false;
@@ -630,7 +628,7 @@
 			deletefailureList(){
 				this.failureList.forEach(failure=>{ this.delGoodsList += failure.productID+',' })
 				this.$util.modal('','确定清空全部失效商品吗?','确定','取消',true,() =>{
-					shoppingCartDelete({userID:this.userID,productIDs:this.delGoodsList}).then(response =>{
+					this.ProductService.shoppingCartDelete({userID:this.userID,productIDs:this.delGoodsList}).then(response =>{
 						this.$util.msg('删除成功',2000);
 						setTimeout(()=>{
 							this.isshowDelbtn = false;

+ 1 - 1
services/ajax.env.js

@@ -1,7 +1,7 @@
 let URL_CONFIG = ""
 if(process.env.NODE_ENV === 'development'){
     // 开发环境
-	// URL_CONFIG = 'http://192.168.2.56:8008'	 //俊俊联调地址
+	// URL_CONFIG = 'http://192.168.2.68:8008'	 //本地IP联调地址
 	// URL_CONFIG = 'http://192.168.2.67:8008'	 //裴裴联调地址
 	URL_CONFIG = 'http://192.168.2.75:8008'	 //超超联调地址
     // URL_CONFIG = 'https://spi-b.caimei365.com'	 //采美测试地址

+ 17 - 0
services/product.service.js

@@ -48,4 +48,21 @@ export default class ProductService {
 	queryShoppingCartList (data = {}) {
 		return this.AjaxService.get({ url:'/shoppingCart/list', data, isLoading: false })
 	}
+	/**
+	 * @更新购物车商品增减
+	 * @param:userId 用户ID(必填)
+	 * @param:productID 商品ID(必填)
+	 * @param:productCount 商品数量ID(必填)
+	 */
+	shoppingCartUpdate (data = {}) {
+		return this.AjaxService.post({ url:'/shoppingCart/update', data, isLoading: true })
+	}
+	/**
+	 * @删除购物车商品
+	 * @param:userId 用户ID(必填)
+	 * @param:productIDs 商品ID(用','号拼接)
+	 */
+	shoppingCartDelete (data = {}) {
+		return this.AjaxService.post({ url:'/shoppingCart/delete', data, isLoading: true })
+	}
 }

+ 0 - 1
supplier/pages/deliver/logistics-list.vue

@@ -9,7 +9,6 @@
 <script>
 	import authorize from '@/common/config/authorize.js'
 	import { mapState,mapMutations } from 'vuex';
-	import { shoppingCartUpdate,shoppingCartDelete } from "@/api/cart.js" 
 	
 	export default{
 		data(){

Некоторые файлы не были показаны из-за большого количества измененных файлов