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

349 lines
9.5 KiB
Vue
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="tn-tabs-class tn-tabs" :class="[backgroundColorClass]" :style="{backgroundColor: backgroundColorStyle, marginTop: $tn.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 {}
}
},
// 未选中的item样式
inactiveItemStyle: {
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.$tn.string.getLengthUnitValue(this.barWidth),
height: this.$tn.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.$tn.string.getLengthUnitValue(this.itemWidth),
height: this.$tn.string.getLengthUnitValue(this.height),
lineHeight: this.$tn.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
Object.assign(style, this.inactiveItemStyle)
}
return style
}
}
},
data() {
return {
// id值
id: this.$tn.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>