ut-tabs.vue 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. <template>
  2. <template v-if="mode == 'scroll-x'">
  3. <scroll-view scroll-x class="ut-tabs-scroll" show-scrollbar="false" :scroll-into-view-offset="SCROLL_OFFSET"
  4. :scroll-into-view="scrollIntoView" ref="scrollViewRef" scroll-with-animation>
  5. <view class="ut-tabs-row">
  6. <view v-for="(tab, idx) in tabs" :key="tab.value" :id="'tab-' + idx" class="ut-tab-item p-rtv"
  7. :class="{ active: idx === activeIndex }" @click="selectTab(idx)" ref="tabRefs">
  8. <view class="tab-label" :style="{
  9. fontSize: fontSize,
  10. color: idx === activeIndex ? themeColor : '#999',
  11. fontWeight: idx === activeIndex ? 'bold' : '500'
  12. }">
  13. <span>{{ tab.label }}</span>
  14. <view v-if="tab.badge && tab.num as number > 0" class="tab-badge">{{ tab.num }}</view>
  15. </view>
  16. <view v-if="idx === activeIndex" class="tab-underline" :style="{
  17. background: themeColor,
  18. height: lineHeight
  19. }"></view>
  20. </view>
  21. </view>
  22. </scroll-view>
  23. </template>
  24. <template v-if="mode == 'btw'">
  25. <view class="ut-tabs-row" :style="{ justifyContent: 'space-between' }">
  26. <view v-for="(tab, idx) in tabs" :key="tab.value" class="ut-tab-item p-rtv"
  27. :class="{ active: idx === activeIndex }" @click="selectTab(idx)" ref="tabRefs">
  28. <view class="tab-label" :style="{
  29. fontSize: fontSize,
  30. color: idx === activeIndex ? themeColor : '#999',
  31. fontWeight: idx === activeIndex ? 'bold' : '500'
  32. }">
  33. <span>{{ tab.label }}</span>
  34. <view v-if="tab.badge && tab.num as number > 0" class="tab-badge">{{ tab.num }}</view>
  35. </view>
  36. <view v-if="idx === activeIndex" class="tab-underline" :style="{
  37. background: themeColor,
  38. height: lineHeight
  39. }"></view>
  40. </view>
  41. </view>
  42. </template>
  43. <template v-if="mode == 'subsection'">
  44. <view class="tabs-subsection-wrap d-flex">
  45. <template v-for="(item, idx) in tabs" :key="idx">
  46. <view class="flex1 subsection-item d-flex p-rtv j-c a-c" :class="{ active: idx === activeIndex }">
  47. <slot :name="item.value">
  48. <text class="f-s-32 c-999">{{ item.label }}</text>
  49. </slot>
  50. </view>
  51. </template>
  52. </view>
  53. </template>
  54. </template>
  55. <script setup lang="ts">
  56. const props = defineProps({
  57. tabs: {
  58. type: Array as () => Array<{ label: string; value: string; badge?: boolean; num?: number }>,
  59. default: () => []
  60. },
  61. modelValue: {
  62. type: [String, Number],
  63. default: ''
  64. },
  65. themeColor: {
  66. type: String,
  67. default: '#37A954'
  68. },
  69. fontSize: {
  70. type: String,
  71. default: '30rpx'
  72. },
  73. lineHeight: {
  74. type: String,
  75. default: '6rpx'
  76. },
  77. mode: {
  78. type: String,
  79. default: 'scroll-x' // btw subsection
  80. }
  81. })
  82. const SCROLL_OFFSET = ref(-(uni as any).$u?.getPx('200rpx'));
  83. const emit = defineEmits(['update:modelValue', 'change'])
  84. const activeIndex = ref(0)
  85. const scrollIntoView = ref('')
  86. const scrollViewRef = ref(null)
  87. const tabRefs = ref([])
  88. watch(() => props.modelValue, (val: any) => {
  89. const idx = props.tabs.findIndex(tab => tab.value === val)
  90. if (idx !== -1) {
  91. activeIndex.value = idx
  92. scrollToTab(idx)
  93. }
  94. })
  95. const selectTab = (idx: number) => {
  96. activeIndex.value = idx
  97. emit('update:modelValue', props.tabs[idx].value)
  98. emit('change', props.tabs[idx])
  99. scrollToTab(idx)
  100. }
  101. // 主动滚动到选中tab
  102. const scrollToTab = (idx: number) => {
  103. scrollIntoView.value = 'tab-' + idx
  104. }
  105. </script>
  106. <style lang="scss" scoped>
  107. .ut-tabs-scroll {
  108. width: 100%;
  109. white-space: nowrap;
  110. }
  111. .ut-tabs-row {
  112. display: flex;
  113. flex-direction: row;
  114. align-items: center;
  115. min-height: 66rpx;
  116. }
  117. .ut-tab-item {
  118. position: relative;
  119. display: inline-flex;
  120. align-items: center;
  121. justify-content: center;
  122. height: 78rpx;
  123. font-weight: 500;
  124. color: #666;
  125. cursor: pointer;
  126. padding: 0 24rpx;
  127. .tab-label {
  128. position: relative;
  129. display: inline-flex;
  130. align-items: center;
  131. justify-content: center;
  132. span {
  133. display: inline-block;
  134. }
  135. .tab-badge {
  136. position: absolute;
  137. top: -10rpx;
  138. left: 90%;
  139. background: #fa3534;
  140. color: #fff;
  141. font-size: 22rpx;
  142. border-radius: 20rpx;
  143. padding: 0 12rpx;
  144. min-width: 32rpx;
  145. text-align: center;
  146. line-height: 32rpx;
  147. height: 32rpx;
  148. box-sizing: border-box;
  149. z-index: 2;
  150. }
  151. }
  152. .tab-underline {
  153. position: absolute;
  154. left: 24rpx;
  155. right: 24rpx;
  156. bottom: 0;
  157. border-radius: 3rpx;
  158. transition: width 0.2s;
  159. z-index: 1;
  160. }
  161. }
  162. .tabs-subsection-wrap {
  163. height: 88rpx;
  164. border-radius: 10rpx;
  165. background-color: #fff;
  166. overflow: hidden;
  167. &.active .subsection-item {
  168. background-color: #f0f9f4;
  169. }
  170. }
  171. </style>