自定义tabs+索引列表,支持左右滑动切换

目录

自定义tabs+索引列表,支持左右滑动切换

创建scroll-x-tabs组件

html 复制代码
<view class="container">
    <scroll-view
      class="scroll-view"
      scroll-x="true"
      :scroll-left="scrollLeft"
      scroll-with-animation
      show-scrollbar="false"
    >
      <view
        v-for="(item, index) in list"
        :key="`${item.dictValue}-${index}-${item.dictCode}`"
        class="scroll-item"
        @click="handleItemClick(index)"
        :style="{
          color:
            (index === currentIndex ? computedActiveColor : computedColor) +
            ' !important',
        }"
      >
        <text>{{ item.dictLabel }}</text>
      </view>
      <!-- 指示条 -->
      <view
        v-if="list.length"
        class="indicator"
        :style="{
          ...indicatorStyle,
          background: lineColor,
          height: lineHeight,
          borderRadius: lineBorderRadius,
        }"
      ></view>
    </scroll-view>
  </view>
javascript 复制代码
props: {
    list: {
      type: Array,
      default: () => [],
    },
    color: {
      type: String,
      default: null,
    },
    activeColor: {
      type: String,
      default: null,
    },
    lineColor: {
      type: String,
      default: "rgba(0,0,0,0.5)",
    },
    lineHeight: {
      type: String,
      default: "6rpx",
    },
    lineBorderRadius: {
      type: String,
      default: "3rpx",
    },
    currentIndex: {
      type: Number,
      default: 0,
    },
  },

  data() {
    return {
      itemWidths: [], // 存储所有项的宽度
      containerWidth: 0, // 容器宽度
      scrollLeft: 0,
      indicatorLeft: 0,
      indicatorWidth: 60,
    };
  },
  computed: {
    ...mapGetters(["appTheme"]),
    computedColor() {
      if (this.color) return this.color;
      return this.appTheme === "dark" ? "#999999" : "#333333";
    },
    computedActiveColor() {
      if (this.activeColor) return this.activeColor;
      return this.appTheme === "dark" ? "#ffffff" : "#000000";
    },
    indicatorStyle() {
      return {
        width: this.itemWidths[this.currentIndex]
          ? `${this.itemWidths[this.currentIndex]}px`
          : "60px",
        transform: `translateX(${this.calculateIndicatorPosition()}px)`,
      };
    },
  },
  watch: {
    list: {
      handler() {
        this.$nextTick(() => {
          this.getItemWidths();
        });
      },
      deep: true,
    },
    currentIndex: {
      handler(newIndex) {
        this.$nextTick(() => {
          this.calculateScrollPosition();
        });
      },
    },
  },
  mounted() {
    this.getItemWidths();
  },
  methods: {
    getItemWidths(index = 0) {
      this.$nextTick(() => {
        const query = uni.createSelectorQuery().in(this);
        query
          .select(".scroll-view")
          .boundingClientRect((containerData) => {
            if (containerData) {
              this.containerWidth = containerData.width;
            }
          })
          .exec();

        query
          .selectAll(".scroll-item")
          .boundingClientRect((data) => {
            console.log("getItemWidths data", data);
            if (data) {
              // 保存所有项的宽度
              this.itemWidths = data.map((item) => item.width);
              // 计算当前项的位置
              this.calculateScrollPosition();
            }
          })
          .exec();
      });
    },
    calculateScrollPosition() {
      // 计算滚动位置,确保选中项在可视区域内
      let totalWidth = 0;
      for (let i = 0; i < this.currentIndex; i++) {
        totalWidth += this.itemWidths[i] || 120; // 如果没有宽度数据,使用默认值
      }

      // 计算选中项的中心位置
      const currentItemWidth = this.itemWidths[this.currentIndex] || 120;
      const targetPosition = totalWidth + currentItemWidth / 2;

      // 计算需要滚动的距离,使选中项居中
      this.scrollLeft = Math.max(0, targetPosition - this.containerWidth / 2);
    },
    calculateIndicatorPosition() {
      // 计算指示器位置
      let totalWidth = 0;
      for (let i = 0; i < this.currentIndex; i++) {
        totalWidth += this.itemWidths[i] || 120;
      }
      return totalWidth;
    },
    handleItemClick(index) {
      this.$emit("update:currentIndex", index);
      this.$emit("onHandleItemClick", index);
      this.$nextTick(() => {
        this.calculateScrollPosition();
      });
    },
  },
