350 lines
9.5 KiB
Vue
350 lines
9.5 KiB
Vue
<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>
|