【uniapp】仿微信通讯录列表实现

效果图

在这里插入图片描述

代码实现

<template>
  <view class="group-member-page">
    <view :style="{ height: statusBarHeight + 'px' }"></view>
    <view class="nav-container">
      <view class="nav-back" @click="goBack">
        <image class="nav-back-img" src="https://img.yiqitogether.com/static/local/index/back@2x.png" mode="" />
      </view>
      <!-- 搜索 -->
      <view class="nav-search">
        <image class="search-icon" src="@/static/images/myImgs/sousuo@2x.png" mode="" />
        <input class="search-input" v-model.trim="inputValue" type="text" placeholder="搜索成员" placeholder-style="color: #ADB3BA" @input="onSearch" />
        <image class="del-icon" v-show="inputValue" src="@/static/images/myImgs/del@2x.png" mode="" @click="onClear" />
      </view>
    </view>
    <view class="main-container">
      <!-- 成员列表 -->
      <view class="list-wrap" v-if="(initiatorInfo.displayName || initiatorInfo.nickName) && showInitiatorInfo">
        <view class="category">发起人</view>
        <view class="list-item">
          <image class="list-item-img" :src="initiatorInfo.headUrl" />
          <view class="list-item-name">{{ initiatorInfo.displayName || initiatorInfo.nickName }}</view>
        </view>
      </view>
      <scroll-view v-if="memberList.length && finished" class="member-list" :style="computedHeight" :scroll-y="true" :enable-back-to-top="true" :scroll-with-animation="true" :scroll-into-view="toView" :scroll-top="scrollTop" @scroll="onScroll">
        <view class="list-wrap last-wrap" v-for="(v, i) in memberList" :key="i" :id="v.sign == '#' ? 'intoView_' : 'intoView' + v.sign">
          <view class="category">{{ v.sign }} ({{ v.list.length }}人)</view>
          <view class="list-item" v-for="(item, index) in v.list" :key="index">
            <image class="list-item-img" :src="item.headUrl" />
            <view class="list-item-name">{{ item.displayName || item.nickName }}</view>
            <view class="list-item-btn" @click="handleRemove(item)" v-if="initiatorInfo.numberId && initiatorInfo.numberId == numberId">将TA移出</view>
          </view>
        </view>
      </scroll-view>
      <view v-if="!memberList.length && finished && !showInitiatorInfo" class="empty-wrap">
        <image class="empty-img" src="@/static/images/qs_wujilu.png" alt="" mode="aspectFill" />
      </view>
      <!-- 右侧字母栏 -->
      <view class="letter-list">
        <view :class="['letter-item', activeLetter == '↑' ? 'active' : '']" @click.stop="toTop" @touchend.stop="handleTouchEnd"><view v-if="activeLetter == '↑'" class="active-letter"></view>
        </view>
        <!-- <view :class="['letter-item', activeLetter == '☆' ? 'active-item' : '']" @click="toStar">☆</view> -->
        <view :class="['letter-item', activeLetter == item ? 'active' : '']" v-for="(item, index) in allLetterList" :key="index" @click.stop="toLetter(item)" @touchend.stop="handleTouchEnd">
          {{ item }}
          <view v-if="activeLetter == item" class="active-letter">{{ item }}</view>
        </view>
      </view>
    </view>
    
    <!-- loading 弹窗 -->
    <!-- 省略…… -->
  </view>
</template>

需要下载js-pinyin包

npm install js-pinyin --save

引入js-pinyin包

