根据自己的需求都可以改
这里写自定义目录标题
- 1.数组中的名字过长,导致滑动异常
- 2.change 事件拿不到当前点击的数据,通过index在原数组中查找得到所需要的id 各种字段麻烦
- 3.添加指定下标下新加红点显示样式
1.数组中的名字过长,导致滑动异常
2.change 事件拿不到当前点击的数据,通过index在原数组中查找得到所需要的id 各种字段麻烦
直接帮点前点击的传上来 change方法中 直接获取 item ,index
<v-tabs v-model="current" v-model:tipsindex='tipsindex' ellipsisNums='3' v-model:tipsnums='tipsnums' isEllipsis :tabs="tabs" @change="changeTab">
const changeTab =(item,index)=>{
console.log(item)
console.log(index)
}
3.添加指定下标下新加红点显示样式
一般情况下只能会在某一项上添加红点,所以只用传显示的下标以及数量,每一项都显示的话这种场景直接在作者的源码里v-tabs.vue
里面 slot
这里直接添加一个布局,显示每一列的数量就行,
直觉贴代码
props.js 里面新加
// 名字过长是否缩写
isEllipsis: {
type: Boolean,
default: false
},
// 超过数量
ellipsisNums: {
type: [Number, String],
default: 5
},
// 红点显示的下标
tipsindex: {
type: [Number, String],
default: 0
},
// 红点显示的数量
tipsnums: {
type: [Number, String],
default: 0
},
// 红点背景颜色
tipbg: {
type:String,
default: 'red'
},
// 红点颜色
tipcolor: {
type:String,
default: '#ff'
}
v-tabs.vue 直接复制
<template>
<view class="v-tabs">
<scroll-view
:id="getDomId"
:scroll-x="scroll"
:scroll-left="scroll ? scrollLeft : 0"
:scroll-with-animation="scroll"
:style="{ position: fixed ? 'fixed' : 'relative', zIndex, width: '100%' }">
<view
class="v-tabs__container"
:style="{
display: scroll ? 'inline-flex' : 'flex',
whiteSpace: scroll ? 'nowrap' : 'normal',
background: bgColor,
height,
padding
}">
<view
:class="['v-tabs__container-item', { disabled: !!v.disabled }, { active: current == i }]"
v-for="(v, 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(v,i)">
<slot :row="v" :index="i">
<view class="name">{{ field ? limitText( v[field]) : limitText(v) }}</view>
<view class="tip" v-if="tipsindex===i">
{{tipsnums}}
</view>
</slot>
</view>
<template v-if="!!tabs.length">
<view
v-if="!pills"
:class="['v-tabs__container-line', { animation: lineAnimation }]"
:style="{
background: lineColor,
width: lineWidth + 'px',
height: lineHeight,
borderRadius: lineRadius,
transform: `translate3d(${lineLeft}px, 0, 0)`
}" />
<view
v-else
:class="['v-tabs__container-pills', { animation: lineAnimation }]"
:style="{
background: pillsColor,
borderRadius: pillsBorderRadius,
width: currentWidth + 'px',
transform: `translate3d(${pillsLeft}px, 0, 0)`,
height
}" />
</template>
</view>
</scroll-view>
<!-- fixed 的站位高度 -->
<view class="v-tabs__placeholder" :style="{ height: fixed ? height : '0', padding }"></view>
</view>
</template>
<script>
import { startMicroTask, throttle } from './utils'
import props from './props'
/**
* v-tabs
* @property {Number} value 选中的下标
* @property {Array} tabs tabs 列表
* @property {String} bgColor = '#fff' 背景颜色
* @property {String} color = '#333' 默认颜色
* @property {String} activeColor = '#2979ff' 选中文字颜色
* @property {String} fontSize = '28rpx' 默认文字大小
* @property {String} activeFontSize = '28rpx' 选中文字大小
* @property {Boolean} bold = [true | false] 选中文字是否加粗
* @property {Boolean} scroll = [true | false] 是否滚动
* @property {String} height = '60rpx' tab 的高度
* @property {String} lineHeight = '10rpx' 下划线的高度
* @property {String} lineColor = '#2979ff' 下划线的颜色
* @property {Number} lineScale = 0.5 下划线的宽度缩放比例
* @property {String} lineRadius = '10rpx' 下划线圆角
* @property {Boolean} pills = [true | false] 是否胶囊样式
* @property {String} pillsColor = '#2979ff' 胶囊背景色
* @property {String} pillsBorderRadius = '10rpx' 胶囊圆角大小
* @property {String} field 如果是对象,显示的键名
* @property {Boolean} fixed = [true | false] 是否固定
* @property {String} paddingItem = '0 22rpx' 选项的边距
* @property {Boolean} lineAnimation = [true | false] 下划线是否有动画
* @property {Number} zIndex = 1993 默认层级
* @property {Boolean} isEllipsis = [true | false] 名字过长是否简写...
* @property {Boolean} ellipsisNums = 名字超过几个
* @property {Boolean} tipsindex = 红点要显示的下标
* @property {Boolean} tipsnums = 红点要显示的数量
* @event {Function(current)} change 改变标签触发
*/
export default {
name: 'VTabs',
props,
// #ifdef VUE3
emits: ['update:modelValue', 'change'],
// #endif
data() {
return {
lineWidth: 30,
currentWidth: 0, // 当前选项的宽度
lineLeft: 0, // 滑块距离左侧的位置
pillsLeft: 0, // 胶囊距离左侧的位置
scrollLeft: 0, // 距离左边的位置
container: { width: 0, height: 0, left: 0, right: 0 }, // 容器的宽高,左右距离
current: 0, // 当前选中项
scrollWidth: 0 // 可以滚动的宽度
}
},
computed: {
getDomId() {
const len = 16
const $chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678' /****默认去掉了容易混淆的字符oOLl,9gq,Vv,Uu,I1****/
const maxPos = $chars.length
let pwd = ''
for (let i = 0; i < len; i++) {
pwd += $chars.charAt(Math.floor(Math.random() * maxPos))
}
return `xfjpeter_${pwd}`
}
},
watch: {
// #ifdef VUE3
modelValue: {
// #endif
// #ifdef VUE2
value: {
// #endif
immediate: true,
handler(newVal) {
this.current = newVal > -1 && newVal < this.tabs.length ? newVal : 0
this.$nextTick(this.update)
}
}
},
methods: {
limitText(val) {
val += ''
if (!val) return
if (!this.isEllipsis) return val
return val.length > this.ellipsisNums ? `${val.slice(0, this.ellipsisNums)}...` : val
},
// 切换事件
change: throttle(function(item,index) {
const isDisabled = !!this.tabs[index].disabled
if (this.current !== index && !isDisabled) {
this.current = index
// #ifdef VUE3
this.$emit('update:modelValue', index)
// #endif
// #ifdef VUE2
this.$emit('input', item,index)
// #endif
this.$emit('change', item,index)
}
}, 300),
createQueryHandler() {
let query
// #ifndef MP-ALIPAY
query = uni.createSelectorQuery().in(this)
// #endif
// #ifdef MP-ALIPAY
query = uni.createSelectorQuery()
// #endif
return query
},
update() {
const _this = this
startMicroTask(() => {
// 没有列表的时候,不执行
if (!this.tabs.length) return
_this
.createQueryHandler()
.select(`#${this.getDomId}`)
.boundingClientRect(data => {
const { width, height, left, right } = data || {}
// 获取容器的相关属性
this.container = { width, height, left, right: right - width }
_this.calcScrollWidth()
_this.setScrollLeft()
_this.setLine()
})
.exec()
})
},
// 计算可以滚动的宽度
calcScrollWidth(callback) {
const view = this.createQueryHandler().select(`#${this.getDomId}`)
view.fields({ scrollOffset: true })
view
.scrollOffset(res => {
if (typeof callback === 'function') {
callback(res)
} else {
// 获取滚动条的宽度
this.scrollWidth = res.scrollWidth
}
})
.exec()
},
// 设置滚动条滚动的进度
setScrollLeft() {
this.calcScrollWidth(res => {
// 动态读取 scrollLeft
let scrollLeft = res.scrollLeft
this.createQueryHandler()
.select(`#${this.getDomId} .v-tabs__container-item.active`)
.boundingClientRect(data => {
if (!data) return
// 除开当前选项外容器的一半宽度
let curHalfWidth = (this.container.width - data.width) / 2
let scrollDiff = this.scrollWidth - this.container.width
// 在原有滚动条的基础上 + (当前元素距离左侧的距离 - 计算的一半宽度) - 容器的外边距之类的
scrollLeft += data.left - curHalfWidth - this.container.left
// 已经滚动在左侧了
if (scrollLeft < 0) scrollLeft = 0
// 已经超出右侧了
else if (scrollLeft > scrollDiff) scrollLeft = scrollDiff
this.scrollLeft = scrollLeft
})
.exec()
})
},
setLine() {
this.calcScrollWidth(res => {
const scrollLeft = res.scrollLeft
this.createQueryHandler()
.select(`#${this.getDomId} .v-tabs__container-item.active`)
.boundingClientRect(data => {
if (!data) return
if (this.pills) {
this.currentWidth = data.width
this.pillsLeft = scrollLeft + data.left - this.container.left
} else {
this.lineWidth = data.width * this.lineScale
this.lineLeft = scrollLeft + data.left + (data.width - data.width * this.lineScale) / 2 - this.container.left
}
})
.exec()
})
}
}
}
</script>
<style lang="scss" scoped>
.v-tabs__container-item{
position: relative !important;
.tip{
width: 14px;
height: 14px;
background: red;
position: absolute;
top: 0px;
right: 0px;
text-align: center;
line-height: 14px;
border-radius: 6px;
color: #fff;
}
}
.v-tabs {
width: 100%;
box-sizing: border-box;
overflow: hidden;
/* #ifdef H5 */
::-webkit-scrollbar {
display: none;
}
/* #endif */
&__container {
min-width: 100%;
position: relative;
display: inline-flex;
align-items: center;
white-space: nowrap;
overflow: hidden;
&-item {
flex-shrink: 0;
display: flex;
align-items: center;
height: 100%;
position: relative;
z-index: 10;
transition: all 0.3s;
white-space: nowrap;
&.disabled {
opacity: 0.5;
color: #999;
}
}
&-line {
position: absolute;
left: 0;
bottom: 0;
}
&-pills {
position: absolute;
z-index: 9;
}
&-line,
&-pills {
&.animation {
transition: all 0.3s linear;
}
}
}
}
</style>
组件中使用 index.vue
<template>
<view>
<!-- <son4></son4>
-->
<view class="main">
<view class="left">
<v-tabs v-model="current" v-model:tipsindex='tipsindex' ellipsisNums='3' v-model:tipsnums='tipsnums' isEllipsis :tabs="tabs" @change="changeTab">
</v-tabs>
</view>
<view class="right">
站位
</view>
</view>
</view>
</template>
<script setup>
import { ref,provide} from 'vue'
// import son4 from "../../components/son4.vue";
// let toChildValue = ref('传递给所有子集的数据')
// provide( 'toChildValue', toChildValue)
let current = ref(0)
let tipsindex = ref(2)
let tipsnums = ref(2)
let tabs = ref([])
// 模拟请求数据
setTimeout(()=>{
tabs.value = ['军事', '国内', '新闻新闻新闻新闻新闻新闻新闻新闻', '军事', '国内', '新闻', '军事', '国内', '新闻']
},1500)
const changeTab =(item,index)=>{
console.log(item)
console.log(index)
}
// 模拟修改红点数量
setTimeout(()=>{
tipsnums.value = 6
},6000)
</script>
<style>
.main{
width: 100%;
height: 40px;
background: pink;
display: flex;
}
.left{
flex: 1;
width: 0;
height: 40px;
}
.right{
width: 100px;
height: 40px;
background: springgreen;
}
</style>