小程序字母检索列表

先上效果

没错看着界面很像黄轶老师webapp的,其实就是想用小程序实现一个,可以根据界面滚动位置字母也实时高亮,也可以点击字母索引跳转到对应的位置。没有使用scroll-view组件,因为不可能什么功能都有现成的。话不多说下面上代码

在这里插入图片描述


1、处理后台返回数据


先看下后台返回的数据是什么样子的,原来是这样子的(大家看到效果图上第一个是周董,因为这是我手动添加的,周董必须放在第一位哦)

在这里插入图片描述
有时候啊,后台看心情还不一定给你返回A-Z的字母呢,说不定返回了还不给你排序(我就遇到过),所以呢我还是自己处理了一遍,这是处理后的数据
在这里插入图片描述
这里说一下思路吧

  1. 后端返回的数据中前10条我们认定为是热门歌手,定义一个Map歌手对象,例如
singerMap[] = {
  hot: {
     title: 'hot',
     list: [...]
  }
}
  1. 将歌手名字转为拼音,可以借助pinyin这个npm包,获取歌手名拼音的首字母,例如
    (tips:最好做一个判断,如果后台返回的数据为空或者返回错误的状态码,直接return,防止报错)
	// key即是这个歌手属于那个字母下面
	const key = pinyin.getCamelChars(item.name).substr(0,1)
      if (key) {
        if (!singerMap[key]) {
          singerMap[key] = {
            title: key,
            list: []
          }
        }
        // 每个字母下面会有多名歌手,获取歌手名拼音的首字母
        singerMap[key].list.push(map([item])[0])
      }
	
	// 这里我们用到了一个自己封装的map方法,主要是做一个映射,构造单个歌手的数据结构
	function map(arr) {
	  if (!Array.isArray(arr)) return
	  return arr.map(item => ({
	    id: item.id,
	    name: item.name,
	    pic: item.picUrl,
	    alias: item.alias,
	    picId_str: item.picId_str,
	    img1v1Url: item.img1v1Url
	  }))
	}
  1. 定义两个数组用来存储热门、字母的歌手;循环遍历处理后的singerMap
	 for (const key in singerMap) {
        const item = singerMap[key]
        if (item.title.match(/[a-zA-Z]/)) {
          letter.push(item)
        } else if (item.title === HOT) {
          hot.push(item)
        }
      }
  1. 最后将字母按顺序排序,将最终处理后的result返回出去
		// 按字母顺序排序
      letter.sort((a, b) => a.title.charCodeAt(0) - b.title.charCodeAt(0))
      result = {singers: hot.concat(letter)}
	  return result

好了,处理数据就说到这里,大家那么聪明的程序员应该都能处理好数据


2、编写基础页面


wxml代码

<!--components/SingerList/SingerList.wxml-->
<view class="index-list">
  <view id="groupRef">
    <view wx:for="{{data}}" wx:key="title" class="group">
      <view id="{{item.title}}" class="title">{{item.title}}</view>
      <view>
        <view wx:for="{{item.list}}" wx:key="id" wx:for-item="t" class="item">
          <image class="avatar" src="{{t.pic}}" lazy-load="{{true}}" />
          <span class="name">{{t.name}}</span>
        </view>
      </view>
    </view>
  </view>
  <!-- <div class="fixed" v-show="fixedTitle" :style="fixedStyle">
    <div class="fixed-title">{{fixedTitle}}</div>
  </div> -->
  <view class="shortcut">
    <view>
      <view
        wx:for="{{data}}"
        wx:key="index"
        data-id="{{item.title}}"
        data-index="{{index}}"
        catchtap="onScrollTo"
        class="shortcut-item {{curIndex === index ? 'active': ''}}"
      >
        {{item.title}}
      </view>
    </view>
  </view>
</view>

wxss代码

/* components/SingerList/SingerList.wxss */
.index-list {
  position: relative;
  width: 100%;
  height: 100%;
  overflow: hidden;
  background: #222;
}
.group {
  padding-bottom: 30rpx;
}
.title {
  height: 60rpx;
  line-height: 60rpx;
  padding-left: 20rpx;
  font-size: 24rpx;
  color: rgba(255, 255, 255, 0.5);
  background-color: #333;
}
.item {
  display: flex;
  align-items: center;
  padding: 40rpx 0 0 40rpx;
}
.avatar {
  width: 100rpx;
  height: 100rpx;
  border-radius: 50%;
}
.name {
  margin-left: 40rpx;
  color: rgba(255, 255, 255, 0.5);
  font-size: 28rpx;
}
.fixed {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
}
.fixed-title {
  height: 60rpx;
  line-height: 60rpx;
  padding-left: 40rpx;
  font-size: 24rpx;
  color: rgba(255, 255, 255, 0.5);
  background: #333;
}
.shortcut {
  position: fixed;
  right: 18rpx;
  top: 50%;
  transform: translateY(-50%);
  width: 40rpx;
  padding: 40rpx 0;
  border-radius: 20rpx;
  text-align: center;
  background-color: rgba(0, 0, 0, 0.3);
  font-family: Helvetica;
}
.shortcut-item {
  padding: 3px;
  line-height: 1;
  color: rgba(255, 255, 255, 0.5);
  font-size: 24rpx;
}
.active {
  color: #ffcd32;
}

