| <template> | 
|   <view class="tn-tabs-class tn-tabs" :class="[backgroundColorClass]" :style="{backgroundColor: backgroundColorStyle, marginTop: $t.string.getLengthUnitValue(top, 'px')}"> | 
|      | 
|     <!-- _tgetRect()对组件根节点无效,因为写了.in(this),故这里获取内层接点尺寸 --> | 
|     <view :id="id"> | 
|       <scroll-view scroll-x class="tn-tabs__scroll-view" :scroll-left="scrollLeft" scroll-with-animation> | 
|         <view class="tn-tabs__scroll-view__box" :class="{'tn-tabs__scroll-view--flex': !isScroll}"> | 
|           <!-- item --> | 
|           <view | 
|             v-for="(item, index) in list" | 
|             :key="index" | 
|             :id="'tn-tabs__scroll-view__item-' + index" | 
|             class="tn-tabs__scroll-view__item tn-text-ellipsis" | 
|             :style="[tabItemStyle(index)]" | 
|             @tap="clickTab(index)" | 
|           > | 
|             <tn-badge v-if="item[count] || item['count']" backgroundColor="tn-bg-red" fontColor="#FFFFFF" :absolute="true" :top="badgeOffset[0] || 0" :right="badgeOffset[1] || 0">{{ item[count] || item['count']}}</tn-badge> | 
|             {{ item[name] || item['name'] }} | 
|           </view> | 
|            | 
|           <!-- 底部滑块 --> | 
|           <view v-if="showBar" class="tn-tabs__bar" :style="[tabBarStyle]"></view> | 
|         </view> | 
|       </scroll-view> | 
|     </view> | 
|   </view> | 
| </template> | 
|   | 
| <script> | 
|   import componentsColor from '../../libs/mixin/components_color.js' | 
|   export default { | 
|     mixins: [componentsColor], | 
|     name: 'tn-tabs', | 
|     props: { | 
|       // 标签列表 | 
|       list: { | 
|         type: Array, | 
|         default() { | 
|           return [] | 
|         } | 
|       }, | 
|       // 列表数据tab名称的属性 | 
|       name: { | 
|         type: String, | 
|         default: 'name' | 
|       }, | 
|       // 列表数据微标数量的属性 | 
|       count: { | 
|         type: String, | 
|         default: 'count' | 
|       }, | 
|       // 当前活动的tab索引 | 
|       current: { | 
|         type: Number, | 
|         default: 0 | 
|       }, | 
|       // 菜单是否可以滑动 | 
|       isScroll: { | 
|         type: Boolean, | 
|         default: true | 
|       }, | 
|       // 高度 | 
|       height: { | 
|         type: Number, | 
|         default: 80 | 
|       }, | 
|       // 距离顶部的距离(px) | 
|       top: { | 
|         type: Number, | 
|         default: 0 | 
|       }, | 
|       // item的宽度 | 
|       itemWidth: { | 
|         type: [String, Number], | 
|         default: 'auto' | 
|       }, | 
|       // 过渡动画时长 | 
|       duration: { | 
|         type: Number, | 
|         default: 0.3 | 
|       }, | 
|       // 选中时的颜色 | 
|       activeColor: { | 
|         type: String, | 
|         default: '#01BEFF' | 
|       }, | 
|       // 未被选中时的颜色 | 
|       inactiveColor: { | 
|         type: String, | 
|         default: '#080808' | 
|       }, | 
|       // 选中的item样式 | 
|       activeItemStyle: { | 
|         type: Object, | 
|         default() { | 
|           return {} | 
|         } | 
|       }, | 
|       // 是否显示底部滑块 | 
|       showBar: { | 
|         type: Boolean, | 
|         default: true | 
|       }, | 
|       // 底部滑块的宽度 | 
|       barWidth: { | 
|         type: Number, | 
|         default: 40 | 
|       }, | 
|       // 底部滑块的高度 | 
|       barHeight: { | 
|         type: Number, | 
|         default: 6 | 
|       }, | 
|       // 自定义底部滑块的样式 | 
|       barStyle: { | 
|         type: Object, | 
|         default() { | 
|           return {} | 
|         } | 
|       }, | 
|       // 单个tab的左右内边距 | 
|       gutter: { | 
|         type: Number, | 
|         default: 30 | 
|       }, | 
|       // 微标的偏移数[top, right] | 
|       badgeOffset: { | 
|         type: Array, | 
|         default() { | 
|           return [20, 22] | 
|         } | 
|       }, | 
|       // 是否加粗字体 | 
|       bold: { | 
|         type: Boolean, | 
|         default: false | 
|       } | 
|     }, | 
|     computed: { | 
|       // 底部滑块样式 | 
|       tabBarStyle() { | 
|         let style = { | 
|           width: this.$t.string.getLengthUnitValue(this.barWidth), | 
|           height: this.$t.string.getLengthUnitValue(this.barHeight), | 
|           borderRadius: `${this.barHeight / 2}rpx`, | 
|           backgroundColor: this.activeColor, | 
|           opacity: this.barMoveFirst ? 0 : 1, | 
|           transform: `translate(${this.scrollBarLeft}px, -100%)`, | 
|           transitionDuration: this.barMoveFirst ? '0s' : `${this.duration}s` | 
|         } | 
|         Object.assign(style, this.barStyle) | 
|         return style | 
|       }, | 
|       // tabItem样式 | 
|       tabItemStyle() { | 
|         return index => { | 
|           let style = { | 
|             width: this.$t.string.getLengthUnitValue(this.itemWidth), | 
|             height: this.$t.string.getLengthUnitValue(this.height), | 
|             lineHeight: this.$t.string.getLengthUnitValue(this.height), | 
|             fontSize: this.fontSizeStyle || '28rpx', | 
|             padding: this.isScroll ? `0 ${this.gutter}rpx` : '', | 
|             flex: this.isScroll ? 'auto' : '1', | 
|             transitionDuration: `${this.duration}s` | 
|           } | 
|           if (index === this.currentIndex) { | 
|             if (this.bold) { | 
|               style.fontWeight = 'bold' | 
|             } | 
|             style.color = this.activeColor | 
|             Object.assign(style, this.activeItemStyle) | 
|           } else { | 
|             style.color = this.inactiveColor | 
|           } | 
|           return style | 
|         } | 
|       } | 
|     }, | 
|     data() { | 
|       return { | 
|         // id值 | 
|         id: this.$t.uuid(), | 
|         // 滚动scroll-view的左边距离 | 
|         scrollLeft: 0, | 
|         // 存放查询后tab菜单的节点信息 | 
|         tabQueryInfo: [], | 
|         // 组件宽度 | 
|         componentWidth: 0, | 
|         // 底部滑块的移动距离 | 
|         scrollBarLeft: 0, | 
|         // 组件到屏幕左边的巨鹿 | 
|         componentLeft: 0, | 
|         // 当前选中的itemIndex | 
|         currentIndex: this.current, | 
|         // 标记底部滑块是否第一次移动,第一次移动的时候不触发动画 | 
|         barMoveFirst: true | 
|       } | 
|     }, | 
|     watch: { | 
|       // 监听tab的变化,重新计算tab菜单信息 | 
|       list(newValue, oldValue) { | 
|         // list变化时,重置内部索引,防止出现超过数据边界的问题 | 
|         if (newValue.length !== oldValue.length) this.currentIndex = 0 | 
|         this.$nextTick(() => { | 
|           this.init() | 
|         }) | 
|       }, | 
|       current: { | 
|         handler(val) { | 
|           this.$nextTick(() => { | 
|             this.currentIndex = val | 
|             this.scrollByIndex() | 
|           }) | 
|         }, | 
|         immediate: true | 
|       } | 
|     }, | 
|     mounted() { | 
|       this.init() | 
|     }, | 
|     methods: { | 
|       // 初始化变量 | 
|       async init() { | 
|         // 获取tabs组件的信息 | 
|         let tabRect = await this._tGetRect('#' + this.id) | 
|         // 计算组件的宽度 | 
|         this.componentLeft = tabRect.left | 
|         this.componentWidth = tabRect.width | 
|         this.getTabRect() | 
|       }, | 
|       // 点击tab菜单 | 
|       clickTab(index) { | 
|         if (index === this.currentIndex) return | 
|         this.$emit('change', index) | 
|       }, | 
|       // 查询tab的布局信息 | 
|       getTabRect() { | 
|         let query = uni.createSelectorQuery().in(this) | 
|         // 遍历所有的tab | 
|         for (let i = 0; i < this.list.length; i++) { | 
|           query.select(`#tn-tabs__scroll-view__item-${i}`).fields({ | 
|             size: true, | 
|             rect: true | 
|           }) | 
|         } | 
|         query.exec((res) => { | 
|           this.tabQueryInfo = res | 
|           // 初始滚动条和底部滑块的位置 | 
|           this.scrollByIndex() | 
|         }) | 
|       }, | 
|       // 滚动scrollView,让活动的tab处于屏幕中间 | 
|       scrollByIndex() { | 
|         // 当前获取tab的布局信息 | 
|         let tabInfo = this.tabQueryInfo[this.currentIndex] | 
|         if (!tabInfo) return | 
|          | 
|         // 活动tab的宽度 | 
|         let tabWidth = tabInfo.width | 
|         // 活动item的左边到组件左边的距离 | 
|         let offsetLeft = tabInfo.left - this.componentLeft | 
|         // 计算scroll-view移动的距离 | 
|         let scrollLeft = offsetLeft - (this.componentWidth - tabWidth) / 2 | 
|         this.scrollLeft = scrollLeft < 0 ? 0 : scrollLeft | 
|          | 
|         // 计算当前滑块需要移动的距离,当前活动item的中点到左边的距离减去滑块宽度的一半 | 
|         let left = tabInfo.left + tabInfo.width / 2 - this.componentLeft | 
|          | 
|         // 计算当前活跃item到组件左边的距离 | 
|         this.scrollBarLeft = left - uni.upx2px(this.barWidth) / 2 | 
|          | 
|         // 防止在计算时出错,所以延迟执行标记不是第一次移动 | 
|         if (this.barMoveFirst) { | 
|           setTimeout(() => { | 
|             this.barMoveFirst = false | 
|           }, 100) | 
|         } | 
|       } | 
|     } | 
|   } | 
| </script> | 
|   | 
| <style lang="scss" scoped> | 
|    | 
|   /* #ifndef APP-NVUE */ | 
|   ::-webkit-scrollbar { | 
|     display: none; | 
|     width: 0 !important; | 
|     height: 0 !important; | 
|     -webkit-appearance: none; | 
|     background: transparent; | 
|   } | 
|   /* #endif */ | 
|    | 
|   /* #ifdef H5 */ | 
|   // 通过样式穿透,隐藏H5下,scroll-view下的滚动条 | 
|   scroll-view ::v-deep ::-webkit-scrollbar { | 
|       display: none; | 
|       width: 0 !important; | 
|       height: 0 !important; | 
|       -webkit-appearance: none; | 
|       background: transparent; | 
|   } | 
|   /* #endif */ | 
|    | 
|   .tn-tabs { | 
|     &__scroll-view { | 
|       position: relative; | 
|       width: 100%; | 
|       white-space: nowrap; | 
|        | 
|       &__box { | 
|         position: relative; | 
|         /* #ifdef MP-TOUTIAO */ | 
|         white-space: nowrap; | 
|         /* #endif */ | 
|       } | 
|        | 
|       &__item { | 
|         position: relative; | 
|         /* #ifndef APP-NVUE */ | 
|         display: inline-block; | 
|         /* #endif */ | 
|         text-align: center; | 
|         transition-property: background-color, color; | 
|       } | 
|        | 
|       &--flex { | 
|         display: flex; | 
|         flex-direction: row; | 
|         justify-content: space-between; | 
|       } | 
|     } | 
|      | 
|     &__bar { | 
|       position: absolute; | 
|       bottom: 0; | 
|     } | 
|   } | 
| </style> |