1. 程式人生 > 其它 >基於 VUE(Element UI) 的 PC 端 自定義索引欄

基於 VUE(Element UI) 的 PC 端 自定義索引欄

最近接到一個需求如下圖,找類似的元件沒找到,只能自己實現了。查閱資料並借鑑了 vant 元件庫的 indexBar 元件實現思想最終實現了需求,功能基本可以滿足,但肯定存在能優化的地方,僅供參考。

注意:本文滾動容器用的是Element UI 的 非官方 <el-scrollbar> 元件,涉及到一些與此相關的屬性。

1. 處理源資料,對資料按照首字母順序進行分類。

源資料格式:

[
        {
          name: "哈哈哈"
        },
        {
          name: "嘻嘻嘻"
        },
        {
          name: 
"嘿嘿嘿" }, { name: "喲喲喲" }, { name: "aaaa" } ]

我們需要的資料格式:

  {
        A:[ 
            {  name: "aaa" }
        ],
        H:[
            {  name: "哈哈哈"  },
            {  name: "嘿嘿嘿"  },
        ],
        X: [
            {  name: "嘻嘻嘻"  }
        ],
        Y: [
             {  name: 
"喲喲喲" } ] }

由父元件傳遞過來的資料:

props: {
    sourceData: {
      type: Array,
      default: () => []
    },
    name: {
      type: String,
      default: "shopName"
    }
}

sourceData 的處理方法(感覺方法寫得太複雜了,待優化):

// 用 js-pinyin 獲取漢字首字母
import pinyin from "js-pinyin"

