yunshangxie/tuniao-ui/components/tn-tabs-swiper/tn-tabs-swiper.vue

445 lines
13 KiB
Vue
Raw Normal View History

2023-12-25 17:56:30 +08:00
<template>
<view class="tn-tabs-swiper-class tn-tabs-swiper" :class="[backgroundColorClass]" :style="{backgroundColor: backgroundColorStyle, marginTop: $tn.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.$tn.string.getLengthUnitValue(this.height),
lineHeight: this.$tn.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.$tn.string.getLengthUnitValue(this.barWidth),
height: this.$tn.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.$tn.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>