You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
high-mini/components/QS-tabs/QS-tabs.vue

485 lines
12 KiB

<template>
<view class="QS-tabs" :style="{
'z-index': zIndex,
'font-size': getFontSize + 'rpx',
'background-color': getBgColor,
'transition-duration': getDuration + 's'
}">
<scroll-view scroll-x class="QS-tabs-scroll" :scroll-left="left" scroll-with-animation :style="{
'z-index': (Number(zIndex) + 1)
}">
<view class="QS-tabs-scroll-box">
<!-- 循环tabs -->
<view class="QS-tabs-scroll-item" :style="{
'height':'100rpx',
'line-height': getHeight + 'rpx',
'min-width': getWidth + 'rpx',
'padding': '0 ' + space + 'rpx',
'color': index===getCurrent?getActiveColor:getDefaultColor,
'font-weight': activeBold&&index===getCurrent?'bold':'',
'transition-duration': getDuration + 's',
'z-index': (Number(zIndex) + 2)
}"
v-for="(item, index) in getTabs" :key="index" @tap="emit(index)" :id="preId + index">
<!-- line1 -->
<view v-if="animationMode==='line1'" class="boxStyle" :style="getDurationStyle +( index===getCurrent?getActiveStyle:getDefaultStyle)"></view>
{{item.name || item}}
</view>
<!-- itemBackground -->
<view v-if="hasItemBackground" class="itemBackgroundBox" :style="{
'height': getHeight + 'rpx',
'width': (isLine3&&tabsInfo[animationFinishCurrent]?tabsInfo[animationFinishCurrent].width:tabsInfo[getCurrent].width) + 'px',
'z-index': Number(zIndex) + 1,
'transition-duration': getDuration + 's',
'left': (tabsInfo[getCurrent]?tabsInfo[getCurrent].left:0) + 'px'
}">
<view class="itemBackground" :style="'transition-duration:' + getDuration + 's;' +
'background-color:' + getItemBackgroundColor + ';' +
'box-shadow: 0 0 5rpx 5rpx ' + getItemBackgroundColor + ';' +
itemBackgroundStyle + ';'" />
</view>
<!-- line2 -->
<view v-if="animationMode==='line2'" class="boxStyle2" :style="getLinezIndex + getDurationStyle +
'width:' + lW + 'px;' +
'background-color:' + (lineColor||getActiveColor) + ';' +
line2Style + ';' +
'left:' + line2Dx + 'px;'" />
<view v-if="animationMode==='line3'" class="boxStyle2" :style="getLinezIndex +
'width:' + lW + 'px;' +
'background-color:' + (lineColor||getActiveColor) + ';' +
line2Style + ';' +
'left:' + getLine3Dx + 'px'" />
</view>
</scroll-view>
</view>
</template>
<script>
const {
windowWidth
} = uni.getSystemInfoSync();
const preId = 'QSTabsID_';
export default {
props: {
tabs: { //需循环的标签列表
type: Array,
default () {
return [];
}
},
current: { //当前所在滑块的 index
type: Number,
default: 0
},
height: { //QS-tabs的高度和行高
type: [String, Number],
default: 80
},
minWidth: { //单个tab的最小宽度 //v1.4修改
type: [String, Number],
default: 250
},
fontSize: { //字体大小
type: [String, Number],
default: 30
},
duration: { //过渡动画时长, 单位 s
type: [String, Number],
default: .5
},
activeColor: { //选中项的主题颜色
type: String,
default: '#33cc33'
},
defaultColor: { //未选中项的主题颜色
type: String,
default: '#888'
},
animationLineWidth: { //动画线条的宽度
type: [String, Number],
default: 20
},
line2Style: { //line2线条的样式
type: String,
default: 'height: 8rpx;border-radius: 4rpx;'
},
animationMode: { //动画类型
type: String,
default: 'line1'
},
autoCenter: { //是否自动滚动至中心目标
type: Boolean,
default: true
},
autoCenterMode: { //滚动至中心目标类型
type: String,
default: 'component'
},
activeStyle: { //line1模式选中项的样式
type: String,
default: 'bottom:0;left:50%;transform: translate(-50%,-100%);height: 8rpx;border-radius:4rpx;'
},
defaultStyle: { //line1模式未选中项的样式
type: String,
default: 'bottom:0;left:50%;transform: translate(-50%,-100%);height: 8rpx;border-radius:4rpx;'
},
backgroundColor: { //统一背景颜色
type: String,
default: 'rgba(255,255,255,0)'
},
hasItemBackground: { //是否开启背景追光
type: Boolean,
default: false
},
itemBackgroundColor: { //统一追光背景颜色
type: String,
default: 'rgba(255,255,255,0)'
},
itemBackgroundStyle: { //追光样式
type: String,
default: ''
},
zIndex: { //css的z-index属性值
type: [String, Number],
default: 99
},
swiperWidth: { //line3生效, 外部swiper的宽度, 单位rpx
type: [String, Number],
default: 750
},
space: { //tab间距
type: [String, Number],
default: '0'
},
activeBold: { //当前tab字体是否加粗
type: Boolean,
default: true
},
lineColor: { //line颜色
type: String,
default: ''
}
},
computed: {
isLine3() {
return this.animationMode === 'line3';
},
getCurrent() {
const current = Number(this.current);
if (current > (this.getTabs.length - 1)) {
return (this.getTabs.length - 1)
}
return current;
},
getTabs() {
return this.tabs;
},
getHeight() {
return Number(this.height);
},
getWidth() {
return Number(this.minWidth);
},
getFontSize() {
return this.fontSize;
},
getDuration() {
return Number(this.duration);
},
getBgColor() {
const defaultColor = this.backgroundColor || 'rgba(255,255,255,0)';
if (this.getTabs[this.getCurrent] instanceof Object) {
return this.getTabs[this.getCurrent].backgroundColor || defaultColor;
} else {
return defaultColor;
}
},
getItemBackgroundColor() {
const defaultColor = this.itemBackgroundColor || 'rgba(255,255,255,0)';
if (this.getTabs[this.getCurrent] instanceof Object) {
return this.getTabs[this.getCurrent].itemBackgroundColor || defaultColor;
} else {
return defaultColor;
}
},
getDurationStyle() {
return `transition-duration: ${this.getDuration}s;`
},
getActiveColor() {
let activeColor;
if (this.getTabs[this.getCurrent] instanceof Object) {
if (this.getTabs[this.getCurrent].activeColor) {
activeColor = this.getTabs[this.getCurrent].activeColor;
} else {
activeColor = this.activeColor;
}
} else {
activeColor = this.activeColor;
}
return activeColor;
},
getDefaultColor() {
let defaultColor;
if (this.getTabs[this.getCurrent] instanceof Object) {
if (this.getTabs[this.getCurrent].defaultColor) {
defaultColor = this.getTabs[this.getCurrent].defaultColor;
} else {
defaultColor = this.defaultColor;
}
} else {
defaultColor = this.defaultColor;
}
return defaultColor;
},
getActiveStyle() {
return `width:${this.animationLineWidth}%;background-color:${this.getActiveColor};${this.activeStyle};`;
},
getDefaultStyle() {
return `width:0;background-color:${this.getActiveColor};${this.defaultStyle};`;
},
getLinezIndexNum() {
return Number(this.zIndex) + 2;
},
getLinezIndex() {
return `z-index: ${this.getLinezIndexNum};`;
},
getLine3Dx() {
return Number(this.line3Dx) + Number(this.line3AddDx);
}
},
watch: {
current(n, o) {
this.change(n);
},
tabs() {
this.init();
}
},
data() {
return {
left: 0,
tabsInfo: [],
line2Width: Number(this.animationLineWidth),
setTimeoutFc: null,
componentsWidth: 0,
animationFinishCurrent: this.current,
pxWidth: 0,
lW: 0,
sW: 0,
preId,
line3Dx: 0,
line3AddDx: 0,
line2Dx: 0
}
},
// #ifndef H5
onReady() {
this.init();
},
// #endif
// #ifdef H5
mounted() {
this.init();
},
// #endif
methods: {
init() {
this.countPx();
let view = uni.createSelectorQuery().in(this);
for (let i = 0; i < this.tabs.length; i++) {
view.select('#' + preId + i).boundingClientRect();
}
view.exec((res) => {
const arr = [];
for (let i = 0; i < res.length; i++) {
arr.push(res[i]);
}
this.tabsInfo = arr;
this.countLine2Dx();
this.countLine3Dx();
let _this = this;
_this.getQuery(() => {
_this.countScrollX();
});
})
},
countLine2Dx() {
if (this.animationMode === 'line2') {
const tab = this.tabsInfo[this.getCurrent];
if(tab) this.line2Dx = tab.left + tab.width / 2 - this.lW / 2;
}
},
countLine3Dx() {
if (this.animationMode === 'line3') {
const tab = this.tabsInfo[this.animationFinishCurrent];
if(tab) this.line3Dx = tab.left + tab.width / 2 - this.lW / 2;
}
},
countPx() {
const w = uni.upx2px(this.getWidth);
this.pxWidth = w;
this.lW = (w * (Number(this.animationLineWidth) / 100))+72.5;
this.sW = uni.upx2px(Number(this.swiperWidth));
},
emit(index) {
this.$emit('change', index);
},
change() {
this.countScrollX();
if (this.animationMode === 'line2') {
this.line2Width = 2;
if (this.setTimeoutFc) clearTimeout(this.setTimeoutFc);
this.setTimeoutFc = setTimeout(() => {
this.line2Width = this.animationLineWidth;
}, this.getDuration * 1000 * 3 / 5);
this.countLine2Dx();
}
},
getQuery(cb) {
try {
let view = uni.createSelectorQuery().in(this).select('.QS-tabs');
view.fields({
size: true
}, data => {
if (data) {
this.componentsWidth = data.width;
if (cb && typeof cb === 'function') cb(data);
} else {
this.retryGetQuery(cb);
}
}).exec();
} catch (e) {
//TODO handle the exception
this.componentsWidth = windowWidth;
}
},
retryGetQuery(cb) {
try {
let view = uni.createSelectorQuery().select('.QS-tabs');
view.fields({
size: true
}, data => {
if (data) {
this.componentsWidth = data.width;
} else {
this.componentsWidth = windowWidth;
}
if (cb && typeof cb === 'function') cb(data);
}).exec();
} catch (e) {
//TODO handle the exception
this.componentsWidth = windowWidth;
}
},
countScrollX() {
if (this.autoCenter) {
let tab;
if(this.isLine3) {
tab = this.tabsInfo[this.animationFinishCurrent];
}else{
tab = this.tabsInfo[this.getCurrent];
}
if(tab) {
let tabCenter = tab.left + tab.width/2;
let fatherWidth;
if (this.autoCenterMode === 'window') {
fatherWidth = windowWidth;
} else {
fatherWidth = this.componentsWidth;
}
this.left = tabCenter - fatherWidth / 2;
}
}
},
setDx(dx) {
const tab = this.tabsInfo[dx>0?(this.animationFinishCurrent + 1):(this.animationFinishCurrent - 1)];
this.line3AddDx = dx / this.sW * (tab?tab.width:this.pxWidth);
},
setFinishCurrent(current) {
this.line3AddDx = 0;
this.animationFinishCurrent = current;
this.countLine3Dx();
}
}
}
</script>
<style scoped>
view,
scroll-view {
box-sizing: border-box;
}
.QS-tabs {
width: 100%;
transition-property: background-color, color;
}
.QS-tabs::-webkit-scrollbar {
display: none;
width: 0 !important;
height: 0 !important;
-webkit-appearance: none;
background: transparent;
}
.QS-tabs-scroll {
width: 100%;
white-space: nowrap;
position: relative;
}
.QS-tabs-scroll-box {
position: relative;
display: flex;
white-space: nowrap !important;
display: block !important;
}
.QS-tabs-scroll-item {
position: relative;
display: inline-block;
text-align: center;
transition-property: background-color, color, font-weight;
}
.content {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.boxStyle {
pointer-events: none;
position: absolute;
transition-property: all;
}
.boxStyle2 {
pointer-events: none;
position: absolute;
bottom: 0;
transition-property: all;
transform: translateY(-100%);
}
.itemBackgroundBox {
pointer-events: none;
position: absolute;
top: 0;
transition-property: left, background-color;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
}
.itemBackground {
height: 100%;
width: 100%;
transition-property: all;
}
</style>