getData() {
      pinyin.setOptions({ checkPolyphone: 
false, charCase: 0 }) let alphabet = [] let _charList = [] for (let i = 0; i < this.sourceData.length; i++) { // this.name 是作為排序依據的欄位名,由父元件傳入,在這裡就是 "name" // 獲取原陣列每一項的 name 值 let name = this.sourceData[i][this.name] // 獲取每一個name值第一個字的大寫首字母(傳入的 name 是中文時預設得到大寫字母,name 是英文時按照原字串輸出,可能是小寫) let initial = pinyin.getCamelChars(name).substring(0, 1).toUpperCase() // 給陣列每一項增加名為 initial 的 key,值就是第一個字的大寫首字母 this.sourceData[i].initial = initial // 獲取用於索引的字母 if (alphabet.indexOf(initial) === -1) { alphabet.push(initial) } } // 按字母表順序排序 alphabet.sort() // 給每個字母增加唯一標識,後面定位時會用到 for (var i = 0; i < alphabet.length; i++) { _charList.push({ id: i, key: alphabet[i] }) } this.charList = _charList let resultData = {} // 將源資料按照首字母分類 for (let i = 0; i < alphabet.length; i++) { resultData[alphabet[i]] = this.sourceData.filter((item) => { return item.initial === alphabet[i] }) } // 得到最終結果 this.indexData = resultData },

2. 元件結構和樣式(這部分沒啥好說的)

<template>
  <div class="index-bar-content">
    <el-scrollbar style="height: 100%" ref="scrollbar">
      <div :id="key" class="main-list" v-for="(value, key) in indexData" :key="key" ref="listGroup">
        <div class="title-key">{{ key }}</div>
        <div class="content-container">
          <div class="content-item" v-for="(val, index) in value" :key="index">
            {{ val[name] }}
          </div>
        </div>
      </div>
    </el-scrollbar>
    <!-- 右側字母列表 -->
    <ul class="char-list">
      <li v-if="totalPage > 1" @click="handlePreviousPage">
        <i class="iconfont iconshang"></i>
      </li>
      <li
        v-for="item in indexList"
        :key="item.id"
        @click="scrollToLetter(item)"
        :class="{ active: currentIndex === item.id }"
      >
        {{ item.key }}
      </li>
      <li v-if="totalPage > 1" @click="handleNextPage">
        <i class="iconfont iconxia"></i>
      </li>
    </ul>
  </div>
</template>
<style lang="scss">
.index-bar-content {
  position: relative;
  width: 400px;
  height: 304px;
  .el-scrollbar__wrap {
    overflow-x: hidden;
    .el-scrollbar__view {
      padding: 0 20px;
    }
  }
  .main-list {
    padding-top: 10px;
    .title-key {
      padding-bottom: 12px;
      font-size: 14px;
      font-weight: bold;
    }
    .content-container {
      display: flex;
      flex-wrap: wrap;
      .content-item {
        margin-right: 16px;
        margin-bottom: 12px;
        font-size: 12px;
      }
    }
  }
}
.el-popover {
  padding: 0;
}
.char-list {
  z-index: 99;
  width: 24px;
  height: 100%;
  background: #fafbfe;
  position: absolute;
  right: 0;
  top: 50%;
  transform: translateY(-50%);
  list-style: none;
  display: flex;
  flex-direction: column;
  text-align: center;
  li {
    height: 19px;
    display: inline;
    cursor: pointer;
    font-size: 14px;
  }
  .active {
    color: #fd4378;
  }
}
</style>

3.功能邏輯(最重要的部分)

首先來看點選右側索引快速定位的功能,第一個點是如何實現左側滾動。我這邊利用的是scrollTop屬性,只要計算出想要滾動的距離,給scrollTop賦值就可以定位到想要的位置了。

    // 計算每部分到容器頂部的距離,存入一個數組中
calculateHeight() {
    this.listHeight = []
    this.$nextTick(() => {
        const list = this.$refs.listGroup
        let height = 0
        this.listHeight.push(height)
        if (list) {
          for (let i = 0; i < list.length; i++) {
            let item = list[i]
            height += item.clientHeight
            this.listHeight.push(height)
          }
        }
    })
},
// 點選右側索引實現左側定位
scrollToLetter(item) {
    let scrollEle = this.$refs.scrollbar.wrap
    scrollEle.scrollTop = this.listHeight[item.id]
}

接下來實現右側索引欄可翻頁功能。每頁顯示多少條可以自己定,我這邊容器高度304px,每個索引元素高19px,所以一頁正好可以放置16個索引,除去上下翻頁的箭頭,就是14個。關鍵程式碼如下:

data() {
    return {
      // 當前頁
      indexPage: 1,
      // 每頁顯示數量
      indexLimit: 14
    }
},
computed: {
    totalPage() {
      return Math.ceil(this.charList.length / this.indexLimit)
    },
    // 計算當前頁的索引字母
    indexList() {
      return this.charList.slice(
        (this.indexPage - 1) * this.indexLimit,
        this.indexPage * this.indexLimit
      )
    }
},
methods: {
    handleNextPage() {
      if (this.indexPage < this.totalPage) {
        this.indexPage++
      }
    },
    handlePreviousPage() {
      if (this.indexPage > 1) {
        this.indexPage--
      }
    }
}

最後一個問題,左側滾動到某部分右側對應索引要高亮顯示。這裡需要監聽頁面的滾動事件並獲取滾動距離來確定具體位置。

handleScroll() {
    let scrollEle = this.$refs.scrollbar.wrap
    scrollEle.onscroll = () => {
        let newY = scrollEle.scrollTop
        const listHeight = this.listHeight
        // 在中間部分滾動
        for (let i = 0; i < listHeight.length - 1; i++) {
          let height1 = listHeight[i]
          let height2 = listHeight[i + 1]
          if (height1 <= newY && newY < height2) {
            this.currentIndex = i
            // 注意翻頁
            let currentPage = Math.floor(i / this.indexLimit) + 1
            this.indexPage = currentPage
            return
          }
      }
    }
}

總結:索引欄定位主要涉及到滾動距離的計算,具體到屬性有scrollTop、clientHeight,這些平時接觸得比較少,所以還是費了一番功夫,正好練一練。

完整程式碼:https://github.com/zdd2017/vue-components/blob/main/indexBar.vue

參考:https://www.cnblogs.com/marquess/p/12686500.html