You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
363 lines
12 KiB
363 lines
12 KiB
<template> |
|
<view class="tabs"> |
|
<scroll-view ref="tabbar1" id="tab-bar" class="tab-bar" :scroll="false" :scroll-x="true" :show-scrollbar="false" |
|
:scroll-into-view="scrollInto"> |
|
<view style="flex-direction: column;"> |
|
<view style="flex-direction: row;"> |
|
<view class="uni-tab-item" v-for="(tab,index) in tabList" :key="tab.id" :id="tab.id" :ref="'tabitem'+index" |
|
:data-id="index" :data-current="index" @click="ontabtap"> |
|
<text class="uni-tab-item-title" :class="tabIndex==index ? 'uni-tab-item-title-active' : ''">{{tab.name}}</text> |
|
</view> |
|
</view> |
|
<view class="scroll-view-indicator"> |
|
<view ref="underline" class="scroll-view-underline" :class="isTap ? 'scroll-view-animation':''" |
|
:style="{left: indicatorLineLeft + 'px', width: indicatorLineWidth + 'px'}"></view> |
|
</view> |
|
</view> |
|
</scroll-view> |
|
<view class="tab-bar-line"></view> |
|
<swiper class="tab-view" ref="swiper1" id="tab-bar-view" :current="tabIndex" :duration="300" @change="onswiperchange" |
|
@transition="onswiperscroll" @animationfinish="animationfinish" @onAnimationEnd="animationfinish"> |
|
<swiper-item class="swiper-item" v-for="(page, index) in tabList" :key="index"> |
|
<!-- #ifndef MP-ALIPAY --> |
|
<swiperPage class="swiper-page" :pid="page.pageid" ref="page"></swiperPage> |
|
<!-- #endif --> |
|
<!-- #ifdef MP-ALIPAY --> |
|
<swiperPage class="swiper-page" :pid="page.pageid" :ref="'page' + index"></swiperPage> |
|
<!-- #endif --> |
|
</swiper-item> |
|
</swiper> |
|
</view> |
|
</template> |
|
|
|
<script> |
|
// #ifdef APP-NVUE |
|
const dom = weex.requireModule('dom'); |
|
// #endif |
|
|
|
// 缓存每页最多 |
|
const MAX_CACHE_DATA = 100; |
|
|
|
// 缓存页签数量 |
|
const MAX_CACHE_PAGE = 3; |
|
const TAB_PRELOAD_OFFSET = 1; |
|
|
|
import swiperPage from './swiper-page.nvue'; |
|
|
|
export default { |
|
components: { |
|
swiperPage |
|
}, |
|
data() { |
|
return { |
|
tabList: [], |
|
tabIndex: 0, |
|
cacheTab: [], |
|
scrollInto: "", |
|
indicatorLineLeft: 0, |
|
indicatorLineWidth: 0, |
|
isTap: false |
|
} |
|
}, |
|
onLoad() { |
|
for (var i = 0; i < 6; i++) { |
|
this.tabList.push({ |
|
id: "tab" + i, |
|
name: 'Tab ' + (i + 1), |
|
pageid: i + 1 |
|
}) |
|
} |
|
}, |
|
onReady() { |
|
this._lastTabIndex = 0; |
|
this.swiperWidth = 0; |
|
this.tabbarWidth = 0; |
|
this.tabListSize = {}; |
|
this._touchTabIndex = 0; |
|
|
|
// #ifndef MP-ALIPAY |
|
this.pageList = this.$refs.page; |
|
// #endif |
|
// #ifdef MP-ALIPAY |
|
this.pageList = []; |
|
for (var i = 0; i < this.tabList.length; i++) { |
|
this.pageList.push(this.$refs['page' + i][0]); |
|
} |
|
// #endif |
|
this.switchTab(this.tabIndex); |
|
|
|
this.getTabbarItemsSize(); |
|
}, |
|
methods: { |
|
ontabtap(e) { |
|
let index = e.target.dataset.current || e.currentTarget.dataset.current; |
|
|
|
// #ifdef APP-PLUS || H5 || MP-WEIXIN || MP-QQ |
|
this.isTap = true; |
|
var currentSize = this.tabListSize[index]; |
|
this.updateIndicator(currentSize.left, currentSize.width); |
|
this._touchTabIndex = index; |
|
// #endif |
|
|
|
this.switchTab(index); |
|
}, |
|
onswiperchange(e) { |
|
// 注意:百度小程序会触发2次 |
|
|
|
// #ifndef APP-PLUS || H5 || MP-WEIXIN || MP-QQ |
|
let index = e.target.current || e.detail.current; |
|
this.switchTab(index); |
|
// #endif |
|
}, |
|
onswiperscroll(e) { |
|
if (this.isTap) { |
|
return; |
|
} |
|
|
|
var offsetX = e.detail.dx; |
|
var preloadIndex = this._lastTabIndex; |
|
if (offsetX > TAB_PRELOAD_OFFSET) { |
|
preloadIndex++; |
|
} else if (offsetX < -TAB_PRELOAD_OFFSET) { |
|
preloadIndex--; |
|
} |
|
if (preloadIndex === this._lastTabIndex || preloadIndex < 0 || preloadIndex > this.pageList.length - 1) { |
|
return; |
|
} |
|
if (this.pageList[preloadIndex].dataList.length === 0) { |
|
this.loadTabData(preloadIndex); |
|
} |
|
|
|
/// 计算 tabbar 底线 |
|
// #ifdef APP-PLUS || H5 || MP-WEIXIN || MP-QQ |
|
var percentage = Math.abs(this.swiperWidth / offsetX); |
|
var currentSize = this.tabListSize[this._lastTabIndex]; |
|
var preloadSize = this.tabListSize[preloadIndex]; |
|
var lineL = currentSize.left + (preloadSize.left - currentSize.left) / percentage; |
|
var lineW = currentSize.width + (preloadSize.width - currentSize.width) / percentage; |
|
this.updateIndicator(lineL, lineW); |
|
// #endif |
|
}, |
|
animationfinish(e) { |
|
// #ifdef APP-PLUS || H5 || MP-WEIXIN || MP-QQ |
|
let index = e.detail.current; |
|
if (this._touchTabIndex === index) { |
|
this.isTap = false; |
|
} |
|
this._lastTabIndex = index; |
|
this.switchTab(index); |
|
this.updateIndicator(this.tabListSize[index].left, this.tabListSize[index].width); |
|
// #endif |
|
}, |
|
getTabbarItemsSize() { |
|
// #ifdef APP-NVUE |
|
// 查询 tabbar 宽度 |
|
uni.createSelectorQuery().in(this).select('#tab-bar').boundingClientRect().exec(rect => { |
|
this.tabbarWidth = rect[0].width; |
|
}); |
|
// 查询 tabview 宽度 |
|
uni.createSelectorQuery().in(this).select('#tab-bar-view').boundingClientRect().exec(rect => { |
|
this.swiperWidth = rect[0].width; |
|
}); |
|
|
|
// 因 nvue 暂不支持 class 查询 |
|
var queryTabSize = uni.createSelectorQuery().in(this); |
|
for (var i = 0; i < this.tabList.length; i++) { |
|
queryTabSize.select('#' + this.tabList[i].id).boundingClientRect(); |
|
} |
|
queryTabSize.exec(rects => { |
|
console.log(JSON.stringify(rects)); |
|
rects.forEach((rect) => { |
|
this.tabListSize[rect.dataset.id] = rect; |
|
}) |
|
}); |
|
// #endif |
|
|
|
// #ifdef MP-WEIXIN || H5 || MP-QQ |
|
uni.createSelectorQuery().in(this).select('.tab-view').fields({ |
|
dataset: true, |
|
size: true, |
|
}, (res) => { |
|
this.swiperWidth = res.width; |
|
}).exec(); |
|
uni.createSelectorQuery().in(this).selectAll('.uni-tab-item').boundingClientRect((rects) => { |
|
rects.forEach((rect) => { |
|
this.tabListSize[rect.dataset.id] = rect; |
|
}) |
|
}).exec(); |
|
// #endif |
|
|
|
// #ifdef APP-NVUE || H5 || MP-WEIXIN || MP-QQ |
|
setTimeout(() => { |
|
this.updateIndicator(this.tabListSize[this.tabIndex].left, this.tabListSize[this.tabIndex].width); |
|
}, 100) |
|
// #endif |
|
}, |
|
updateIndicator(left, width) { |
|
this.indicatorLineLeft = left; |
|
this.indicatorLineWidth = width; |
|
}, |
|
switchTab(index) { |
|
if (this.pageList[index].dataList.length === 0) { |
|
this.loadTabData(index); |
|
} |
|
|
|
if (this.tabIndex === index) { |
|
return; |
|
} |
|
|
|
// 缓存 tabId |
|
if (this.pageList[this.tabIndex].dataList.length > MAX_CACHE_DATA) { |
|
let isExist = this.cacheTab.indexOf(this.tabIndex); |
|
if (isExist < 0) { |
|
this.cacheTab.push(this.tabIndex); |
|
} |
|
} |
|
|
|
this.tabIndex = index; |
|
|
|
// #ifdef APP-NVUE |
|
this.scrollTabTo(index); |
|
// #endif |
|
// #ifndef APP-NVUE |
|
this.scrollInto = this.tabList[index].id; |
|
// #endif |
|
|
|
// 释放 tabId |
|
if (this.cacheTab.length > MAX_CACHE_PAGE) { |
|
let cacheIndex = this.cacheTab[0]; |
|
this.clearTabData(cacheIndex); |
|
this.cacheTab.splice(0, 1); |
|
} |
|
}, |
|
scrollTabTo(index) { |
|
const el = this.$refs['tabitem' + index][0]; |
|
let offset = 0; |
|
// TODO fix ios offset |
|
if (index > 0) { |
|
offset = this.tabbarWidth / 2 - this.tabListSize[index].width / 2; |
|
if (this.tabListSize[index].right < this.tabbarWidth / 2) { |
|
offset = this.tabListSize[0].width; |
|
} |
|
} |
|
dom.scrollToElement(el, { |
|
offset: -offset |
|
}); |
|
}, |
|
loadTabData(index) { |
|
this.pageList[index].loadData(); |
|
}, |
|
clearTabData(index) { |
|
this.pageList[index].clear(); |
|
} |
|
} |
|
} |
|
</script> |
|
|
|
<style> |
|
/* #ifndef APP-PLUS */ |
|
page { |
|
width: 100%; |
|
min-height: 100%; |
|
display: flex; |
|
} |
|
|
|
/* #endif */ |
|
|
|
.tabs { |
|
flex: 1; |
|
flex-direction: column; |
|
overflow: hidden; |
|
background-color: #ffffff; |
|
/* #ifdef MP-ALIPAY || MP-BAIDU */ |
|
height: 100vh; |
|
/* #endif */ |
|
} |
|
|
|
.tab-bar { |
|
width: 750rpx; |
|
height: 84rpx; |
|
flex-direction: row; |
|
/* #ifndef APP-PLUS */ |
|
white-space: nowrap; |
|
/* #endif */ |
|
} |
|
|
|
/* #ifndef APP-NVUE */ |
|
.tab-bar ::-webkit-scrollbar { |
|
display: none; |
|
width: 0 !important; |
|
height: 0 !important; |
|
-webkit-appearance: none; |
|
background: transparent; |
|
} |
|
|
|
/* #endif */ |
|
|
|
.scroll-view-indicator { |
|
position: relative; |
|
height: 2px; |
|
background-color: transparent; |
|
} |
|
|
|
.scroll-view-underline { |
|
position: absolute; |
|
top: 0; |
|
bottom: 0; |
|
width: 0; |
|
background-color: #007AFF; |
|
} |
|
|
|
.scroll-view-animation { |
|
transition-duration: 0.2s; |
|
transition-property: left; |
|
} |
|
|
|
.tab-bar-line { |
|
height: 1rpx; |
|
background-color: #cccccc; |
|
} |
|
|
|
.tab-view { |
|
flex: 1; |
|
} |
|
|
|
.uni-tab-item { |
|
/* #ifndef APP-PLUS */ |
|
display: inline-block; |
|
/* #endif */ |
|
flex-wrap: nowrap; |
|
padding-left: 25px; |
|
padding-right: 25px; |
|
} |
|
|
|
.uni-tab-item-title { |
|
color: #555; |
|
font-size: 30rpx; |
|
height: 80rpx; |
|
line-height: 80rpx; |
|
flex-wrap: nowrap; |
|
/* #ifndef APP-PLUS */ |
|
white-space: nowrap; |
|
/* #endif */ |
|
} |
|
|
|
.uni-tab-item-title-active { |
|
color: #007AFF; |
|
} |
|
|
|
.swiper-item { |
|
flex: 1; |
|
flex-direction: column; |
|
} |
|
|
|
.swiper-page { |
|
flex: 1; |
|
flex-direction: row; |
|
position: absolute; |
|
left: 0; |
|
top: 0; |
|
right: 0; |
|
bottom: 0; |
|
} |
|
</style>
|
|
|