时空网前端
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

216 lines
5.7 KiB

4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
  1. <!-- tab组件: <me-tabs v-model="tabIndex" :tabs="tabs" @change="tabChange"></me-tabs> -->
  2. <template>
  3. <view class="me-tabs bg-white" :class="{'tabs-fixed': fixed}" :style="{height: tabHeightVal}">
  4. <scroll-view v-if="tabs.length" :id="viewId" :scroll-left="scrollLeft" scroll-x scroll-with-animation
  5. :scroll-animation-duration="300">
  6. <view class="tabs-item" :class="{'tabs-flex':!isScroll, 'tabs-scroll':isScroll}">
  7. <!-- tab -->
  8. <view class="tab-item lf-font-28" :style="{width: tabWidthVal, height: tabHeightVal1, 'line-height':tabHeightVal1}" v-for="(tab, i) in tabs"
  9. :class="{'active': value===i}" :key="i" @click="tabClick(i)">
  10. {{getTabName(tab)}}
  11. </view>
  12. <!-- 下划线 -->
  13. <view class="tabs-line" :style="{left:lineLeft}"></view>
  14. </view>
  15. </scroll-view>
  16. </view>
  17. </template>
  18. <script>
  19. export default {
  20. props: {
  21. tabs: { // 支持格式: ['全部', '待付款'] 或 [{name:'全部'}, {name:'待付款'}]
  22. type: Array,
  23. default () {
  24. return []
  25. }
  26. },
  27. nameKey: { // 取name的字段
  28. type: String,
  29. default: 'name'
  30. },
  31. value: { // 当前显示的下标 (使用v-model语法糖: 1.props需为value; 2.需回调input事件)
  32. type: [String, Number],
  33. default: 0
  34. },
  35. fixed: Boolean, // 是否悬浮,默认false
  36. tabWidth: Number, // 每个tab的宽度,默认不设置值,为flex平均分配; 如果指定宽度,则不使用flex,每个tab居左,超过则水平滑动(单位默认rpx)
  37. height: { // 高度,单位rpx
  38. type: Number,
  39. default: 60
  40. }
  41. },
  42. data() {
  43. return {
  44. viewId: 'id_' + Math.random().toString(36).substr(2, 16),
  45. scrollLeft: 0
  46. }
  47. },
  48. computed: {
  49. isScroll() {
  50. return this.tabWidth && this.tabs.length // 指定了tabWidth的宽度,则支持水平滑动
  51. },
  52. tabHeightPx() {
  53. return uni.upx2px(this.height)
  54. },
  55. tabHeightPx1() {
  56. return uni.upx2px(60)
  57. },
  58. tabHeightVal() {
  59. return this.tabHeightPx + 'px'
  60. },
  61. tabHeightVal1() {
  62. return this.tabHeightPx1 + 'px'
  63. },
  64. tabWidthPx() {
  65. return uni.upx2px(this.tabWidth)
  66. },
  67. tabWidthVal() {
  68. return this.isScroll ? this.tabWidthPx + 'px' : ''
  69. },
  70. lineLeft() {
  71. if (this.isScroll) {
  72. return this.tabWidthPx * this.value + this.tabWidthPx / 2 + 'px' // 需转为px (用rpx的话iOS真机显示有误差)
  73. } else {
  74. return 100 / this.tabs.length * (this.value + 1) - 100 / (this.tabs.length * 2) + '%'
  75. }
  76. }
  77. },
  78. watch: {
  79. tabs() {
  80. this.warpWidth = null; // 重新计算容器宽度
  81. this.scrollCenter(); // 水平滚动到中间
  82. },
  83. value() {
  84. this.scrollCenter(); // 水平滚动到中间
  85. }
  86. },
  87. methods: {
  88. getTabName(tab) {
  89. return typeof tab === "object" ? tab[this.nameKey] : tab
  90. },
  91. tabClick(i) {
  92. if (this.value != i) {
  93. this.$emit("input", i);
  94. this.$emit("change", i);
  95. }
  96. },
  97. async scrollCenter() {
  98. if (!this.isScroll) return;
  99. if (!this.warpWidth) { // tabs容器的宽度
  100. let rect = await this.initWarpRect()
  101. this.warpWidth = rect ? rect.width : uni.getSystemInfoSync().windowWidth; // 某些情况下取不到宽度,暂时取屏幕宽度
  102. }
  103. let tabLeft = this.tabWidthPx * this.value + this.tabWidthPx / 2; // 当前tab中心点到左边的距离
  104. let diff = tabLeft - this.warpWidth / 2 // 如果超过tabs容器的一半,则滚动差值
  105. this.scrollLeft = diff;
  106. // #ifdef MP-TOUTIAO
  107. this.scrollTimer && clearTimeout(this.scrollTimer)
  108. this.scrollTimer = setTimeout(() => { // 字节跳动小程序,需延时再次设置scrollLeft,否则tab切换跨度较大时不生效
  109. this.scrollLeft = Math.ceil(diff)
  110. }, 400)
  111. // #endif
  112. },
  113. initWarpRect() {
  114. return new Promise(resolve => {
  115. setTimeout(() => { // 延时确保dom已渲染, 不使用$nextclick
  116. let query = uni.createSelectorQuery();
  117. // #ifndef MP-ALIPAY
  118. query = query.in(this) // 支付宝小程序不支持in(this),而字节跳动小程序必须写in(this), 否则都取不到值
  119. // #endif
  120. query.select('#' + this.viewId).boundingClientRect(data => {
  121. resolve(data)
  122. }).exec();
  123. }, 20)
  124. })
  125. }
  126. },
  127. mounted() {
  128. this.scrollCenter() // 滚动到当前下标
  129. }
  130. }
  131. </script>
  132. <style lang="scss">
  133. .me-tabs {
  134. position: relative;
  135. box-sizing: border-box;
  136. overflow-y: hidden;
  137. background-color: #FE9903;
  138. border-radius: 18rpx;
  139. // box-shadow: 0 0.06rem 0.3rem rgba(0, 0, 0, .1);
  140. &.tabs-fixed {
  141. width: 100%;
  142. }
  143. .tabs-item {
  144. margin:0 4rpx;
  145. border-radius: 16px;
  146. display: flex;
  147. position: relative;
  148. white-space: nowrap;
  149. padding-bottom: 30rpx; // 撑开高度,再配合me-tabs的overflow-y: hidden,以达到隐藏滚动条的目的
  150. box-sizing: border-box;
  151. .tab-item {
  152. flex: 1;
  153. position: relative;
  154. text-align: center;
  155. box-sizing: border-box;
  156. color: white;
  157. &::after {
  158. position: relative;
  159. right: -24rpx;
  160. bottom: 0;
  161. width: 100%;
  162. height: 32rpx;
  163. // content: '';
  164. border-right: 1rpx solid white;
  165. }
  166. &.active {
  167. // font-weight: bold;
  168. color: black;
  169. background-color: white;
  170. border-radius: 14rpx;
  171. height: 26px!important;
  172. line-height: 26px!important;
  173. }
  174. }
  175. }
  176. // 平分的方式显示item
  177. .tabs-flex {
  178. display: flex;
  179. align-items: center;
  180. .tab-item {
  181. flex: 1;
  182. background-color: #FE9903;
  183. }
  184. }
  185. // 居左显示item,支持水平滑动
  186. .tabs-scroll {
  187. .tab-item {
  188. display: inline-block;
  189. }
  190. }
  191. // 选中tab的线
  192. .tabs-line {
  193. z-index: 1;
  194. position: absolute;
  195. bottom: 30rpx; // 至少与.tabs-item的padding-bottom一致,才能保证在底部边缘
  196. width: 100rpx;
  197. height: 5rpx;
  198. transform: translateX(-50%);
  199. border-radius: 4rpx;
  200. transition: left .3s;
  201. // background: red;
  202. }
  203. }
  204. </style>