Skip to content

Index-Bar 索引栏组件

用于快速定位列表内容的索引栏组件,支持点击和滑动两种交互方式,适配小程序和App平台。

📌平台差异说明

APP(vue)H5微信小程序支付宝小程序

🏯基本使用示例

html
<template>
  <view class="index-bar-demo">
    <view class="content-list">
      <view 
        v-for="(group, index) in contactList" 
        :key="index"
        :id="`section-${group.index}`"
        class="contact-group"
      >
        <view class="group-title">{{ group.index }}</view>
        <view v-for="(contact, idx) in group.list" :key="idx" class="contact-item">
          {{ contact.name }}
        </view>
      </view>
    </view>
    
    <!-- 索引栏 -->
    <hy-index-bar 
      v-model="activeIndex" 
      :index-list="indexList" 
      @scroll="handleScroll"
    />
  </view>
</template>

<script setup>
import { ref, computed } from 'vue'

// 联系人数据
const contactList = ref([
  { index: 'A', list: [{ name: '阿杰' }, { name: '阿明' }] },
  { index: 'B', list: [{ name: '贝贝' }, { name: '宝宝' }] },
  // 更多数据...
])

// 索引列表
const indexList = computed(() => contactList.value.map(item => item.index))

// 当前激活的索引
const activeIndex = ref('A')

// 滚动事件处理
const handleScroll = (index) => {
  // 根据索引滚动到对应位置
  uni.createSelectorQuery()
    .select(`#section-${index}`)
    .boundingClientRect(rect => {
      if (rect) {
        uni.pageScrollTo({
          scrollTop: rect.top,
          duration: 300
        })
      }
    })
    .exec()
}
</script>

<style lang="scss" scoped>
.index-bar-demo {
  position: relative;
  height: 100vh;
}

.content-list {
  padding: 20rpx;
}

.contact-group {
  margin-bottom: 40rpx;
}

.group-title {
  font-size: 32rpx;
  font-weight: bold;
  padding: 10rpx 0;
  background-color: #f5f5f5;
}

.contact-item {
  font-size: 28rpx;
  padding: 20rpx 0;
  border-bottom: 1rpx solid #eee;
}
</style>

隐藏提示框

html
<hy-index-bar 
  v-model="activeIndex" 
  :index-list="indexList" 
  :show-toast="false"
/>

自定义样式

html
<hy-index-bar 
  v-model="activeIndex" 
  :index-list="indexList"
  :index-color="'#666'"
  :active-index-color="'#007AFF'"
  :index-bg-color="'#f0f0f0'"
  :active-index-bg-color="'#e0e0e0'"
  :index-size="14"
  :width="30"
  :height="'80%'"
  :customStyle="{ backgroundColor: 'rgba(255, 255, 255, 0.8)' }"
/>

索引列表数据格式

支持两种数据格式:

1. 字符串数组

js
const indexList = ref(['A', 'B', 'C', 'D', 'E'])

2. 对象数组

js
const indexList = ref([
  { index: 'A', data: [...] },
  { index: 'B', data: [...] },
  { index: 'C', data: [...] }
])

完整代码

vue
<template>
  <hy-config-provider>
    <scroll-view class="hy-scroll-view" scroll-y :scroll-top="scrollTop" @scroll="handleScroll">
      <view
          v-for="item in indexList"
          :key="item.index"
          class="hy-index-section"
          :id="`index-${item.index}`"
      >
        <view class="hy-index-section__title hy-title">{{ item.index }}</view>
        <view class="hy-index-section__container">
          <view
              v-for="(city, cityIndex) in item.data"
              :key="cityIndex"
              class="hy-index-section__item"
          >
            {{ city }}
          </view>
        </view>
      </view>
    </scroll-view>

    <hy-index-bar
        v-model="currentActiveIndex"
        :index-list="indexs"
        :position="position"
        :show-toast="showToast"
        @click="handleIndexClick"
        @scroll="handleIndexScroll"
    />
  </hy-config-provider>
</template>
ts
import { ref, reactive, computed, getCurrentInstance, nextTick, onMounted } from 'vue'
import { storeToRefs } from 'pinia'
import { getRect, sleep } from 'hy-app'

