Taro实现 微信小程序 可左右滑动切换的Tab组件

2022-10-27  本文已影响0人  R_X
image.png

左右滑动切换Tab

一、文件组成:

1、BgTitleTouchGroup.vue
<template>
    <!-- 可以滑屏切换的 tabGroup -->
    <view class="tab-group">
        <!-- 标题列表 -->
        <scroll-view
            id="bgTitleTouchGroup"
            :scroll-x="true"
            :scroll-into-view="`bgg${activeIndex}`"
            :scroll-with-animation="true"
            class="bg-title-list flex f-ai-c"
            :class="[align]"
        >
            <view
                v-for="(item, index) in newTabList"
                :id="`bgg${index}`"
                :key="index"
                class="tab-item-box"
            >
                <BgTitle
                    v-if="item.active"
                    :title="item.title"
                    :fontSize="30"
                />
                <text
                    v-else
                    class="normal"
                    :style="{
                        minWidth: normalMinWidth
                    }"
                    @tap="clickHandle(index)"
                >
                    {{ item.title }}
                </text>
            </view>
        </scroll-view>
        <!-- </view> -->
        <!-- 内容容器 -->
        <view
            class="content"
            :style="{'transition-duration': `${duration}s`, transform: `translateX(${xPositon}%)`}"
            @touchstart="onTouchStart"
            @touchmove="onTouchMove"
            @touchend="onTouchEnd"
        >
            <view class="tabs__track flex">
                <slot />
            </view>
        </view>
    </view>
</template>

<script>
import './BgTitleTouchGroup.less';

import BgTitle from '../BgTitle/index.vue';
import { getDirection, resetTouchStatus } from '@/combination';

export default {
    name: 'BgTitleTouchGroup',
    components: { BgTitle },
    inject: {
        normalMinWidth: {
            from: 'normalMinWidth',
            // default: '110rpx'
            default: 'auto'
        }
    },
    props: {
        // 过度动画的时间
        duration: {
            type: Number,
            default: 0.2
        },
        align: {
            type: String,
            default: 'f-jc-sb'
        }
    },
    data () {
        return {
            activeIndex: 0, // 当前查看的tab的索引
            newTabList: [],
            xPositon: 0, // 容器在X轴移动的距离
            swipeable: true,
            swiping: false,
            direction: '',
            deltaX: 0,
            deltaY: 0,
            offsetX: 0,
            offsetY: 0,
            startX: 0,
            startY: 0
        };
    },
    created () {
        this.initTabList();
    },
    methods: {
        setActiveIndex (index) {
            this.activeIndex = index;
            for (let i = 0; i < this.newTabList.length; i++) {
                if (i === index) {
                    this.newTabList[i].active = true;
                } else this.newTabList[i].active = false;
            }
        },
        initTabList () {
            let slots = this.$slots.default();
            // for循环方式添加的 tabItem, 反之则为逐个写入的tabItem
            if (slots.length === 1 && typeof slots[0].type === 'symbol') {
                slots = slots[0].children;
            }
            for (let i = 0; i < slots.length; i++) {
                this.newTabList.push({
                    title: slots[i].props.title,
                    active: !i
                });
            }
        },
        clickHandle (index) {
            for (let i = 0; i < this.newTabList.length; i++) {
                if (i === index) this.newTabList[i].active = true;
                else this.newTabList[i].active = false;
            }
            this.xPositon = -100 * index;
            this.activeIndex = index;
            this.$emit('bgTitleClick', index);
        },
        touchStart (event) {
            resetTouchStatus(this);
            var touch = event.touches[0];
            this.startX = touch.clientX;
            this.startY = touch.clientY;
        },
        touchMove (event) {
            var touch = event.touches[0];
            this.deltaX = touch.clientX - this.startX;
            this.deltaY = touch.clientY - this.startY;
            this.offsetX = Math.abs(this.deltaX);
            this.offsetY = Math.abs(this.deltaY);
            this.direction = this.direction || getDirection(this.offsetX, this.offsetY);
        },
        onTouchStart (event) {
            if (!this.swipeable) { return; }
            this.swiping = true;
            this.touchStart(event);
        },
        onTouchMove (event) {
            if (!this.swipeable || !this.swiping) { return; }
            this.touchMove(event);
        },
        // watch swipe touch end
        onTouchEnd () {
            if (!this.swipeable || !this.swiping) { return; }
            var _a = this; var direction = _a.direction; var deltaX = _a.deltaX; var offsetX = _a.offsetX;
            var minSwipeDistance = 50;
            if (direction === 'horizontal' && offsetX >= minSwipeDistance) {
                this.activeIndex = this.getAvaiableTab(deltaX);
                const list = this.newTabList.map((l, ind) => {
                    l.active = ind === this.activeIndex;
                    return l;
                });
                this.newTabList = list;
                this.xPositon = -100 * this.activeIndex;
                this.$emit('bgTitleClick', this.activeIndex);
            }
            this.swiping = false;
        },
        getAvaiableTab (direction) {
            var _a = this;
            var tabs = _a.newTabList;
            var currentIndex = _a.activeIndex;
            var step = direction > 0 ? -1 : 1;
            for (var i = step; currentIndex + i < tabs.length && currentIndex + i >= 0; i += step) {
                var index = currentIndex + i;
                if (index >= 0 && index < tabs.length && tabs[index] && !tabs[index].active) {
                    return index;
                }
            }
            return 0;
        }
    }
};
</script>
2、BgTitleTouchGroup.less
.tab-group {
    width: 100%;
    overflow: hidden;
    .bg-title-list {
        white-space: nowrap;
        .normal {
            display: inline-block;
            font-size: 26rpx;
        }
        .tab-item-box {
            display: inline-block;
            margin-right: 61rpx;
        }
        .tab-item-box:last-child {
            margin-right:0;
        }
    }
    .content {
        .tabs__track {
            position: relative;
            width: 100%;
            height: 100%;
            will-change: left;
        }
    }
}
3、BgTitleTouchItem.vue
<template>
    <!-- 可以滑屏切换的 tabItem -->
    <view class="tab-item">
        <slot />
    </view>
