效果图
代码实现
<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%;
}
}
}
}