基於 VUE(Element UI) 的 PC 端 自定義索引欄
阿新 • • 發佈:2021-09-28
最近接到一個需求如下圖,找類似的元件沒找到,只能自己實現了。查閱資料並借鑑了 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