feat: 首次提交
This commit is contained in:
526
components/firstui/fui-vtabs/fui-vtabs.vue
Normal file
526
components/firstui/fui-vtabs/fui-vtabs.vue
Normal file
@ -0,0 +1,526 @@
|
||||
<!--本文件由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>
|
Reference in New Issue
Block a user