点击看my-tabs的(uniapp+vue3+TypeScrit 自定义tabs - 掘金 (juejin.cn))组件
<template>
<my-tabs :tabsList="tabsList" @change="tabsChange"></my-tabs>
<view class="content" v-if="current === 1">
<view v-if="classify.showOrder" class="category-wrap">
<scroll-view scroll-y scroll-with-animation class="category-bar">
<view v-for="(item, index) in classify.cateData" :key="index" class="category-item" :class="[classify.current === index ? 'current' : '']" @click="switchCate(index)">
<text class="line-1">{{ item.name }}</text>
</view>
</scroll-view>
<scroll-view scroll-y scroll-with-animation class="category-content" :scroll-top="classify.scrollRightTop" @scroll="rightScroll">
<view style="background-color: #f9f9f9">
<view v-for="(item, index) in classify.cateData" :key="index" class="category-right-item" :class="{ 'category-right-item-last': classify.cateData.length == index + 1 }">
<view class="item-title">
<text>{{ item.name }}</text>
</view>
<view class="item-container">
<view v-for="(item1, index1) in item.childList" :key="index1" class="thumbnail-box">
<image class="item-cate-image" :src="item.icon || `${imgUrl}bg.jpg`" resize="_r60xp" @click="onGoods(item1)"></image>
<view class="item-cate-name">{{ item1.name }}</view>
</view>
</view>
</view>
</view>
</scroll-view>
</view>
</view>
<view class="room-box" v-else>
<view class="room-item" @click="onHandle(item)" :style="{ backgroundImage: `url(${imgUrl}bg.jpg)` }" v-for="(item, index) in 20">
<view class="room-label">客厅+{{ index }}</view>
<view class="icon">
<uni-icons type="right" size="23" color="#fff"></uni-icons>
</view>
</view>
</view>
</template>
<script lang="ts" setup>
import { onReady } from '@dcloudio/uni-app';
import myTabs from '@/components/myTabs.vue';
import { getShopCategory } from '@/api/api';
import { ref, reactive, inject } from 'vue';
const imgUrl = inject('imgUrl');
const current = ref(1);
const classify: any = reactive({
cateData: [], //分类数据结构
current: 0, //左边分类栏当前的选中的项
scrollLeftTop: 0, //左边分类栏项的高度
cateItemHeight: 0, //右边栏项的高度
cateBarHeight: 0, //左边分类栏的总高度
rightScrollArr: [], //右边栏每项高度组成的数组
scrollRightTop: 0, //当前右边栏滚动的高度
showOrder: true,
});
const tabsList = [
{ label: '品类', value: 1 },
{ label: '房间', value: 2 },
];
const roomList: any = ref([]);
onReady(() => {
getShopCategory().then((res: any) => {
if (res.code === 200) {
classify.cateData = res.data;
}
});
getCateItemTop();
});
function getCateItemTop() {
let query = uni.createSelectorQuery();
query
.selectAll('.category-right-item')
.boundingClientRect((rects: any) => {
rects.forEach((rect: any) => {
classify.rightScrollArr.push(rect.top - rects[0].top);
});
})
.exec(function () {});
}
async function switchCate(index: any) {
if (classify.rightScrollArr.length == 0) {
await getCateItemTop();
}
if (index === classify.current) return;
//将右边的scroll高度重设
classify.scrollRightTop = classify.rightScrollArr[index];
// 分类选中的居中
// classify.scrollRightTop = classify.rightScrollArr[index - 1];
cateDataStatus(index);
}
//设置 左边分类的滚动状态
function cateDataStatus(index: any) {
//当前选择的项
classify.current = index;
// 如果为0,意味着尚未初始化,调用getElRect获取目标元素的高度
if (classify.cateBarHeight == 0 || classify.cateItemHeight == 0) {
getElRect('.category-bar', 'cateBarHeight');
getElRect('.category-item', 'cateItemHeight');
}
//将该项垂直居中,算法: 左边scroll的scrollTop = 当前项的高度 - 分类栏的一半高度
classify.scrollLeftTop = index * classify.cateItemHeight + classify.cateItemHeight / 2 - classify.cateBarHeight / 2;
}
//获取一个目标元素的高度
function getElRect(elClass: any, dataVal: any) {
const query = uni.createSelectorQuery();
query
.select(elClass)
.fields(
{
size: true,
},
(res: any) => {
[dataVal] = res.height;
},
)
.exec(function () {});
}
async function rightScroll(e: any) {
if (classify.rightScrollArr.length == 0) {
await getCateItemTop();
}
if (!classify.cateBarHeight) {
await getElRect('.category-bar', 'cateBarHeight');
}
// scrollHeight的值等于滚动位置加上左边栏的高度一半是为了始终拿的是中间显示的右边项做对比
// let scrollHeight = e.detail.scrollTop + classify.cateBarHeight / 2;
let scrollHeight = e.detail.scrollTop;
for (let i = 0; i < classify.rightScrollArr.length; i++) {
let height1 = classify.rightScrollArr[i];
let height2 = classify.rightScrollArr[i + 1];
//如果不存在height2,意味着数据循环已经到了最后一个,设置左边菜单为最后一项即可。
// if (!height2 || (scrollHeight >= height1 && scrollHeight < height2)) {
if (!height2 || scrollHeight <= height1) {
cateDataStatus(i);
return;
}
}
}
function tabsChange(e: any) {
current.value = e;
}
function onHandle(val: any) {
console.log(val);
}
function onGoods(val: any) {
uni.navigateTo({ url: '/subPack/pointsMall/goods?data=' + encodeURIComponent(JSON.stringify(val)) });
}
</script>
<style lang="scss" scoped>
page {
font-size: 27rpx;
color: #303133;
}
.tabs .container {
position: sticky !important;
top: 0rpx;
}
.content {
display: flex;
flex-direction: column;
height: calc(100vh - 100rpx);
}
.category-wrap {
flex: 1;
display: flex;
overflow: hidden !important;
::-webkit-scrollbar {
display: none;
width: 0 !important;
height: 0 !important;
-webkit-appearance: none;
background: transparent;
color: transparent;
}
}
.category-bar {
width: 30%;
// background-color: #f6f6f6;
background-color: #f6f6f6;
.category-item {
text-align: center;
color: #1e2732;
padding: 20rpx 0rpx;
&.current {
// color: #1e2732;
color: #1e90ff;
background-color: #ffffff;
}
}
}
.search-box {
display: flex;
align-items: center;
margin: 24rpx;
background-color: #eaeaea;
border-radius: 50rpx;
padding: 10rpx 16rpx;
font-size: 26rpx;
color: #909399;
}
.line-1 {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
.category-right-item {
// margin-bottom: 30rpx;
background-color: #fff;
padding: 16rpx;
border-radius: 8rpx;
}
.category-right-item-last {
// height: calc(100vh - 84rpx - var(--window-top));
height: calc(100vh - 114rpx - var(--window-top));
}
.item-title {
font-size: 26rpx;
font-weight: bold;
color: #303133;
text-align: center;
}
.item-container {
display: flex;
flex-wrap: wrap;
}
.thumbnail-box {
width: 33.33%;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
margin-top: 20rpx;
}
.item-cate-name {
font-weight: normal;
font-size: 24rpx;
color: #303133;
}
.item-cate-image {
width: 120rpx;
height: 120rpx;
}
.room-box {
display: flex;
flex-direction: column;
gap: 20rpx;
padding: 10rpx 20rpx;
.room-item {
width: 100%;
background-size: 100%;
background-position: center;
background-repeat: no-repeat;
display: flex;
align-items: center;
justify-content: space-between;
padding: 55rpx 0rpx;
color: #fff;
border-radius: 10rpx;
.room-label {
padding-left: 30rpx;
}
.icon {
padding-right: 30rpx;
}
}
}
</style>