</template>

<script>
import './index.less';

export default {
    name: 'BgTitleTouchItem',
    props: {
        title: {
            type: String,
            default: '标题1'
        }
    }
};
</script>
4、BgTitleTouchItem.less
.tab-item {
    width: 100%;
    flex-shrink: 0;
    box-sizing: border-box;
}
.tab-item_inactive {
    height: 0;
    overflow: visible;
}
5、announcement.vue
<template>
    <view>
        <BgTitleTouchGroup
            v-if="tabList.length"
            :align="'f-jc-fs'"
            @bgTitleClick="bgTitleClickHandle"
        >
            <BgTitleTouchItem
                v-for="(ite, ind) in tabList"
                :key="ite.id"
                :title="ite.typeName"
            >
                <view v-if="activeTypeIndex === ind">
                    <scroll-view
                        v-if="recodes.length"
                        :scroll-y="true"
                        class="announs"
                        @scrolltolower="onTolowerMixin(getRecodeList)"
                            >
                        <CommonListItem
                            v-for="item in recodes"
                            :key="item.id"
                            :record="item"
                            style="margin-bottom: 30rpx;"
                            @equiClick="clickHandle(item.id)"
                        />
                    </scroll-view>
                    <NoData
                        v-else
                        style="margin-top: 20rpx;"
                    />
                </view>
            </BgTitleTouchItem>
        </BgTitleTouchGroup>
        <NoData
            v-else
            style="margin-top: 20rpx;"
        />
    </view>
</template>
<script>
    import {
        BgTitleTouchGroup,
        BgTitleTouchItem,
        CommonListItem,
        NoData
    } from '@/components';
export default {
    name: 'Announcement',
    components: {
        BgTitleTouchGroup,
        BgTitleTouchItem,
        CommonListItem,
        NoData
    },
    data () {
        return {
            tabList: [],
            recodes: [],
            activeTypeId: '',
            activeTypeIndex: 0
        };
    },
    methods: {
        // 某个类型title点击
        bgTitleClickHandle (index) {
            this.activeTypeId = this.tabList[index].id;
            this.activeTypeIndex = index;
            this.initRecords();
            this.getRecodeList();
        },
    }
};
</script>
上一篇下一篇

猜你喜欢

热点阅读