<template>
<view class="tabs" :style="{ height }">
<scroll-view
id="scrollContainer"
:scroll-x="scroll"
:scroll-left="scroll ? scrollLeft : 0"
:scroll-with-animation="scroll"
:style="{ position: fixed ? 'fixed' : 'relative', zIndex: 100, width: isFilter ? 'calc(100% - 100rpx)' : '100%' }"
@dragend="scrollEnd"
>
<view
class="tabs_container"
:style="{
display: scroll ? 'inline-flex' : 'flex',
whiteSpace: scroll ? 'nowrap' : 'normal',
background: bgColor,
height,
padding
}"
>
<view
class="item"
v-for="(item, i) in tabs"
:key="i"
:style="{
color: current == i ? activeColor : color,
fontSize: current == i ? fontSize : fontSize,
fontWeight: bold && current == i ? 'bold' : '',
justifyContent: !scroll ? 'center' : '',
flex: scroll ? '' : 1,
padding: paddingItem
}"
@click="change(i)"
>
{{ filed ? item[filed] : item }}
</view>
<view
v-if="!pills"
:class="['line', {animation: lineAnimation}]"
:style="{
background: lineColor,
width: lineWidth + 'px',
height: lineHeight,
borderRadius: lineRadius,
left: lineLeft + 'px',
bottom: lineBottom,
transform: `translateX(-${lineWidth / 2}px)`
}"
></view>
<view
v-else
:class="['pills', {animation: lineAnimation}]"
:style="{
background: pillsColor,
borderRadius: pillsBorderRadius,
left: pillsLeft + 'px',
width: currentWidth + 'px',
height
}"
></view>
</view>
</scroll-view>
<view v-if="isFilter" class="tabs-filter" @click="filter" :style="{ height: height, padding, background: bgColor, position: fixed ? 'fixed' : 'absolute', }">
<img class="bg" src="/static/common/tabs_bg.png" alt="">
<img src="/static/common/icon_filter.png" alt="" class="filter-img">
</view>
</view>
</template>
<script>
/**
* v-tabs
* @property {Number} value 选中的下标
* @property {Array} tabs tabs 列表
* @property {String} bgColor = '#fff' 背景颜色
* @property {String} color = '#262626' 默认颜色
* @property {String} activeColor = '#1890FF' 选中文字颜色
* @property {String} fontSize = '28rpx' 默认文字大小
* @property {String} activeFontSize = '28rpx' 选中文字大小
* @property {Boolean} bold = [true | false] 选中文字是否加粗
* @property {Boolean} scroll = [true | false] 是否滚动
* @property {String} height = '84rpx' tab 的高度
* @property {String} lineHeight = '8rpx' 下划线的高度
* @property {String} lineColor = '#1890FF' 下划线的颜色
* @property {Number} lineScale = 0.5 下划线的宽度缩放比例
* @property {String} lineRadius = '4rpx' 下划线圆角
* @property {String} lineBottom = '8rpx' 下划线位置
* @property {Boolean} pills = [true | false] 是否胶囊样式
* @property {String} pillsColor = '#1890FF' 胶囊背景色
* @property {String} pillsBorderRadius = '10rpx' 胶囊圆角大小
* @property {String} filed 如果是对象,显示的键名
* @property {Boolean} fixed = [true | false] 是否固定
* @property {String} paddingItem = '0 22rpx' 选项的边距
* @property {Boolean} lineAnimation = [true | false] 下划线是否有动画
* @property {Boolean} isFilter = [true | false] 是否有搜索
* @event {Function(current)} change 改变标签触发
* @event {Function()} filter 改变标签触发
*/
export default {
props: {
value: {
type: Number,
default: 0
},
tabs: {
type: Array,
default () {
return []
}
},
bgColor: {
type: String,
default: '#fff'
},
padding: {
type: String,
default: '0'
},
color: {
type: String,
default: '#000000'
},
activeColor: {
type: String,
default: '#1890FF'
},
fontSize: {
type: String,
default: '28rpx'
},
activeFontSize: {
type: String,
default: '28rpx'
},
bold: {
type: Boolean,
default: true
},
scroll: {
type: Boolean,
default: true
},
height: {
type: String,
default: '84rpx'
},
lineColor: {
type: String,
default: '#1890FF'
},
lineHeight: {
type: String,
default: '8rpx'
},
lineScale: {
type: Number,
default: 0.5
},
lineRadius: {
type: String,
default: '4rpx'
},
lineBottom: {
type: String,
default: '8rpx',
},
pills: {
type: Boolean,
default: false
},
pillsColor: {
type: String,
default: '#1890FF'
},
pillsBorderRadius: {
type: String,
default: '10rpx'
},
filed: {
type: String,
default: ''
},
fixed: {
type: Boolean,
default: false
},
paddingItem: {
type: String,
default: '0 24rpx'
},
lineAnimation: {
type: Boolean,
default: true
},
isFilter: {
type: Boolean,
default: false,
}
},
data () {
return {
lineWidth: 30,
currentWidth: 0, // 当前选项的宽度
lineLeft: 0, // 滑块距离左侧的位置
pillsLeft: 0, // 胶囊距离左侧的位置
scrollLeft: 0, // 距离左边的位置
containerWidth: 0, // 容器的宽度
current: 0 // 当前选中项
}
},
watch: {
value (newVal) {
this.current = newVal
this.$nextTick(() => {
this.getTabItemWidth()
})
},
current (newVal) {
this.$emit('input', newVal)
},
tabs (newVal) {
this.$nextTick(() => {
this.getTabItemWidth()
})
}
},
methods: {
// 切换事件
change (index) {
if (this.current !== index) {
this.current = index
this.$emit('change', index)
}
},
// 点击筛选
filter() {
this.$emit('filter')
},
// 获取左移动位置
getTabItemWidth () {
let query = uni
.createSelectorQuery()
// #ifndef MP-ALIPAY
.in(this)
// #endif
// 获取容器的宽度
query
.select(`#scrollContainer`)
.boundingClientRect((data) => {
if (!this.containerWidth && data) {
this.containerWidth = data.width
}
})
.exec()
// 获取所有的 tab-item 的宽度
query
.selectAll('.item')
.boundingClientRect((data) => {
if (!data) {
return
}
let lineLeft = 0
let currentWidth = 0
if (data) {
for (let i = 0; i < data.length; i++) {
if (i < this.current) {
lineLeft += data[i].width
} else if (i == this.current) {
currentWidth = data[i].width
} else {
break
}
}
}
// 当前滑块的宽度
this.currentWidth = currentWidth
// 缩放后的滑块宽度
this.lineWidth = currentWidth * this.lineScale * 0.6
// 滑块作移动的位置
this.lineLeft = lineLeft + currentWidth / 2
// 胶囊距离左侧的位置
this.pillsLeft = lineLeft
// 计算滚动的距离左侧的位置
if (this.scroll) {
this.scrollLeft = this.lineLeft - this.containerWidth / 2
}
})
.exec()
}
},
mounted () {
this.current = this.value
this.$nextTick(() => {
this.getTabItemWidth()
})
}
}
</script>
<style lang="scss" scoped>
.tabs {
position: relative;
width: 100%;
display: flex;
justify-content: space-between;
box-sizing: border-box;
overflow: hidden;
::-webkit-scrollbar {
display: none;
}
.tabs_container {
min-width: 100%;
position: relative;
display: inline-flex;
align-items: center;
white-space: nowrap;
overflow: hidden;
.item {
display: flex;
align-items: center;
height: 100%;
position: relative;
z-index: 10;
transition: all 0.3s;
white-space: nowrap;
font-weight: bold;
}
.line {
position: absolute;
}
.pills {
position: absolute;
z-index: 9;
}
.line,
.pills {
&.animation {
transition: all 0.3s linear;
}
}
}
}
.tabs-filter {
position: absolute;
right: 0;
width: 100rpx;
height: 84rpx;
flex-shrink: 0;
z-index: 101;
.bg {
width: 100%;
height: 100%;
}
.filter-img {
position: absolute;
top: 26rpx;
left: 42rpx;
width: 32rpx;
height: 32rpx;
}
}
</style>
调用
// 可以滚动多个 有筛选的情况下
<Tabs v-model="selectScrollIndex" :tabs="selectScrollList" @change="changeselectScroll" filed="name" isFilter @filter="filterTabs"></Tabs>
// 不可滚动 几个的情况下无筛选
<Tabs v-model="summarizeIndex" :scroll="false" :tabs="SummarizeList" @change="changeSummarize" filed="name" ></Tabs>
data() {
return {
selectScrollIndex: 0, // tab切换下标
selectScrollList: [{ // tab切换数据
name: '数据概述',
}, {
name: '营业趋势图'
}, {
name: '菜类流水图'
}, {
name: '菜品销售图'
}, {
name: '营业详情',
}, {
name: '业务构成'
}, {
name: '堂食详情'
}, {
name: '外卖详情'
}, {
name: '用户习惯'
}, {
name: '用户结构'
}, {
name: '会员&普通用户&复购'
}, {
name: '订单分布'
}],
summarizeIndex: 0, // 数据概览下标
SummarizeList: [{ // 数据概览列表
name: '营收汇总'
}, {
name: '堂食订单'
}, {
name: '外卖订单'
}],
}
},
method: {
changeselectScroll(index) {
// 点击返回index
},
filterTabs() {
//点击筛选按钮
}
}
tabs切换 (参数都在代码里了,懒的写)