const instance = getCurrentInstance()
// 索引栏位置
const position = ref<string>('right')
const sectionRect = ref<UniNamespace.NodeInfo[]>([])
const scrollTop = ref<number>(0)
// 是否显示提示
const showToast = ref<boolean>(true)
// 当前激活的索引
const currentActiveIndex = ref<string>('')
const isScroll = ref<boolean>(true)
// 模拟城市数据
const cityData = reactive({
    A: ['安庆', '安阳', '鞍山', '安顺', '安康', '阿克苏', '阿勒泰'],
    B: [
        '北京',
        '上海',
        '广州',
        '深圳',
        '重庆',
        '成都',
        '杭州',
        '武汉',
        '西安',
        '苏州',
        '天津',
        '南京',
        '长沙',
        '郑州',
        '东莞',
        '青岛',
        '沈阳',
        '宁波',
        '昆明'
    ],
    C: ['长春', '常州', '长沙', '成都', '重庆', '福州', '长春', '长沙'],
    D: ['大连', '东莞', '德州', '大同', '大庆', '大理'],
    E: ['鄂州', '恩施', '鄂州'],
    F: ['福州', '佛山', '抚顺', '阜新', '阜阳', '抚州'],
    G: ['广州', '贵阳', '桂林', '赣州', '广元', '广安'],
    H: ['杭州', '合肥', '哈尔滨', '海口', '呼和浩特', '湖州', '邯郸', '汉中', '衡水'],
    J: ['济南', '长春', '吉林', '锦州', '荆州', '荆门', '晋江'],
    K: ['昆明', '开封', '克拉玛依', '喀什'],
    L: ['兰州', '洛阳', '临沂', '聊城', '龙岩', '丽江'],
    M: ['绵阳', '马鞍山', '茂名', '梅州', '眉山'],
    N: ['南京', '南昌', '南宁', '南通', '宁波', '南阳', '南充'],
    P: ['平顶山', '盘锦', '攀枝花', '萍乡'],
    Q: ['青岛', '泉州', '曲靖', '秦皇岛', '齐齐哈尔', '庆阳'],
    R: ['日照', '日喀则', '荣昌'],
    S: ['深圳', '苏州', '沈阳', '石家庄', '绍兴', '厦门', '商丘', '上饶', '十堰'],
    T: ['天津', '太原', '唐山', '泰州', '台州', '通辽', '吐鲁番'],
    W: ['武汉', '西安', '温州', '无锡', '潍坊', '乌鲁木齐', '威海', '渭南'],
    X: ['西安', '厦门', '徐州', '新乡', '信阳', '襄阳', '西宁'],
    Y: ['烟台', '扬州', '宜昌', '岳阳', '延安', '银川', '运城'],
    Z: ['郑州', '株洲', '珠海', '中山', '湛江', '肇庆', '张家口', '枣庄']
})

// 转换为索引列表格式
const indexList = computed(() => {
    return Object.keys(cityData)
        .sort()
        .map((key) => ({
            index: key,
            title: key,
            data: cityData[key as keyof typeof cityData]
        }))
})
const indexs = computed(() => {
    return Object.keys(cityData).sort()
})

onMounted(() => {
    nextTick(() => {
        getRect('.hy-index-section', true).then((rect) => {
            sectionRect.value = rect
        })
    })
})

// 处理滚动事件
const handleScroll = (e: any) => {
    // 使用uni.createSelectorQuery()获取所有索引section的位置信息
    const scrollTop = e.detail.scrollTop
    if (isScroll.value) {
        const index = sectionRect.value.findIndex((item) => item.top - 1 > scrollTop) - 1
        const keys = Object.keys(cityData)
        console.log(keys[index], '==')
        currentActiveIndex.value = keys[index]
    }
}

// 处理索引点击事件
const handleIndexClick = (index: string, event: Event) => {
    scrollToSection(index)
}

// 处理索引滚动事件
const handleIndexScroll = (index: string) => {
    scrollToSection(index)
}

const scrollToSection = async (i: string) => {
    isScroll.value = false
    const index = Object.keys(cityData).findIndex((item) => item === i)
    scrollTop.value = sectionRect.value[index]?.top!
    await sleep()
    isScroll.value = true
}
scss
.hy-scroll-view {
    width: 100%;
    height: 100%;
}

.hy-index-section {
  padding: $hy-border-margin-padding-base;

  @include e(title) {
  }

  @include e(item) {
    font-size: 28rpx;
    padding: $hy-border-margin-padding-base;
    border-bottom: $hy-border-line;
    &:last-child {
      border: none;
    }
  }

  @include e(container) {
    padding: $hy-border-margin-padding-base 0;
    border-radius: $hy-border-radius-base;
    background: $hy-background--container;
  }
}

API

参数说明类型默认值
v-model当前激活的索引值string-
indexList索引列表数据,支持字符串数组或对象数组string|number|IIndexItem[]-
position索引栏位置,可选值:'left'、'right'left|rightright
showToast是否显示索引字母提示框booleantrue
indexColor索引项颜色string-
activeIndexColor激活状态索引项颜色string-
indexBgColor索引项背景色stringtransparent
activeIndexBgColor激活状态索引项背景色string-
indexSize索引项大小(像素)number|string12
height索引栏高度number|string100%
width索引栏宽度(像素)number/string20
customStyle自定义需要用到的外部样式CSSProperties-
customClass自定义外部类名string-

Events

事件名说明回调参数
click点击索引项时触发index: string - 当前点击的索引值
event: Event - 事件对象
scroll滑动索引栏时触发index: string - 当前滑动到的索引值
08:44