shop_uniapp/components/tabs/tabs.vue

382 lines
12 KiB
Vue
Raw 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="tabs">
<u-sticky :enable="isFixed" :bg-color="stickyBgColor" :offset-top="top" :h5-nav-height="0">
<view :id="id" :style="{
background: bgColor,
'border-radius': borderRadius,
width: width,
margin: '0 auto'
}">
<scroll-view :style="{height: height + 'rpx'}" scroll-x class="scroll-view" :scroll-left="scrollLeft"
scroll-with-animation>
<view class="scroll-box" :class="{'tabs-scorll-flex': !isScroll}">
<view class="tab-item line1" :id="'tab-item-' + index" v-for="(item, index) in list"
:key="index" @tap="clickTab(index)" :style="[tabItemStyle(index)]">
<u-badge :count="item[count] || item['dot'] || item['num'] || 0" :offset="offset" size="mini" class="badge"></u-badge>
{{item[name] || item['name']}}
</view>
<view v-if="showBar" class="tab-bar" :style="[tabBarStyle]"></view>
</view>
</scroll-view>
</view>
</u-sticky>
<view class="tab-content">
<view>
<slot></slot>
</view>
</view>
</view>
</template>
<script>
import {
getRect
} from '@/utils/tools'
export default {
name: "tabs",
props: {
isScroll: {
type: Boolean,
default: true
},
current: {
type: [Number, String],
default: 0
},
height: {
type: [String, Number],
default: 80
},
fontSize: {
type: [String, Number],
default: 28
},
duration: {
type: [String, Number],
default: 0.3
},
activeColor: {
type: String,
default: '#FF2C3C'
},
inactiveColor: {
type: String,
default: '#333'
},
barWidth: {
type: [String, Number],
default: 40
},
barHeight: {
type: [String, Number],
default: 4
},
gutter: {
type: [String, Number],
default: 30
},
bgColor: {
type: String,
default: '#ffffff'
},
name: {
type: String,
default: 'name'
},
count: {
type: String,
default: 'count'
},
offset: {
type: Array,
default: () => {
return [5, 20]
}
},
bold: {
type: Boolean,
default: true
},
activeItemStyle: {
type: Object,
default () {
return {}
}
},
showBar: {
type: Boolean,
default: true
},
barStyle: {
type: Object,
default () {
return {}
}
},
itemWidth: {
type: [Number, String],
default: 'auto'
},
isFixed: {
type: Boolean,
default: false
},
top: {
type: [Number, String],
default: 0
},
width: {
type: [Number, String],
default: '100%'
},
stickyBgColor: {
type: String,
default: '#ffffff'
},
borderRadius: {
type: [Number, String],
default: 0
},
async: {
type: Boolean,
default: false
}
},
provide() {
return {
tabs: this
}
},
data() {
return {
list: [],
scrollLeft: 0,
tabQueryInfo: [],
componentWidth: 0,
scrollBarLeft: 0,
parentLeft: 0,
id: 'cu-tab',
currentIndex: this.current,
barFirstTimeMove: true,
isAsync: false
};
},
watch: {
list(n, o) {
if (!this.barFirstTimeMove && n.length !== o.length) {
this.currentIndex = 0;
}
this.$nextTick(() => {
this.init();
});
},
current: {
immediate: true,
handler(nVal, oVal) {
this.$nextTick(() => {
this.currentIndex = nVal;
this.scrollByIndex();
});
}
},
async: {
immediate: true,
handler(nVal, oVal) {
this.isAsync = nVal;
}
},
},
computed: {
tabBarStyle() {
let style = {
width: this.barWidth + 'rpx',
transform: `translate(${this.scrollBarLeft}px, -100%)`,
// 滑块在页面渲染后第一次滑动时,无需动画效果
'transition-duration': `${this.barFirstTimeMove ? 0 : this.duration }s`,
'background-color': this.activeColor,
height: this.barHeight + 'rpx',
opacity: this.barFirstTimeMove ? 0 : 1,
// 设置一个很大的值,它会自动取能用的最大值,不用高度的一半,是因为高度可能是单数,会有小数出现
'border-radius': `${this.barHeight / 2}px`
};
Object.assign(style, this.barStyle);
return style;
},
// tab的样式
tabItemStyle() {
return (index) => {
let style = {
height: this.height + 'rpx',
'line-height': this.height + 'rpx',
'font-size': this.fontSize + 'rpx',
padding: this.isScroll ? `0 ${this.gutter}rpx` : '',
flex: this.isScroll ? 'auto' : '1',
width: `${this.itemWidth}rpx`
};
// 字体加粗
if (index == this.currentIndex && this.bold) style.fontWeight = 'bold';
if (index == this.currentIndex) {
style.color = this.activeColor;
// 给选中的tab item添加外部自定义的样式
style = Object.assign(style, this.activeItemStyle);
} else {
style.color = this.inactiveColor;
}
return style;
}
}
},
methods: {
updateTabs() {
this.list = this.childrens.map((item) => {
const {
name,
dot,
num,
active,
inited,
updateRender
} = item
return {
name,
dot,
num,
active,
inited,
updateRender
}
})
this.$nextTick(function() {
this.init()
})
},
async init() {
let tabRect = await getRect('#' + this.id, false, this);
this.parentLeft = tabRect.left;
this.componentWidth = tabRect.width;
this.getTabRect();
},
clickTab(index) {
this.$emit('change', index);
if(this.isAsync) return;
if (index == this.currentIndex) return;
this.$nextTick(() => {
this.currentIndex = index;
this.scrollByIndex();
});
},
getTabRect() {
let query = uni.createSelectorQuery().in(this);
for (let i = 0; i < this.list.length; i++) {
query.select(`#tab-item-${i}`).fields({
size: true,
rect: true
});
}
query.exec(
function(res) {
this.tabQueryInfo = res;
this.scrollByIndex();
}.bind(this)
);
},
scrollByIndex() {
let tabInfo = this.tabQueryInfo[this.currentIndex];
if (!tabInfo) return;
let tabWidth = tabInfo.width;
let offsetLeft = tabInfo.left - this.parentLeft;
let scrollLeft = offsetLeft - (this.componentWidth - tabWidth) / 2;
this.scrollLeft = scrollLeft < 0 ? 0 : scrollLeft;
let left = tabInfo.left + tabInfo.width / 2 - this.parentLeft;
this.scrollBarLeft = left - uni.upx2px(this.barWidth) / 2;
if (this.barFirstTimeMove == true) {
setTimeout(() => {
this.barFirstTimeMove = false;
}, 100)
}
this.childrens.forEach((item, ind) => {
let active = ind === this.currentIndex;
if (active !== item.active || !item.inited) {
item.updateRender(active, this);
}
});
}
},
created() {
this.childrens = []
},
mounted() {
this.updateTabs();
}
};
</script>
<style lang="scss" scoped>
/* #ifndef APP-NVUE */
::-webkit-scrollbar,
::-webkit-scrollbar,
::-webkit-scrollbar {
display: none;
width: 0 !important;
height: 0 !important;
-webkit-appearance: none;
background: transparent;
}
/* #endif */
.scroll-box {
height: 100%;
position: relative;
/* #ifdef MP-TOUTIAO */
white-space: nowrap;
/* #endif */
}
.tab-fixed {
position: sticky;
top: 0;
width: 100%;
}
/* #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 */
.scroll-view {
width: 100%;
white-space: nowrap;
position: relative;
}
.tab-item {
position: relative;
/* #ifndef APP-NVUE */
display: inline-block;
/* #endif */
text-align: center;
transition-property: background-color, color;
}
.tab-bar {
position: absolute;
bottom: 6rpx;
}
.tabs-scorll-flex {
display: flex;
justify-content: space-between;
}
</style>