自定义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>
相关推荐
诗句藏于尽头3 小时前
音乐播放器-单html文件
前端·html
歪歪1003 小时前
ts-jest与其他TypeScript测试工具的对比
前端·javascript·测试工具·typescript·前端框架
CodeSheep3 小时前
JetBrains官宣,又一个IDE可以免费用了!
前端·后端·程序员
刘新明19893 小时前
Frida辅助分析OLLVM虚假控制流程(下)
java·开发语言·前端
江城开朗的豌豆4 小时前
小程序登录不迷路:一篇文章搞定用户身份验证
前端·javascript·微信小程序
aesthetician4 小时前
React 19.2.0: 新特性与优化深度解析
前端·javascript·react.js
FIN66684 小时前
射频技术领域的领航者,昂瑞微IPO即将上会审议
前端·人工智能·前端框架·信息与通信
U.2 SSD4 小时前
ECharts漏斗图示例
前端·javascript·echarts
江城开朗的豌豆4 小时前
我的小程序登录优化记:从短信验证到“一键获取”手机号
前端·javascript·微信小程序