Files
FirstUI-vue/components/firstui/fui-vtabs/fui-vtabs.vue
2023-08-17 21:28:49 +08:00

526 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.

<!--本文件由FirstUI授权予新疆天衡创新研究院有限公司手机号1 86 140 7 2549身份证尾号5A07X5专用请尊重知识产权勿私下传播违者追究法律责任-->
<template>
<view class="fui-vtabs__wrap" :style="{width:vtabsW,height:vtabsH}">
<view :style="{width:tabWidth+'rpx',background:background}">
<scroll-view :show-scrollbar="false" class="fui-vtabs__scroll-bar"
:style="{width:tabWidth+'rpx',height:vtabsH}" scroll-y :scroll-into-view="scrollInto"
:scroll-with-animation="isTap">
<view class="fui-vtabs__item__wrap" :style="{width:tabWidth+'rpx'}">
<view class="fui-vtabs__item"
:class="{'fui-vtabs__left-border':isBorder,'fui-vtabs__item-hover':!item.disable,'fui-vtabs__item-disable':item.disable,'fui-vtabs__border-color':!getBorderColor && current===index && isBorder}"
:id="'fui_vtabs_bar_'+index" v-for="(item,index) in vals" :key="index"
:style="{background:current===index?activeBgColor:background,borderLeftColor:current===index && isBorder?getBorderColor:'transparent',width:tabWidth+'rpx',height:tabHeight+'rpx'}"
@tap="switchTab(index)">
<image class="fui-vtabs__icon"
:style="{width:(item.iconWidth || 40)+'rpx',height:(item.iconHeight || 40)+'rpx'}"
:src="current===index && item.activeIcon?item.activeIcon:item.icon" v-if="item.icon">
</image>
<!-- #ifdef APP-NVUE -->
<text class="fui-vtabs__text"
:class="{'fui-vtabs__selected-color':!getActiveColor && current===index}"
:style="{fontSize:(current===index?activeSize:size)+'rpx',color:current===index?getActiveColor:color,fontWeight:current===index?activeFontWeight:fontWeight}">{{item.name}}<text
:class="{'fui-vtabs__badge-color':!getBadgeBackground,'fui-vtabs__badge-dot':isDot,'fui-vtabs__badge':!isDot}"
:style="{color:badgeColor,background:getBadgeBackground}"
v-if="item.badge">{{isDot?'':item.badge}}</text></text>
<!-- #endif -->
<!-- #ifndef APP-NVUE -->
<view class="fui-vtabs__text"
:class="{'fui-vtabs__selected-color':!getActiveColor && current===index}"
:style="{fontSize:(current===index?activeSize:size)+'rpx',color:current===index?getActiveColor:color,fontWeight:current===index?activeFontWeight:fontWeight}">
{{item.name}}<text
:class="{'fui-vtabs__badge-color':!getBadgeBackground,'fui-vtabs__badge-dot':isDot,'fui-vtabs__badge':!isDot}"
:style="{color:badgeColor,background:getBadgeBackground}"
v-if="item.badge">{{isDot?'':item.badge}}</text>
</view>
<!-- #endif -->
</view>
</view>
</scroll-view>
</view>
<scroll-view :show-scrollbar="false" scroll-y :style="{height:vtabsH}" class="fui-vtabs__content-wrap"
:scroll-top="contentScrollTop" :scroll-with-animation="animation" @scroll="contentScroll">
<view class="fui-vtabs__content">
<slot></slot>
</view>
</scroll-view>
</view>
</template>
<script>
export default {
name: "fui-vtabs",
emits: ['click', 'change'],
props: {
vtabs: {
type: Array,
default () {
return []
}
},
width: {
type: [Number, String],
default: '0'
},
height: {
type: [Number, String],
default: '0'
},
tabWidth: {
type: [Number, String],
default: 220
},
tabHeight: {
type: [Number, String],
default: 110
},
size: {
type: [Number, String],
default: 26
},
activeSize: {
type: [Number, String],
default: 26
},
color: {
type: String,
default: '#333333'
},
activeColor: {
type: String,
default: ''
},
fontWeight: {
type: [Number, String],
default: 'normal'
},
activeFontWeight: {
type: [Number, String],
default: 'normal'
},
background: {
type: String,
default: '#eeeeee'
},
activeBgColor: {
type: String,
default: '#ffffff'
},
isBorder: {
type: Boolean,
default: true
},
borderColor: {
type: String,
default: ''
},
activeTab: {
type: [Number, String],
default: 0
},
animation: {
type: Boolean,
default: true
},
badgeColor: {
type: String,
default: '#fff'
},
badgeBackground: {
type: String,
default: ''
},
isDot: {
type: Boolean,
default: false
},
//是否联动为false时content只需展示对应索引数据
linkage: {
type: Boolean,
default: true
}
},
provide() {
return {
vtabs: this
}
},
computed: {
getActiveColor() {
let color = this.activeColor
// #ifdef APP-NVUE
if (!color || color === true) {
const app = uni && uni.$fui && uni.$fui.color;
color = (app && app.primary) || '#465CFF';
}
// #endif
return color;
},
getBorderColor() {
let color = this.borderColor
// #ifdef APP-NVUE
if (!color || color === true) {
const app = uni && uni.$fui && uni.$fui.color;
color = (app && app.primary) || '#465CFF';
}
// #endif
return color;
},
getBadgeBackground() {
let color = this.badgeBackground
// #ifdef APP-NVUE
if (!color || color === true) {
const app = uni && uni.$fui && uni.$fui.color;
color = (app && app.danger) || '#FF2B2B';
}
// #endif
return color;
}
},
data() {
return {
vals: [],
scrollInto: '',
current: 0,
contentScrollTop: 0,
// contentScrollInto: '',
heightRecords: [],
contentHeight: {},
vtabsW: '320px',
vtabsH: '600px',
isTap: false
};
},
watch: {
vtabs(vals) {
this.initData(vals)
},
activeTab(newVal, oldVal) {
if (this.linkage) {
this.setDefaultTab(newVal)
} else {
this.switchTab(newVal, true);
}
},
current(val) {
this.scrollTabBar(val)
},
height(val) {
this.setHeight(val)
},
width(val) {
this.setWidth(val)
}
},
created() {
this.children = []
this.setWidth(this.width)
this.setHeight(this.height)
this.calcHeightTimer = null
this.scrollTimer = null
this.initData(this.vtabs)
},
methods: {
setWidth(width) {
let res = uni.getSystemInfoSync()
if (width == 0 || width == '0px' || width == '0rpx') {
this.vtabsW = res.windowWidth + 'px'
} else {
this.vtabsW = width;
}
},
setHeight(height) {
let res = uni.getSystemInfoSync()
if (height == 0 || height == '0px' || height == '0rpx') {
this.vtabsH = res.windowHeight + 'px'
} else {
this.vtabsH = height;
}
},
setDefaultTab(index, idx = 0) {
let len = this.vtabs.length
if (this.heightRecords.length === len && len > 0) {
this.switchTab(index, true);
} else {
if (idx >= 100) return
idx++
setTimeout(() => {
this.setDefaultTab(index, idx)
}, 250)
}
},
initData(vals) {
if (vals && vals.length > 0) {
if (typeof vals[0] !== 'object') {
//字符串
vals = vals.map(item => {
return {
name: item
}
})
}
this.vals = vals;
this.$nextTick(() => {
if (this.linkage) {
setTimeout(() => {
this.setDefaultTab(this.activeTab);
}, 50)
} else {
this.switchTab(this.activeTab, true);
}
})
}
},
scrollTabBar(index) {
let len = this.vtabs.length;
if (len === 0) return;
index = index < 6 ? 0 : index - 5;
if (index >= len) index = len - 1;
this.scrollInto = `fui_vtabs_bar_${index}`;
},
switchTab(index, init) {
index = Number(index)
const item = {
...this.vals[index]
}
if (item.disable) return;
if (this.linkage) {
this.contentScrollTop = this.heightRecords[this.current - 1] || 0;
this.$nextTick(() => {
setTimeout(() => {
this.current = index;
this.contentScrollTop = this.heightRecords[index - 1] || 0;
// this.contentScrollInto = `fui-vtabs-content__${index}`;
}, 50)
});
} else {
this.current = index;
}
if (!init) {
this.isTap = true;
this.$emit('click', {
index: index,
...item
})
}
},
calcHeight() {
let len = this.vtabs.length;
let _heightRecords = [];
let temp = 0;
for (let i = 0; i < len; i++) {
_heightRecords[i] = temp + (this.contentHeight[i] || 0);
temp = _heightRecords[i];
}
this.heightRecords = _heightRecords;
},
contentScroll(e) {
if (!this.linkage) return;
if (this.isTap) {
if (this.scrollTimer) {
clearTimeout(this.scrollTimer)
}
this.scrollTimer = setTimeout(() => {
this.isTap = false;
}, 50)
return;
}
let _heightRecords = this.heightRecords;
if (_heightRecords.length === 0) return;
let len = this.vtabs.length;
let scrollTop = e.detail.scrollTop + (len - 1) * 0.19;
let index = 0;
if (scrollTop >= _heightRecords[0]) {
for (let i = 1; i < len; i++) {
if (scrollTop >= _heightRecords[i - 1] && scrollTop < _heightRecords[i]) {
index = i;
break;
}
}
}
if (index != this.current) {
const item = {
...this.vals[index]
}
this.$emit('change', {
index: index,
...item
})
this.current = index;
}
},
getCalcHeight(height, index) {
this.contentHeight[index] = height;
if (this.calcHeightTimer) {
clearTimeout(this.calcHeightTimer);
}
this.calcHeightTimer = setTimeout(() => {
this.calcHeight();
}, 150);
},
uninstall(tabIndex, target) {
this.children.forEach((item, index) => {
if (item === target) {
this.children.splice(index, 1)
}
})
delete this.contentHeight[tabIndex]
this.calcHeight()
},
//重置列表高度信息
reset() {
if (this.linkage) {
this.children.forEach((item, index) => {
item.calcHeight((height) => {
this.getCalcHeight(height, Number(item.tabIndex))
})
})
}
}
}
}
</script>
<style scoped>
.fui-vtabs__wrap {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
}
/* #ifndef APP-NVUE */
.fui-vtabs__scroll-bar {
flex-shrink: 0;
}
/* #endif */
.fui-vtabs__item__wrap {
/* #ifndef APP-NVUE */
display: flex;
flex-shrink: 0;
/* #endif */
align-items: center;
justify-content: center;
flex-direction: column;
}
.fui-vtabs__item {
padding: 0 20rpx;
/* #ifndef APP-NVUE */
display: flex;
box-sizing: border-box;
overflow: hidden;
/* #endif */
flex-direction: row;
align-items: center;
justify-content: center;
text-align: center;
}
.fui-vtabs__icon {
width: 40rpx;
height: 40rpx;
margin-right: 12rpx;
}
.fui-vtabs__item-hover {
/* #ifdef H5 */
cursor: pointer;
/* #endif */
}
.fui-vtabs__item-disable {
opacity: .5;
/* #ifdef H5 */
cursor: not-allowed;
/* #endif */
}
.fui-vtabs__left-border {
border-left-width: 8rpx;
border-left-style: solid;
}
.fui-vtabs__text {
/* #ifndef APP-NVUE */
display: block;
/* #endif */
/* #ifdef APP-NVUE */
flex-wrap: wrap;
flex-direction: row;
align-items: center;
justify-content: center;
/* #endif */
position: relative;
}
.fui-vtabs__badge {
height: 36rpx;
padding: 0 12rpx;
color: #FFFFFF;
font-size: 24rpx;
line-height: 36rpx;
border-radius: 100px;
/* #ifndef APP-NVUE */
min-width: 36rpx !important;
display: flex;
box-sizing: border-box;
/* #endif */
flex-direction: row;
align-items: center;
justify-content: center;
position: absolute;
right: -32rpx;
top: -18rpx;
transform: scale(0.9);
z-index: 10;
}
.fui-vtabs__badge-dot {
height: 8px !important;
width: 8px !important;
/* #ifdef APP-NVUE */
border-radius: 100px;
/* #endif */
/* #ifndef APP-NVUE */
display: inline-block;
/* #endif */
/* #ifndef APP-NVUE */
border-radius: 50%;
/* #endif */
position: absolute;
right: -6px;
top: -3px;
z-index: 10;
}
.fui-vtabs__content-wrap {
flex: 1;
}
.fui-vtabs__content {
/* #ifndef APP-NVUE */
width: 100%;
height: 100%;
/* #endif */
}
/* #ifndef APP-NVUE */
.fui-vtabs__selected-color {
color: var(--fui-color-primary, #465CFF) !important;
}
.fui-vtabs__border-color {
border-left-color: var(--fui-color-primary, #465CFF) !important;
}
.fui-vtabs__badge-color {
background: var(--fui-color-danger, #FF2B2B) !important;
}
/* #endif */
</style>