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.
484 lines
12 KiB
484 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>
|
|
|