Files
2025-04-30 14:04:34 +08:00

350 lines
9.5 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.

<template>
<view :class="className" :style="headStyle" :id="`${selectMark}-box`">
<view class="benben-flex-tabs" :class="className" :style="[stickyContent]">
<slot></slot>
</view>
</view>
</template>
<script>
let pageInfo = null
export default {
components: {},
name: 'benben-flex-tabs',
props: {
className: {
type: String,
default: '',
},
selectMark: {
type: String,
default: 'benben-tabs',
},
scrollspy: {
type: Boolean,
default: false,
},
value: {
type: [String, Number],
default: '',
},
openSticky: {
type: Boolean,
default: false,
},
openTitleType: {
type: Boolean,
default: false,
},
top: {
type: [String, Number],
default: 0,
},
zIndex: {
type: [String, Number],
default: 90,
},
isShowContent: {
type: Boolean,
default: false,
},
isShowLeft: {
type: Boolean,
default: false,
},
isShowRight: {
type: Boolean,
default: false,
},
tabsInfo: {
type: Object,
default: () => ({
lineleft: '',
lineWidth: '',
moveX: 0,
scrollX: 0,
}),
},
},
data() {
return {
tabs: [],
flag: true,
isScroll: true,
tabsHeight: 0,
setTimeoutFn: null,
flagSetTimeoutFn: null,
moveX: 0,
lineWidth: 0,
lineleft: 0,
stickyWidth: 'auto',
stickyHeight: 'auto',
stickyLeft: 0,
isFixed: false,
}
},
//监听属性
watch: {
value: {
handler(val, oldVal) {
this.$emit('change', val)
this.queryMultipleNodes(val)
if (this.isScroll) {
this.pageScrollTo(val)
}
this.isScroll = true
},
},
'tabsInfo.PageScrollX': {
handler(val) {
if (!this.scrollspy) return
this.setTimeoutFn && clearTimeout(this.setTimeoutFn)
this.setTimeoutFn = setTimeout(() => {
for (let index = 0; index < this.tabs.length; index++) {
let elementBefore = 0
let elementAfter = Infinity
if (index != 0) {
elementBefore = this.tabs[index].top - this.tabsHeight
}
if (index != this.tabs.length - 1) {
elementAfter = this.tabs[index + 1].top - this.tabsHeight
}
if (val + 1 > elementBefore && val + 1 < elementAfter) {
if (this.tabs[index].name != this.value && this.flag) {
this.isScroll = false
this.$emit('input', this.tabs[index].name)
}
}
}
}, 300)
},
},
},
computed: {
headStyle() {
return `box-sizing: content-box;padding-top:${this.openTitleType ? this.StatusBarRpx : 0}rpx`
},
stickyContent() {
let style = {}
style.position = this.isFixed ? 'fixed' : 'static'
if (this.openTitleType) {
style.position = 'fixed'
style.paddingTop = this.StatusBarRpx + 'rpx'
}
style.top = this.stickyTop + 'px'
style.left = this.stickyLeft + 'px'
if (this.stickyWidth != 'auto') {
style.width = this.stickyWidth + 'px'
}
if (this.stickyHeight != 'auto') {
style.height = this.stickyHeight + 'px'
}
style.zIndex = this.isFixed ? this.zIndex : 0
return style
},
stickyTop() {
if (this.openTitleType) {
return Math.floor(this.top / this.unitRatio)
} else {
return Math.floor((this.top + this.StatusBarRpx) / this.unitRatio)
}
},
},
methods: {
toJSON() {},
// 获取元素
uGetRect(selector, all) {
return new Promise((resolve) => {
// #ifdef H5
let rect = document[all ? 'querySelectorAll' : 'querySelector'](selector).getBoundingClientRect()
if (all && Array.isArray(rect) && rect.length) {
resolve(rect)
}
if (!all && rect) {
resolve(rect)
}
// #endif
// #ifndef H5
uni
.createSelectorQuery()
.in(this)
[all ? 'selectAll' : 'select'](selector)
.boundingClientRect((rect) => {
if (all && Array.isArray(rect) && rect.length) {
resolve(rect)
}
if (!all && rect) {
resolve(rect)
}
})
.exec()
// #endif
})
},
initObserveContent() {
// 获取吸顶内容的高度用于在js吸顶模式时给父元素一个填充高度防止"塌陷"
this.uGetRect(`#${this.selectMark}-box`).then((res) => {
this.stickyHeight = res.height
this.stickyLeft = res.left
this.stickyWidth = res.width
this.$nextTick(() => {
this.observeContent()
})
})
},
observeContent() {
// 先断掉之前的观察
this.disconnectObserver('contentObserver')
let contentObserver = uni.createIntersectionObserver(this)
/* #ifndef MP-WEIXIN */
let pages = getCurrentPages()
let pageInfo = getCurrentPages()[pages.length - 1]
contentObserver = uni.createIntersectionObserver(pageInfo)
/* #endif */
// 到屏幕顶部的高度时触发
contentObserver.relativeToViewport({
top: -this.stickyTop,
})
// 绑定观察的元素
contentObserver.observe(`#${this.selectMark}-box`, (res) => {
this.setFixed(res)
})
this.contentObserver = contentObserver
},
setFixed(res) {
this.isFixed = this.stickyTop > res.boundingClientRect.bottom
},
disconnectObserver(observerName) {
// 断掉观察,释放资源
const observer = this[observerName]
observer && observer.disconnect()
},
//滚动到指定位置
queryMultipleNodes(name) {
let that = this
let lineWidth = 0
let boxlet = 0
let boxWidth = 0
let maxLeft = 0
let itemLeft = 0
let moveX = 0
this.$nextTick(() => {
uni
.createSelectorQuery()
.select(`#${that.selectMark}`)
.boundingClientRect()
.select(`#${that.selectMark}-title-item-${name}`)
.boundingClientRect()
.select(`#${that.selectMark}-line`)
.boundingClientRect()
.select(`#${that.selectMark}-content`)
.boundingClientRect()
.exec(function (rects) {
that.tabsHeight = Math.floor(rects[0].height + that.StatusBar + that.top / that.unitRatio)
boxlet = rects[0].left
boxWidth = rects[0].width
lineWidth = rects[2].width
if (rects[1] && rects[1].width && lineWidth > rects[1].width) {
lineWidth = rects[1].width
itemLeft = rects[1].left
}
maxLeft = rects[3].width - rects[0].width
maxLeft = maxLeft < 0 ? 0 : maxLeft
let query = uni.createSelectorQuery().select(`#${that.selectMark}`).scrollOffset()
query.select(`#${that.selectMark}-title-item-${name}`).boundingClientRect()
query.exec(function (res) {
if (!res[1]) return
itemLeft = that.tabsInfo.scrollX + res[1].left - boxlet + res[1].width / 2
if (itemLeft >= boxWidth / 2) {
moveX = itemLeft - boxWidth / 2
moveX = moveX > maxLeft ? maxLeft : moveX
} else {
moveX = 0
}
that.$emit('update:tabsInfo', {
...that.tabsInfo,
lineleft: itemLeft + 'px',
lineWidth: lineWidth + 'px',
moveX: moveX,
})
})
})
})
},
setflag() {
this.flag = false
this.flagSetTimeoutFn && clearTimeout(this.flagSetTimeoutFn)
this.flagSetTimeoutFn = setTimeout(() => {
this.flag = true
}, 1000)
},
pageScrollTo(name) {
if (!this.isShowContent) return
if (!this.scrollspy) return
var that = this
var query = uni.createSelectorQuery()
query.selectViewport().scrollOffset()
query.select(`#${this.selectMark}-body-item-${name}`).boundingClientRect()
query.exec(function (res) {
var miss = res[0].scrollTop - that.tabsHeight
if (res[1]?.top) miss += res[1]?.top
that.setflag()
uni.pageScrollTo({
scrollTop: miss,
duration: 300,
})
})
},
// 获取主体高度
getBodyHeight() {
if (!this.isShowContent) return
if (!this.scrollspy) return
let that = this
uni
.createSelectorQuery()
.selectAll(`.${this.selectMark}-body-item`)
.boundingClientRect()
.exec(function (res) {
let tabs = []
res[0].map((item, index) => {
let name = item.id.replace(`${that.selectMark}-body-item-`, '')
if (index == 0) {
tabs.push({
name,
top: 0,
})
} else {
tabs.push({
name,
top: item.top,
})
}
})
that.tabs = tabs
})
},
},
created() {},
mounted() {
this.$emit('update:tabs-info', {
...this.tabsInfo,
lineleft: '0px',
moveX: 0,
})
this.$nextTick(() => {
this.queryMultipleNodes(this.value)
this.getBodyHeight()
})
},
updated() {
if (this.openSticky || this.openTitleType) {
this.initObserveContent()
}
},
beforeDestroy() {
this.disconnectObserver('contentObserver')
},
}
</script>
<style lang="scss" scoped></style>