import pinyin from 'js-pinyin'
data() {
    return {
        statusBarHeight: 0,
        showInitiatorInfo: true,
        initiatorInfo: {}, // 发起人
        numberId: '', // 当前登录人Id
        groupNo: '',
        allLetterList: ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '#'],
        memberList: [],
        scrollTop: 0,
        oldScrollTop: 0, // 解决滑动不流畅问题
        toView: '',
        activeLetter: ''
    }
},
computed: {
  computedHeight() {
    let reduceHeight = 98
    if (this.showInitiatorInfo) {
      reduceHeight = 250
    } else {
      reduceHeight = 98
    }
    return { height: `calc(100vh - ${this.statusBarHeight}px - ${reduceHeight}rpx )` }
  }
},
onLoad(options) {
  //获取手机系统信息
  const info = uni.getSystemInfoSync()
  //设置状态栏高度
  this.statusBarHeight = info.statusBarHeight

  this.groupNo = options.groupNo || ''
  this.numberId = uni.getStorageSync('numberId')
},
onShow() {
  this.scrollTop = 0
  this.getList()
},
methods: {
    // 获取群组成员
    getList() {
      this.finished = false
      this.memberList = []
      this.showInitiatorInfo = true
      rongYun
        .getGroupUserList({
          groupNo: this.groupNo
        })
        .then(res => {
          if (res.code == 'SUCCESS') {
            let data = res.data.userList || []
            if (this.inputValue) {
              data = data.filter(item => {
                if (item.displayName) {
                  return item.displayName.indexOf(this.inputValue) != -1 || String(item.numberId).indexOf(this.inputValue) != -1
                } else {
                  return item.nickName.indexOf(this.inputValue) != -1 || String(item.numberId).indexOf(this.inputValue) != -1
                }
              })
              let newArr = data.filter(item => item.userType == 'System') || []
              this.showInitiatorInfo = newArr.length ? true : false
            }
            this.initiatorInfo = res.data.userList.filter(item => item.userType == 'System')[0] || {}
            let userList = data.filter(item => item.userType == 'Nomal')
            let arr = []
            this.allLetterList.forEach((item, index) => {
              arr.push({
                sign: item,
                list: []
              })
            })
            this.allLetterList.forEach((item, index) => {
              userList.forEach(item2 => {
                let firstLetter = pinyin
                  .getCamelChars(item2.displayName || item2.nickName)
                  ?.slice(0, 1)
                  .toLocaleUpperCase()
                if (firstLetter == item) {
                  arr[index].list.push(item2)
                }
                if (this.allLetterList.indexOf(firstLetter) == -1 && item == '#') {
                  arr[arr.length - 1].list.push(item2)
                }
              })
            })
            this.memberList = arr && arr.length ? arr.filter(item => item.list.length > 0) : []
            this.finished = true
          } else {
            this.finished = true
            // 弹出报错提示......
          }
        })
        .catch(err => {
          this.finished = true
        })
    },
    // 滚动,不添加该方法置顶不生效
    onScroll(e) {
        // 直接设置scrollTop滑动时会卡顿
        this.oldScrollTop = e.detail.scrollTop
    },
    // 滚动到顶部
    toTop() {
        this.activeLetter = '↑'
        this.scrollTop = this.oldScrollTop 
        this.$nextTick(() => {
            this.scrollTop = 0
        })
    },
    // 滚动到星标好友
    toStar() {},
    // 滚动到某个字母位置
    toLetter(e) {
        this.activeLetter = e
        if (e == '#') {
            this.toView = 'intoView_'
        } else {
            this.toView = 'intoView' + e
        }
    },
    handleTouchEnd() {
        setTimeout(() => {
            this.activeLetter = ''
        }, 600)
    }
}
.group-member-page {
  background: #ffffff;
  height: 100vh;
  overflow: hidden;

  // navbar
  .nav-container {
    width: 100%;
    height: 88rpx;
    position: relative;
    display: flex;
    align-items: center;
    z-index: 999;
    background-color: #fff;
    padding: 10rpx 40rpx 10rpx 24rpx;
    box-sizing: border-box;

    .nav-back {
      width: 44rpx;
      height: 44rpx;
      flex-shrink: 0;
      padding-right: 12rpx;

      .nav-back-img {
        width: 100%;
        height: 100%;
      }
    }
    .nav-search {
      flex: 1;
      height: 68rpx;
      background: #f6f7f8;
      border-radius: 34rpx;
      display: flex;
      align-items: center;
      padding: 0 24rpx;

      .search-icon {
        width: 26rpx;
        height: 26rpx;
        flex-shrink: 0;
        margin-right: 20rpx;
      }
      .search-input {
        flex: 1;
        font-size: 28rpx;
        line-height: 36rpx;
        height: 36rpx;
        color: #2a343e;
      }
      .del-icon {
        flex-shrink: 0;
        width: 32rpx;
        height: 32rpx;
        padding-left: 16rpx;
      }
    }
  }
  .main-container {
    width: 100%;
    height: 100%;
    padding: 10rpx 50rpx 0 24rpx;
    box-sizing: border-box;
    overflow-y: auto;
    position: relative;

    .empty-wrap {
      padding: 80rpx 0;
      text-align: center;
      font-size: 0;
      .empty-img {
        width: 310rpx;
        height: 310rpx;
        background-size: cover;
      }
    }

    .member-list {
      box-sizing: border-box;
    }
    .list-wrap {
      padding-top: 30rpx;
      .category {
        font-size: 24rpx;
        font-family: OPPOSans, OPPOSans-Medium;
        font-weight: 500;
        color: #adb3ba;
        line-height: 32rpx;
        margin-bottom: 20rpx;
      }
      .list-item {
        display: flex;
        align-items: center;
        margin-bottom: 40rpx;

        &-img {
          width: 70rpx;
          height: 70rpx;
          background: #d8d8d8;
          flex-shrink: 0;
          border-radius: 50%;
        }
        &-name {
          flex: 1;
          font-size: 28rpx;
          font-family: OPPOSans, OPPOSans-Medium;
          font-weight: 500;
          color: #2d3f49;
          line-height: 36rpx;
          padding: 20rpx 16rpx 14rpx;
        }
        &-btn {
          width: 144rpx;
          font-size: 24rpx;
          font-family: OPPOSans, OPPOSans-Medium;
          font-weight: 500;
          color: #ff466d;
          line-height: 32rpx;
          padding: 14rpx 0;
          flex-shrink: 0;
          background: #ffedf1;
          border-radius: 36rpx;
          text-align: center;
          box-sizing: border-box;
        }
      }
      .list-item:last-child {
        margin-bottom: 0;
      }
    }
    .last-wrap:last-of-type {
      padding-bottom: 30rpx;
      padding-bottom: calc(constant(safe-area-inset-bottom) + 30rpx);
      padding-bottom: calc(env(safe-area-inset-bottom) + 30rpx);
    }

    .letter-list {
      width: 32rpx;
      text-align: center;
      position: absolute;
      top: 50%;
      right: 6rpx;
      transform: translateY(-50%);
      padding-bottom: 40rpx;
      z-index: 10;

      .letter-item {
        width: 32rpx;
        height: 32rpx;
        display: flex;
        align-items: center;
        justify-content: center;
        font-size: 22rpx;
        font-family: OPPOSans, OPPOSans-Medium;
        font-weight: 500;
        color: #999999;
        line-height: 32rpx;
        position: relative;

        // 字母放大效果
        .active-letter {
          position: absolute;
          top: -31rpx;
          left: -304rpx;
          width: 94rpx;
          height: 94rpx;
          display: flex;
          align-items: center;
          justify-content: center;
          background: #cccccc;
          border-radius: 50%;
          z-index: 999;
          font-size: 50rpx;
          color: #ffffff;
        }
      }
      .active {
        width: 32rpx;
        height: 32rpx;
        background: #fb5c4e;
        color: #fff;
        border-radius: 50%;
      }
    }
  }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Komorebi゛

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值