| <template> | 
|   <view class="tn-tabs-swiper-class tn-tabs-swiper" :class="[backgroundColorClass]" :style="{backgroundColor: backgroundColorStyle, marginTop: $t.string.getLengthUnitValue(top, 'px'), zIndex: zIndex}"> | 
|      | 
|     <scroll-view scroll-x class="tn-tabs-swiper__scroll-view" :scroll-left="scrollLeft" scroll-with-animation :style="{zIndex: zIndex + 1}"> | 
|       <view class="tn-tabs-swiper__scroll-view__box" :class="{'tn-tabs-swiper__scroll-view--flex': !isScroll}"> | 
|          | 
|         <!-- item --> | 
|         <view | 
|           v-for="(item, index) in list" | 
|           :key="index" | 
|           :id="'tn-tabs-swiper__scroll-view__item-' + index" | 
|           class="tn-tabs-swiper__scroll-view__item tn-text-ellipsis" | 
|           :style="[tabItemStyle(index)]" | 
|           @tap="emit(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-swiper__bar" :style="[tabBarStyle]"></view> | 
|       </view> | 
|     </scroll-view> | 
|   </view> | 
| </template> | 
|   | 
| <script> | 
|   import componentsColor from '../../libs/mixin/components_color.js' | 
|   const { windowWidth } = uni.getSystemInfoSync() | 
|    | 
|   export default { | 
|     mixins: [componentsColor], | 
|     name: 'tn-tabs-swiper', | 
|     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' | 
|       }, | 
|       // swiper的宽度 | 
|       swiperWidth: { | 
|         type: Number, | 
|         default: 750 | 
|       }, | 
|       // 选中时的颜色 | 
|       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 | 
|       }, | 
|       // 滚动至中心目标类型 | 
|       autoCenterMode: { | 
|         type: String, | 
|         default: 'window' | 
|       }, | 
|       zIndex: { | 
|         type: Number, | 
|         default: 1 | 
|       } | 
|     }, | 
|     computed: { | 
|       currentIndex() { | 
|         const current = Number(this.current) | 
|         // 判断是否超出 | 
|         if (current > this.list.length - 1) { | 
|           return this.list.length - 1 | 
|         } | 
|         if (current < 0) return 0 | 
|         return current | 
|       }, | 
|       // 滑块需要移动的距离 | 
|       scrollBarLeft() { | 
|         return Number(this.tabLineDx) + Number(this.tabLineAddDx) | 
|       }, | 
|       // 滑块宽度转换为px | 
|       barWidthPx() { | 
|         return uni.upx2px(this.barWidth) | 
|       }, | 
|       // 将swiper宽度转换为px | 
|       swiperWidthPx() { | 
|         return uni.upx2px(this.swiperWidth) | 
|       }, | 
|       // tab样式 | 
|       tabItemStyle() { | 
|         return index => { | 
|           let style = { | 
|             height: this.$t.string.getLengthUnitValue(this.height), | 
|             lineHeight: this.$t.string.getLengthUnitValue(this.height), | 
|             fontSize: this.fontSizeStyle || '28rpx', | 
|             color: this.tabsInfo.length > 0 ? (this.tabsInfo[index] ? this.tabsInfo[index].color : this.activeColor) : this.inactiveColor, | 
|             padding: this.isScroll ? `0 ${this.gutter}rpx` : '', | 
|             flex: this.isScroll ? 'auto' : '1', | 
|             zIndex: this.zIndex + 2 | 
|           } | 
|           if (index === this.currentIndex) { | 
|             if (this.bold) { | 
|               style.fontWeight = 'bold' | 
|             } | 
|             Object.assign(style, this.activeItemStyle) | 
|           } | 
|           return style | 
|         } | 
|       }, | 
|       // 底部滑块样式 | 
|       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, | 
|           left: this.scrollBarLeft + 'px' | 
|         } | 
|         Object.assign(style, this.barStyle) | 
|         return style | 
|       }, | 
|     }, | 
|     data() { | 
|       return { | 
|         // 滚动scroll-view的左边滚动距离 | 
|         scrollLeft: 0, | 
|         // 存放tab菜单节点信息 | 
|         tabsInfo: [], | 
|         // 屏幕宽度 | 
|         windowWidth: 0, | 
|         // 滑动动画结束后对应的标签Index | 
|         animationFinishCurrent: this.current, | 
|         // 组件的宽度 | 
|         componentsWidth: 0, | 
|         // 移动距离 | 
|         tabLineAddDx: 0, | 
|         tabLineDx: 0, | 
|         // 颜色渐变数组 | 
|         colorGradientArr: [], | 
|         // 两个颜色之间的渐变等分 | 
|         colorStep: 100, | 
|       } | 
|     }, | 
|     watch: { | 
|       current(value) { | 
|         this.change(value) | 
|         this.setFinishCurrent(value) | 
|       }, | 
|       list() { | 
|         this.$nextTick(() => { | 
|           this.init() | 
|         }) | 
|       } | 
|     }, | 
|     mounted() { | 
|       this.init() | 
|     }, | 
|     methods: { | 
|       // 初始化 | 
|       async init() { | 
|         await this.getTabsInfo() | 
|         this.countLine3Dx() | 
|         this.getQuery(() => { | 
|           this.setScrollViewToCenter() | 
|         }) | 
|         // 获取渐变颜色数组 | 
|         this.colorGradientArr = this.$t.color.colorGradient(this.inactiveColor, this.activeColor, this.colorStep) | 
|       }, | 
|       // 发送事件 | 
|       emit(index) { | 
|         this.$emit('change', index) | 
|       }, | 
|       // tabs发生变化 | 
|       change() { | 
|         this.setScrollViewToCenter() | 
|       }, | 
|       // 获取各个tab的节点信息 | 
|       getTabsInfo() { | 
|         return new Promise((resolve, reject) => { | 
|           let view = uni.createSelectorQuery().in(this) | 
|           for (let i = 0; i < this.list.length; i++) { | 
|             view.select('#tn-tabs-swiper__scroll-view__item-'+i).boundingClientRect() | 
|           } | 
|           view.exec(res => { | 
|             const arr = [] | 
|             for (let i = 0; i < res.length; i++) { | 
|               // 添加颜色属性 | 
|               res[i].color = this.inactiveColor | 
|               if (i === this.currentIndex) res[i].color = this.activeColor | 
|               arr.push(res[i]) | 
|             } | 
|             this.tabsInfo = arr | 
|             resolve() | 
|           }) | 
|         }) | 
|       }, | 
|       // 查询components信息 | 
|       getQuery(cb) { | 
|         try { | 
|           let view = uni.createSelectorQuery().in(this).select('.tn-tabs-swiper') | 
|           view.fields({ | 
|               size: true | 
|             }, | 
|             data => { | 
|               if (data) { | 
|                 this.componentsWidth = data.width | 
|                 if (cb && typeof cb === 'function') cb(data) | 
|               } else { | 
|                 this.getQuery(cb) | 
|               } | 
|             } | 
|           ).exec() | 
|         } catch (e) { | 
|           this.componentsWidth = windowWidth | 
|         } | 
|       }, | 
|       // 当swiper滑动结束的时候,计算滑块最终停留的位置 | 
|       countLine3Dx() { | 
|         const tab = this.tabsInfo[this.animationFinishCurrent] | 
|         // 让滑块中心点和当前tab中心重合 | 
|         if (tab) this.tabLineDx = tab.left + tab.width / 2 - this.barWidthPx / 2 - this.tabsInfo[0].left | 
|       }, | 
|       // 把活动的tab移动到屏幕中心 | 
|       setScrollViewToCenter() { | 
|         let tab = this.tabsInfo[this.animationFinishCurrent] | 
|         if (tab) { | 
|           let tabCenter = tab.left + tab.width / 2 | 
|           let parentWidth | 
|           // 活动tab移动到中心时,以屏幕还是tab组件宽度为基准 | 
|           if (this.autoCenterMode === 'window') { | 
|             parentWidth = windowWidth | 
|           } else { | 
|             parentWidth = this.componentsWidth | 
|           } | 
|           this.scrollLeft = tabCenter - parentWidth / 2 | 
|         } | 
|       }, | 
|       // 设置偏移位置 | 
|       setDx(dx) { | 
|          | 
|         // 计算下一个标签的步进值 | 
|         let nextIndexStep = Math.ceil(Math.abs(dx / this.swiperWidthPx)) | 
|         let nextTabIndex = dx > 0 ? this.animationFinishCurrent + 1 : this.animationFinishCurrent - 1 | 
|         // 处理索引超出边界问题 | 
|         nextTabIndex = nextTabIndex <= 0 ? 0 : nextTabIndex | 
|         nextTabIndex = nextTabIndex >= this.list.length ? this.list.length - 1 : nextTabIndex | 
|          | 
|         // 当前tab中心点x轴坐标 | 
|         let currentTab = this.tabsInfo[this.animationFinishCurrent] | 
|         let currentTabX = currentTab.left + currentTab.width / 2 | 
|          | 
|         // 下一个tab中心点x轴坐标 | 
|         let nextTab = this.tabsInfo[nextTabIndex] | 
|         let nextTabX = nextTab.left + nextTab.width / 2 | 
|          | 
|         // 两个tab之间的距离 | 
|         let distanceX = Math.abs(nextTabX - currentTabX) | 
|         this.tabLineAddDx = (dx / this.swiperWidthPx) * distanceX | 
|         this.setTabColor(this.animationFinishCurrent, nextTabIndex, dx) | 
|       }, | 
|       // 设置tab的颜色 | 
|       setTabColor(currentTabIndex, nextTabIndex, dx) { | 
|         let nextIndexStep = Math.ceil(Math.abs(dx / this.swiperWidthPx)) | 
|         if (Math.abs(dx) > this.swiperWidthPx) { | 
|           dx = dx > 0 ? dx - (this.swiperWidthPx * (nextIndexStep - 1)) : dx + (this.swiperWidthPx * (nextIndexStep - 1)) | 
|         } | 
|         let colorIndex = Math.abs(Math.ceil((dx / this.swiperWidthPx) * 100)) | 
|         let colorLength = this.colorGradientArr.length | 
|         // 处理超出索引边界 | 
|         colorIndex = colorIndex >= colorLength ? colorLength - 1 : colorIndex <= 0 ? 0 : colorIndex | 
|         if (nextIndexStep > 1) { | 
|           // 设置下一个tab的颜色 | 
|           // 设置之前tab的颜色为默认颜色 | 
|           if (dx > 0) { | 
|             this.tabsInfo[nextTabIndex + (nextIndexStep - 1) > this.tabsInfo.length - 1 ? this.tabsInfo.length - 1 : nextTabIndex + (nextIndexStep - 1)].color = this.colorGradientArr[colorIndex] | 
|             this.tabsInfo[nextTabIndex + (nextIndexStep - 2) > this.tabsInfo.length - 1 ? this.tabsInfo.length - 1 : nextTabIndex + (nextIndexStep - 2)].color = this.colorGradientArr[colorLength - 1 - colorIndex] | 
|           } else { | 
|             this.tabsInfo[nextTabIndex - (nextIndexStep - 1) < 0 ? 0 : nextTabIndex - (nextIndexStep - 1)].color = this.colorGradientArr[colorIndex] | 
|             this.tabsInfo[nextTabIndex - (nextIndexStep - 2) < 0 ? 0 : nextTabIndex - (nextIndexStep - 2)].color = this.colorGradientArr[colorLength - 1 - colorIndex] | 
|           } | 
|         } else { | 
|           // 设置下一个tab的颜色 | 
|           this.tabsInfo[nextTabIndex].color = this.colorGradientArr[colorIndex] | 
|           // 设置当前tab的颜色 | 
|           this.tabsInfo[currentTabIndex].color = this.colorGradientArr[colorLength - 1 - colorIndex] | 
|         } | 
|          | 
|       }, | 
|       // swiper滑动结束 | 
|       setFinishCurrent(current) { | 
|         // 如果滑动的索引不一致,修改tab颜色变化,因为可能会有直接点击tab的情况 | 
|         this.tabsInfo.map((item, index) => { | 
|           if (current == index) item.color = this.activeColor | 
|           else item.color = this.inactiveColor | 
|           return item | 
|         }) | 
|         this.tabLineAddDx = 0 | 
|         this.animationFinishCurrent = current | 
|         this.countLine3Dx() | 
|       } | 
|     } | 
|   } | 
| </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-swiper { | 
|     &__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> |