zoukankan      html  css  js  c++  java
  • 基于 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

  • 相关阅读:
    Mysql表连接查询
    mysql查询语句 和 多表关联查询 以及 子查询
    MySql 模糊查询、范围查询
    Mysql外键约束设置使用方法
    python基础:re模块匹配时贪婪和非贪婪模式
    解决Ubuntu 16.04下提示boot分区空间不足的办法
    String,StringBuffer,StringBuilder的区别
    多线程模拟生产者和消费者模型
    线程同步处理
    多线程的三种实现
  • 原文地址:https://www.cnblogs.com/zdd2017/p/15300666.html
Copyright © 2011-2022 走看看