css 复制代码
.container {
  width: 100%;
  padding: 10rpx 0;
}

.scroll-view {
  width: 100%;
  white-space: nowrap;
  height: 80rpx;
  box-sizing: border-box;
}

.scroll-item {
  display: inline-block;
  padding: 0 30rpx;
  height: 70rpx;
  line-height: 70rpx;
  text-align: center;
  font-size: 30rpx;
  color: #333;
}

.indicator {
  transition: all 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}

创建touch-list组件

html 复制代码
<swiper
    class="swiper-container"
    :current="currentIndex"
    @change="handleSwiperChange"
    :duration="300"
    :style="{ height: swiperHeight }"
  >
    <swiper-item
      v-for="(item, index) in listLength"
      :key="index"
      class="swiper-item"
    >
      <view class="content" v-if="index === currentIndex">
        <slot></slot>
      </view>
    </swiper-item>
  </swiper>
javascript 复制代码
  props: {
    currentIndex: {
      type: Number,
      default: 0,
    },
    listLength: {
      type: Number,
      default: 0,
    },
    swiperHeight: {
      type: String,
      default: "500rpx",
    },
  },
  methods: {
    handleSwiperChange(e) {
      this.$emit("update:currentIndex", e.detail.current);
      this.$emit("onHandleSwiperChange", e.detail.current);
    },
  },
css 复制代码
.swiper-container {
  width: 100%;
}

.swiper-item {
  width: 100%;
  height: 100%;
}

.content {
  width: 100%;
  height: 100%;
  overflow: scroll;
}

父组件使用

html 复制代码
<scroll-x-tabs
   :list="exploreTypeDictList"
   :currentIndex.sync="current"
   :line-color="lineColor"
 ></scroll-x-tabs>
 
 <touch-list
    :list-length="exploreTypeDictList.length"
    :currentIndex.sync="current"
    :swiperHeight="swiperHeight"
    @onHandleSwiperChange="onHandleSwiperChange"
>
    <view class="learn-grid" v-if="studyList.length">
          <!--列表内容-->
    </view>
 </touch-list>
相关推荐
daols8842 分钟前
vxe-table 如何实现跟 excel 一样的筛选框,支持字符串、数值、日期类型筛选
前端·javascript·excel·vxe-table
青青子衿悠悠我心1 小时前
围小猫秘籍
前端
私人珍藏库1 小时前
[Windows] Chrome_Win64_v142.0.7444.163 便携版
前端·chrome
Wect1 小时前
Monorepo 架构全解析:从概念到落地的完整指南
前端
panda49191 小时前
css主流布局
前端·css
一千柯橘1 小时前
vite 下使用 Module Federation
前端
风止何安啊1 小时前
快 2026 年了,谁还在为 this 挠头?看完这篇让你彻底从懵圈到精通
前端·javascript·node.js
烟袅1 小时前
从零开始:前端如何通过 `fetch` 调用 大模型(详解)
前端·javascript·llm
Electrolux2 小时前
基于WASM的纯前端Office解决方案:在线编辑/导入导出/权限切换(已开源)
前端
合作小小程序员小小店2 小时前
web网页开发,在线%医院诊断管理%系统,基于Idea,html,css,jQuery,java,jsp,ssh,mysql。
java·前端·css·数据库·jdk·html·intellij-idea