在UniApp中创建自定义的可自由滚动的精美选项卡组件,你可以结合UniApp的组件、样式和逻辑来实现。实现这样的效果首先选到的是scroll-view、view、 text三个组件组成,接下来我们使用工具教大家实现。
模板复用
此组件我们已经发布至组件模板,即需搜索选项卡、找到我们这个组件即可快速复用。
实现选项卡效果
FLEX组件拖动至设计区
首先找到我们的flex组件拖至设计区,开启水平滚动属性即可变成scroll-view组件。
选项卡标题设置
选项卡标题存在选中标题、非选中标题,我们使用FLEX组件装起来。定义选项卡的数据tabDatas,tabScrollLeft,tabIndex等属性
选中、非选中效果样式位置
选项卡包括一个选中、一个非选中效果。我们采用文本内容来分别设置对应的样式。
选中、非选中判断显示
选中的时候tabIndex==index
非选中的时候tabIndex!=index
点击事件切换选中
组件扩展方法增加自己的选中方法实现this.tabIndex=index
绑定点击事件
调用方法传入index
友好滚动扩展属性
为了点击对应的选项卡滚动至对应的选项卡尽量保持中间位置,使用了scroll-view里scroll-left属性。在第一步找到开启水平滚动flex组件增加扩展属性:scroll-with-animation="true" enhanced :scroll-left="tabsScrollLeft"
生成源码
生成源码即可在hbuilder实时查看效果。
<template>
<view class="container container329152">
<view class="flex flex-wrap diygw-col-24 flex-direction-column">
<text class="diygw-col-24 text1-clz"> 已绑定组件动态数据,导出源码起效。组件数据在上面一层扩组数据里,可以根据动态API的数据来绑定。使用时可删除此提示 </text>
<scroll-view scroll-x :scroll-with-animation="true" enhanced :scroll-left="tabsScrollLeft" class="flex scroll-view flex-wrap diygw-col-24 flex10-clz">
<view class="flex flex-nowrap">
<view v-for="(item, index) in tabDatas" :key="index" class="flex flex-wrap diygw-col-0 flex-direction-column items-center flex12-clz" @tap="selectTabs(index)">
<text v-if="tabIndex == index" class="diygw-text-line1 diygw-col-24 gradual-red text6-clz">
{{ item.title }}
</text>
<text v-if="tabIndex != index" class="diygw-text-line1 diygw-col-0 text2-clz">
{{ item.title }}
</text>
</view>
</view>
</scroll-view>
<scroll-view scroll-x :scroll-with-animation="true" enhanced :scroll-left="tabsScrollLeft" class="flex scroll-view flex-wrap diygw-col-24 flex5-clz">
<view class="flex flex-nowrap">
<view v-for="(item, index) in tabDatas" :key="index" class="flex flex-wrap diygw-col-0 flex-direction-column items-center flex6-clz" @tap="selectTabs(index)">
<text v-if="tabIndex == index" class="diygw-text-line1 diygw-col-24 gradual-orange text7-clz">
{{ item.title }}
</text>
<text v-if="tabIndex != index" class="diygw-text-line1 diygw-col-0 text8-clz">
{{ item.title }}
</text>
</view>
</view>
</scroll-view>
<scroll-view scroll-x :scroll-with-animation="true" enhanced :scroll-left="tabsScrollLeft" class="flex scroll-view flex-wrap diygw-col-24 flex3-clz">
<view class="flex flex-nowrap">
<view v-for="(item, index) in tabDatas" :key="index" class="flex flex-wrap diygw-col-0 flex-direction-column items-center flex4-clz" @tap="selectTabs(index)">
<text v-if="tabIndex == index" class="diygw-text-line1 diygw-col-24 gradual-green text4-clz">
{{ item.title }}
</text>
<text v-if="tabIndex != index" class="diygw-text-line1 diygw-col-0 text5-clz">
{{ item.title }}
</text>
</view>
</view>
</scroll-view>
<scroll-view scroll-x :scroll-with-animation="true" enhanced :scroll-left="tabsScrollLeft" class="flex scroll-view flex-wrap diygw-col-24 flex1-clz">
<view class="flex flex-nowrap">
<view v-for="(item, index) in tabDatas" :key="index" class="flex flex-wrap diygw-col-0 flex-direction-column items-center flex2-clz" @tap="selectTabs(index)">
<text v-if="tabIndex == index" class="diygw-text-line1 diygw-col-24 gradual-blue text-clz">
{{ item.title }}
</text>
<text v-if="tabIndex != index" class="diygw-text-line1 diygw-col-0 text3-clz">
{{ item.title }}
</text>
</view>
</view>
</scroll-view>
</view>
<view class="clearfix"></view>
</view>
</template>
<script>
export default {
data() {
return {
//用户全局信息
userInfo: {},
//页面传参
globalOption: {},
//自定义全局变量
globalData: { isshow: false },
listNum: 1,
list: {
rows: [
{
id: 0,
title: '',
remark: '',
img: '',
content: null,
cateId: 0,
userId: 0,
createTime: '',
updateTime: '',
deleteTime: null
}
],
total: 0,
code: 0,
msg: ''
},
catesNum: 1,
cates: {
rows: [
{
id: 0,
title: '',
remark: '',
img: '',
userId: 0,
createTime: '',
updateTime: '',
deleteTime: null
}
],
total: 0,
code: 0,
msg: ''
},
tabIndex: 0,
tabsScrollLeft: 0,
tabDatas: [{ title: '关注' }, { title: '精选' }, { title: '推荐' }, { title: '热门' }, { title: '热门' }, { title: '热门' }, { title: '热门' }],
};
},
onShow() {
this.setCurrentPage(this);
},
onLoad(option) {
this.setCurrentPage(this);
if (option) {
this.setData({
globalOption: this.getOption(option)
});
}
this.init();
},
methods: {
async init() {
await this.listApi();
await this.catesApi();
},
// 列表数据 API请求方法
async listApi(param) {
let thiz = this;
param = param || {};
//如果请求要重置页面,请配置点击附加参数refresh=1 增加判断如输入框回调param不是对象
if (param.refresh || typeof param != 'object') {
this.listNum = 1;
}
//请求地址及请求数据,可以在加载前执行上面增加自己的代码逻辑
let http_url = '/cms/api.article/list';
let http_data = {
pageNum: this.listNum,
pageSize: 10,
title_like: param.title_like || this.title
};
let http_header = {};
if (this.cates.rows.length > 0 && this.utabs > 0) {
let row = this.cates.rows[this.utabs];
if (row.id) {
http_data.cateId = row.id;
}
}
let list = await this.$http.post(http_url, http_data, http_header, 'json');
let datarows = list.rows;
if (http_data.pageNum == 1) {
this.list = list;
} else if (datarows) {
let rows = this.list.rows.concat(datarows);
list.rows = rows;
this.list = Object.assign(this.list, list);
}
if (datarows && datarows.length > 0) {
this.listNum = this.listNum + 1;
}
this.globalData.isshow = true;
},
// 分类数据 API请求方法
async catesApi(param) {
let thiz = this;
param = param || {};
//请求地址及请求数据,可以在加载前执行上面增加自己的代码逻辑
let http_url = '/cms/api.cate/list';
let http_data = {
pageNum: this.catesNum,
pageSize: 10,
pageSize: param.pageSize || '20'
};
let http_header = {};
let cates = await this.$http.post(http_url, http_data, http_header, 'json');
cates.rows.unshift({ id: '', title: '全部' });
this.cates = cates;
},
// 修改数据 自定义方法
async editFunction(param) {
let thiz = this;
let index = param && (param.index || param.index == 0) ? param.index : thiz.index || '';
this.form = JSON.parse(JSON.stringify(this.list.rows[param.index]));
this.navigateTo({
type: 'openmodal',
id: 'modal'
});
},
selectTabs(index) {
this.tabIndex = index;
//根据自己的选项卡动态调整下60大小
let tabsScrollLeft = index * 60;
if (index <= 2) {
tabsScrollLeft = 0;
}
this.tabsScrollLeft = tabsScrollLeft;
},
selectTabs(index) {
this.tabIndex = index;
//根据自己的选项卡动态调整下60大小
let tabsScrollLeft = index * 60;
if (index <= 2) {
tabsScrollLeft = 0;
}
this.tabsScrollLeft = tabsScrollLeft;
},
selectTabs(index) {
this.tabIndex = index;
//根据自己的选项卡动态调整下60大小
let tabsScrollLeft = index * 60;
if (index <= 2) {
tabsScrollLeft = 0;
}
this.tabsScrollLeft = tabsScrollLeft;
},
selectTabs(index) {
this.tabIndex = index;
//根据自己的选项卡动态调整下60大小
let tabsScrollLeft = index * 60;
if (index <= 2) {
tabsScrollLeft = 0;
}
this.tabsScrollLeft = tabsScrollLeft;
}
},
onPullDownRefresh() {
// 列表数据 API请求方法
this.listNum = 1;
this.listApi();
uni.stopPullDownRefresh();
},
onReachBottom() {
// 列表数据 API请求方法
this.listApi();
}
};
</script>
<style lang="scss" scoped>
.text1-clz {
margin-left: 10rpx;
padding-top: 10rpx;
padding-left: 10rpx;
width: calc(100% - 10rpx - 10rpx) !important;
padding-bottom: 10rpx;
margin-top: 10rpx;
margin-bottom: 10rpx;
margin-right: 10rpx;
padding-right: 10rpx;
}
.flex10-clz {
padding-top: 10rpx;
padding-left: 10rpx;
padding-bottom: 10rpx;
padding-right: 10rpx;
}
.flex12-clz {
margin-left: 0rpx;
margin-top: 0rpx;
margin-bottom: 0rpx;
margin-right: 10rpx;
}
.text6-clz {
padding-top: 10rpx;
border-bottom-left-radius: 120rpx;
overflow: hidden;
font-weight: bold;
padding-left: 30rpx;
padding-bottom: 10rpx;
border-top-left-radius: 120rpx;
border-top-right-radius: 120rpx;
border-bottom-right-radius: 120rpx;
padding-right: 30rpx;
}
.text2-clz {
background-color: #f4f4f4;
padding-top: 10rpx;
border-bottom-left-radius: 120rpx;
overflow: hidden;
padding-left: 30rpx;
padding-bottom: 10rpx;
border-top-left-radius: 120rpx;
border-top-right-radius: 120rpx;
border-bottom-right-radius: 120rpx;
padding-right: 30rpx;
}
.flex5-clz {
padding-top: 10rpx;
padding-left: 10rpx;
padding-bottom: 10rpx;
padding-right: 10rpx;
}
.flex6-clz {
margin-left: 0rpx;
margin-top: 0rpx;
margin-bottom: 0rpx;
margin-right: 10rpx;
}
.text7-clz {
padding-top: 10rpx;
border-bottom-left-radius: 120rpx;
overflow: hidden;
font-weight: bold;
padding-left: 30rpx;
padding-bottom: 10rpx;
border-top-left-radius: 120rpx;
border-top-right-radius: 120rpx;
border-bottom-right-radius: 120rpx;
padding-right: 30rpx;
}
.text8-clz {
background-color: #f4f4f4;
padding-top: 10rpx;
border-bottom-left-radius: 120rpx;
overflow: hidden;
padding-left: 30rpx;
padding-bottom: 10rpx;
border-top-left-radius: 120rpx;
border-top-right-radius: 120rpx;
border-bottom-right-radius: 120rpx;
padding-right: 30rpx;
}
.flex3-clz {
padding-top: 10rpx;
padding-left: 10rpx;
padding-bottom: 10rpx;
padding-right: 10rpx;
}
.flex4-clz {
margin-left: 0rpx;
margin-top: 0rpx;
margin-bottom: 0rpx;
margin-right: 10rpx;
}
.text4-clz {
padding-top: 10rpx;
border-bottom-left-radius: 120rpx;
overflow: hidden;
font-weight: bold;
padding-left: 30rpx;
padding-bottom: 10rpx;
border-top-left-radius: 120rpx;
border-top-right-radius: 120rpx;
border-bottom-right-radius: 120rpx;
padding-right: 30rpx;
}
.text5-clz {
background-color: #f4f4f4;
padding-top: 10rpx;
border-bottom-left-radius: 120rpx;
overflow: hidden;
padding-left: 30rpx;
padding-bottom: 10rpx;
border-top-left-radius: 120rpx;
border-top-right-radius: 120rpx;
border-bottom-right-radius: 120rpx;
padding-right: 30rpx;
}
.flex1-clz {
padding-top: 10rpx;
padding-left: 10rpx;
padding-bottom: 10rpx;
padding-right: 10rpx;
}
.flex2-clz {
margin-left: 0rpx;
margin-top: 0rpx;
margin-bottom: 0rpx;
margin-right: 10rpx;
}
.text-clz {
padding-top: 10rpx;
border-bottom-left-radius: 120rpx;
overflow: hidden;
font-weight: bold;
padding-left: 30rpx;
padding-bottom: 10rpx;
border-top-left-radius: 120rpx;
border-top-right-radius: 120rpx;
border-bottom-right-radius: 120rpx;
padding-right: 30rpx;
}
.text3-clz {
background-color: #f4f4f4;
padding-top: 10rpx;
border-bottom-left-radius: 120rpx;
overflow: hidden;
padding-left: 30rpx;
padding-bottom: 10rpx;
border-top-left-radius: 120rpx;
border-top-right-radius: 120rpx;
border-bottom-right-radius: 120rpx;
padding-right: 30rpx;
}
.container329152 {
}
</style>