3、处理逻辑部分(数据请提前在data中定义好)


1、首先这个组件接👋两个数据
在这里插入图片描述
scrollTop即父组件实时滚动的top值

  onPageScroll(e) {
    this.setData({
      scrollTop: e.scrollTop
    })
  }

2、刚进入页面的时候我们需要获取group类中的dom信息,这是为了滚动高亮显示字母做准备

 pageLifetimes: {
    show() {
      const groupRef = wx.createSelectorQuery().in(this)
      groupRef.selectAll('.group').boundingClientRect(rect => {
        const listHeights = rect.map(item => item.top)
        this.setData({
          listHeights,
          groupRef: rect
        })
      }).exec()
    }
  },

3、先处理点击字母跳转到对应位置

methods: {
	onScrollTo({currentTarget: {dataset}}) {
      const {id, index} = dataset
      const query = wx.createSelectorQuery().in(this)
      // 这里的动态id就是我们在wxml中给每个item绑定的id
      query.select(`#${dataset.id}`).boundingClientRect(rect => {
        this.setData({curIndex: index})
        this.triggerEvent('scrollY', {
          scrollTop: rect.top,
          alphaId: id
        })
      }).exec()
    },
}

然后在父组件中接受不了子组件传来的值,并进行跳转。为什么不在子组件中完成这个逻辑,
因为这个列表组件只是为了渲染,任何功能性的都会放在父组件中处理

  // 接受组件传来的滚动值,并滚动到指定位置
  onScrollTo(e) {
    console.log(e)
    const {scrollTop} = e.detail
    wx.pageScrollTo({scrollTop})
  },

4、处理滚动页面时,字母高亮

在模板中,我们是使用index作为标志来显示高亮的(你也可以使用title),现在我们要监听scrollTop的值,计算每一项列表的高度区间;然后用新的scrollTop和高度区间的top、bottom进行一个判断

 observers: {
    data() {
      this.calculate() 
    },
    scrollTop(newVal) {
      const singersVal = this.data.listHeights
      // 在最开始的时候push了一个0,这里就需要-1,相对应
      for (let i = 0; i < singersVal.length - 1; i++) {
        const heightTop = singersVal[i]
        const heightBottom = singersVal[i + 1]
        console.log(heightTop)
        if (newVal >= heightTop && newVal <= heightBottom) {
          this.setData({
            curIndex: i
          })
        }
      }
    }
  },
  methods: {
  	// 计算每一项列表的高度区间
    calculate() {
      const {listHeights, groupRef} = this.data
      // 区间高度
      let height = 0
      // 每次计算前都要先清空,从0开始
      listHeights.length = 0
      listHeights.push(height)
      for (let i = 0; i < groupRef.length; i++) {
        height += groupRef[i].height
        listHeights.push(height)
      }
      this.setData({listHeights})
    }
  }

完整代码

// components/SingerList/SingerList.js
Component({
  properties: {
    data: {
      type: Array,
      value: []
    },
    scrollTop: {
      type: Number,
      value: 0
    }
  },
  data: {
    defaultId: '热',
    curIndex: 0,
    listHeights: [],
    groupRef: []
  },
  pageLifetimes: {
    show() {
      const groupRef = wx.createSelectorQuery().in(this)
      groupRef.selectAll('.group').boundingClientRect(rect => {
        const listHeights = rect.map(item => item.top)
        this.setData({
          listHeights,
          groupRef: rect
        })
      }).exec()
    }
  },
  observers: {
    data() {
      this.calculate() 
    },
    scrollTop(newVal) {
      const singersVal = this.data.listHeights
      // 在最开始的时候push了一个0,这里就需要-1,相对应
      for (let i = 0; i < singersVal.length - 1; i++) {
        const heightTop = singersVal[i]
        const heightBottom = singersVal[i + 1]
        console.log(heightTop)
        if (newVal >= heightTop && newVal <= heightBottom) {
          this.setData({
            curIndex: i
          })
        }
      }
    }
  },
  methods: {
    onScrollTo({currentTarget: {dataset}}) {
      const {id, index} = dataset
      const query = wx.createSelectorQuery().in(this)
      query.select(`#${dataset.id}`).boundingClientRect(rect => {
        this.setData({curIndex: index})
        this.triggerEvent('scrollY', {
          scrollTop: rect.top,
          alphaId: id
        })
      }).exec()
    },
    // 计算每一项列表的高度区间
    calculate() {
      const {listHeights, groupRef} = this.data
      // 区间高度
      let height = 0
      // 每次计算前都要先清空,从0开始
      listHeights.length = 0
      listHeights.push(height)
      for (let i = 0; i < groupRef.length; i++) {
        height += groupRef[i].height
        listHeights.push(height)
      }
      this.setData({listHeights})
    }
  }
})

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值