uniapp自定义tabs加刷新
1.插件
https://ext.dcloud.net.cn/plugin?id=343
2.main.js引入
3.示例
<template>
<view>
<!-- 菜单 -->
<view class="top-warp">
<view class="tip" @click="tabChange()">每次切换菜单及时刷新列表,不缓存数据</view>
<me-tabs v-model="tabIndex" :tabs="tabs" @change="tabChange"></me-tabs>
</view>
<!-- top="xxx"下拉布局往下偏移,防止被悬浮菜单遮住 -->
<mescroll-body ref="mescrollRef" @init="mescrollInit" top="120" @down="downCallback" :up="upOption" @up="upCallback"
@emptyclick="emptyClick">
<!-- 数据列表 -->
<!-- <good-list :list="goods"></good-list> -->
<view v-if="this.tabIndex==0">hao</view>
<view v-if="this.tabIndex==1">hao1</view>
<view v-if="this.tabIndex==2">hao2</view>
<view v-if="this.tabIndex==3">hao3</view>
</mescroll-body>
</view>
</template>
<script>
import MescrollMixin from "@/components/mescroll-uni/mescroll-mixins.js";
import {
apiSearch
} from "@/api/mock.js"
export default {
mixins: [MescrollMixin], // 使用mixin (在main.js注册全局组件)
data() {
return {
upOption: {
// page: {
// num: 0, // 当前页码,默认0,回调之前会加1,即callback(page)会从1开始
// size: 10 // 每页数据的数量
// },
noMoreSize: 4, //如果列表已无数据,可设置列表的总数量要大于半页才显示无更多数据;避免列表数据过少(比如只有一条数据),显示无更多数据会不好看; 默认5
empty: {
tip: '~ 搜索无数据 ~', // 提示
btnText: '去看看'
}
},
goods: [], //列表数据
tabs: ['全部', '奶粉', '面膜', '图书'],
tabIndex: 0 // tab下标
}
},
methods: {
/*上拉加载的回调: 其中page.num:当前页 从1开始, page.size:每页数据条数,默认10 */
upCallback(page) {
//联网加载数据
let keyword = this.tabs[this.tabIndex]
apiSearch(page.num, page.size, keyword).then(curPageData => {
//联网成功的回调,隐藏下拉刷新和上拉加载的状态;
this.mescroll.endSuccess(curPageData.length);
//设置列表数据
if (page.num == 1) this.goods = []; //如果是第一页需手动制空列表
this.goods = this.goods.concat(curPageData); //追加新数据
}).catch(() => {
//联网失败, 结束加载
this.mescroll.endErr();
})
},
//点击空布局按钮的回调
emptyClick() {
uni.showToast({
title: '点击了按钮,具体逻辑自行实现'
})
},
// 切换菜单
tabChange() {
this.goods = [] // 先置空列表,显示加载进度
this.mescroll.resetUpScroll() // 再刷新列表数据
}
}
}
</script>
<style>
.top-warp {
z-index: 9990;
position: fixed;
top: --window-top;
/* css变量 */
left: 0;
width: 100%;
height: 120upx;
background-color: white;
}
.top-warp .tip {
font-size: 28upx;
height: 60upx;
line-height: 60upx;
text-align: center;
}
</style>
4.自定义tabs样式
<!-- tab组件: <me-tabs v-model="tabIndex" :tabs="tabs" @change="tabChange"></me-tabs> -->
<template>
<view class="me-tabs" :class="{'tabs-fixed': fixed}" :style="{height: tabHeightVal}">
<scroll-view v-if="tabs.length" :id="viewId" :scroll-left="scrollLeft" scroll-x scroll-with-animation
:scroll-animation-duration="300">
<view class="tabs-item" :class="{'tabs-flex':!isScroll, 'tabs-scroll':isScroll}">
<!-- tab -->
<view class="tab-item" :style="{width: tabWidthVal, height: tabHeightVal, 'line-height':tabHeightVal}" v-for="(tab, i) in tabs"
:class="{'active': value===i}" :key="i" @click="tabClick(i)">
<view style="position: relative; height: 0rpx; top: -10rpx; left: 30rpx;">6</view>
{{getTabName(tab)}}
</view>
<!-- 下划线 -->
<view class="tabs-line" :style="{left:lineLeft}"></view>
</view>
</scroll-view>
</view>
</template>
<script>
export default {
props: {
tabs: { // 支持格式: ['全部', '待付款'] 或 [{name:'全部'}, {name:'待付款'}]
type: Array,
default () {
return []
}
},
nameKey: { // 取name的字段
type: String,
default: 'name'
},
value: { // 当前显示的下标 (使用v-model语法糖: 1.props需为value; 2.需回调input事件)
type: [String, Number],
default: 0
},
fixed: Boolean, // 是否悬浮,默认false
tabWidth: Number, // 每个tab的宽度,默认不设置值,为flex平均分配; 如果指定宽度,则不使用flex,每个tab居左,超过则水平滑动(单位默认rpx)
height: { // 高度,单位rpx
type: Number,
default: 64
}
},
data() {
return {
viewId: 'id_' + Math.random().toString(36).substr(2, 16),
scrollLeft: 0
}
},
computed: {
isScroll() {
return this.tabWidth && this.tabs.length // 指定了tabWidth的宽度,则支持水平滑动
},
tabHeightPx() {
return uni.upx2px(this.height)
},
tabHeightVal() {
return this.tabHeightPx + 'px'
},
tabWidthPx() {
return uni.upx2px(this.tabWidth)
},
tabWidthVal() {
return this.isScroll ? this.tabWidthPx + 'px' : ''
},
lineLeft() {
if (this.isScroll) {
return this.tabWidthPx * this.value + this.tabWidthPx / 2 + 'px' // 需转为px (用rpx的话iOS真机显示有误差)
} else {
return 100 / this.tabs.length * (this.value + 1) - 100 / (this.tabs.length * 2) + '%'
}
}
},
watch: {
tabs() {
this.warpWidth = null; // 重新计算容器宽度
this.scrollCenter(); // 水平滚动到中间
},
value() {
this.scrollCenter(); // 水平滚动到中间
}
},
methods: {
getTabName(tab) {
return typeof tab === "object" ? tab[this.nameKey] : tab
},
tabClick(i) {
if (this.value != i) {
this.$emit("input", i);
this.$emit("change", i);
}
},
async scrollCenter() {
if (!this.isScroll) return;
if (!this.warpWidth) { // tabs容器的宽度
let rect = await this.initWarpRect()
this.warpWidth = rect ? rect.width : uni.getSystemInfoSync().windowWidth; // 某些情况下取不到宽度,暂时取屏幕宽度
}
let tabLeft = this.tabWidthPx * this.value + this.tabWidthPx / 2; // 当前tab中心点到左边的距离
let diff = tabLeft - this.warpWidth / 2 // 如果超过tabs容器的一半,则滚动差值
this.scrollLeft = diff;
// #ifdef MP-TOUTIAO
this.scrollTimer && clearTimeout(this.scrollTimer)
this.scrollTimer = setTimeout(() => { // 字节跳动小程序,需延时再次设置scrollLeft,否则tab切换跨度较大时不生效
this.scrollLeft = Math.ceil(diff)
}, 400)
// #endif
},
initWarpRect() {
return new Promise(resolve => {
setTimeout(() => { // 延时确保dom已渲染, 不使用$nextclick
let query = uni.createSelectorQuery();
// #ifndef MP-ALIPAY
query = query.in(this) // 支付宝小程序不支持in(this),而字节跳动小程序必须写in(this), 否则都取不到值
// #endif
query.select('#' + this.viewId).boundingClientRect(data => {
resolve(data)
}).exec();
}, 20)
})
}
},
mounted() {
this.scrollCenter() // 滚动到当前下标
}
}
</script>
<style lang="scss">
.me-tabs {
position: relative;
font-size: 24rpx;
background-color: #fff;
border-bottom: 1rpx solid #eee;
box-sizing: border-box;
overflow-y: hidden;
background-color: #fff;
&.tabs-fixed {
z-index: 990;
position: fixed;
top: var(--window-top);
left: 0;
width: 100%;
}
.tabs-item {
position: relative;
white-space: nowrap;
padding-bottom: 30rpx; // 撑开高度,再配合me-tabs的overflow-y: hidden,以达到隐藏滚动条的目的
box-sizing: border-box;
.tab-item {
position: relative;
text-align: center;
box-sizing: border-box;
&.active {
font-weight: bold;
color: red;
}
}
}
// 平分的方式显示item
.tabs-flex {
display: flex;
.tab-item {
flex: 1;
}
}
// 居左显示item,支持水平滑动
.tabs-scroll {
.tab-item {
display: inline-block;
}
}
// 选中tab的线
.tabs-line {
z-index: 1;
position: absolute;
bottom: 30rpx; // 至少与.tabs-item的padding-bottom一致,才能保证在底部边缘
width: 20rpx;
height: 6rpx;
transform: translateX(-50%);
border-radius: 4rpx;
transition: left .3s;
background: red;
}
}
</style>