Browse Source

群聊优化,群人员限制放宽至1万人

master
xsx 1 year ago
parent
commit
c421a5dbdd
  1. 8
      im-platform/src/main/java/com/bx/implatform/contant/Constant.java
  2. 5
      im-platform/src/main/java/com/bx/implatform/service/impl/GroupMessageServiceImpl.java
  3. 33
      im-uniapp/components/chat-at-box/chat-at-box.vue
  4. 24
      im-uniapp/components/chat-group-readed/chat-group-readed.vue
  5. 34
      im-uniapp/components/group-member-selector/group-member-selector.vue
  6. 56
      im-uniapp/components/virtual-scroller/virtual-scroller.vue
  7. 5
      im-uniapp/pages/chat/chat-box.vue
  8. 13
      im-uniapp/pages/chat/chat.vue
  9. 20
      im-uniapp/pages/common/external-link.vue
  10. 35
      im-uniapp/pages/group/group-member.vue
  11. 1
      im-uniapp/store/chatStore.js
  12. 4
      im-web/src/components/chat/ChatAtBox.vue
  13. 14
      im-web/src/components/chat/ChatBox.vue
  14. 32
      im-web/src/components/chat/ChatGroupReaded.vue
  15. 34
      im-web/src/components/chat/ChatGroupSide.vue
  16. 74
      im-web/src/components/common/VirtualScroller.vue
  17. 27
      im-web/src/components/group/GroupMemberSelector.vue
  18. 32
      im-web/src/view/Group.vue

8
im-platform/src/main/java/com/bx/implatform/contant/Constant.java

@ -14,9 +14,15 @@ public final class Constant {
* 最大上传文件大小
*/
public static final Long MAX_FILE_SIZE = 20 * 1024 * 1024L;
/**
* 群聊最大人数
*/
public static final Long MAX_GROUP_MEMBER = 500L;
public static final Long MAX_GROUP_MEMBER = 10000L;
/**
* 回执消息限制最大人数
*/
public static final Long LARGE_GROUP_MEMBER = 500L;
}

5
im-platform/src/main/java/com/bx/implatform/service/impl/GroupMessageServiceImpl.java

@ -14,6 +14,7 @@ import com.bx.imcommon.enums.IMTerminalType;
import com.bx.imcommon.model.IMGroupMessage;
import com.bx.imcommon.model.IMUserInfo;
import com.bx.imcommon.util.CommaTextUtils;
import com.bx.implatform.contant.Constant;
import com.bx.implatform.contant.RedisKey;
import com.bx.implatform.dto.GroupMessageDTO;
import com.bx.implatform.entity.Group;
@ -64,6 +65,10 @@ public class GroupMessageServiceImpl extends ServiceImpl<GroupMessageMapper, Gro
}
// 群聊成员列表
List<Long> userIds = groupMemberService.findUserIdsByGroupId(group.getId());
if (dto.getReceipt() && userIds.size() > Constant.LARGE_GROUP_MEMBER) {
// 大群的回执消息过于消耗资源,不允许发送
throw new GlobalException(String.format("当前群聊大于%s人,不支持发送回执消息", Constant.LARGE_GROUP_MEMBER));
}
// 不用发给自己
userIds = userIds.stream().filter(id -> !session.getUserId().equals(id)).collect(Collectors.toList());
// 保存消息

33
im-uniapp/components/chat-at-box/chat-at-box.vue

@ -9,7 +9,7 @@
</view>
<scroll-view v-show="atUserIds.length > 0" scroll-x="true" scroll-left="120">
<view class="at-user-items">
<view v-for="m in showMembers" v-show="m.checked" class="at-user-item" :key="m.userId">
<view v-for="m in checkedMembers" class="at-user-item" :key="m.userId">
<head-image :name="m.showNickName" :url="m.headImage" size="mini"></head-image>
</view>
</view>
@ -18,18 +18,15 @@
<uni-search-bar v-model="searchText" cancelButton="none" radius="100" placeholder="搜索"></uni-search-bar>
</view>
<view class="member-items">
<scroll-view class="scroll-bar" scroll-with-animation="true" scroll-y="true">
<view v-for="m in showMembers" v-show="m.showNickName.includes(searchText)" :key="m.userId">
<view class="member-item" :class="{ checked: m.checked }" @click="onSwitchChecked(m)">
<head-image :name="m.showNickName" :online="m.online" :url="m.headImage"
<virtual-scroller class="scroll-bar" :items="memberItems">
<template v-slot="{ item }">
<view class="member-item" :class="{ checked: item.checked }" @click="onSwitchChecked(item)">
<head-image :name="item.showNickName" :online="item.online" :url="item.headImage"
size="small"></head-image>
<view class="member-name">{{ m.showNickName }}</view>
<!-- <view class="member-checked">-->
<!-- <radio :checked="m.checked" @click.stop="onSwitchChecked(m)" />-->
<!-- </view>-->
<view class="member-name">{{ item.showNickName }}</view>
</view>
</view>
</scroll-view>
</template>
</virtual-scroller>
</view>
</view>
</uni-popup>
@ -91,13 +88,13 @@ export default {
},
computed: {
atUserIds() {
let ids = [];
this.showMembers.forEach((m) => {
if (m.checked) {
ids.push(m.userId);
}
})
return ids;
return this.showMembers.filter(m => m.checked).map(m => m.userId);
},
checkedMembers() {
return this.showMembers.filter(m => m.checked);
},
memberItems() {
return this.showMembers.filter(m => m.showNickName.includes(this.searchText));
}
}
}

24
im-uniapp/components/chat-group-readed/chat-group-readed.vue

@ -7,26 +7,26 @@
</view>
<view class="content">
<view v-if="current === 0">
<scroll-view class="scroll-bar" scroll-with-animation="true" scroll-y="true">
<view v-for="m in readedMembers" :key="m.userId">
<virtual-scroller class="scroll-bar" :items="readedMembers">
<template v-slot="{ item }">
<view class="member-item">
<head-image :name="m.aliasName" :online="m.online" :url="m.headImage"
<head-image :name="item.showNickName" :online="item.online" :url="item.headImage"
:size="90"></head-image>
<view class="member-name">{{ m.aliasName }}</view>
<view class="member-name">{{ item.showNickName }}</view>
</view>
</view>
</scroll-view>
</template>
</virtual-scroller>
</view>
<view v-if="current === 1">
<scroll-view class="scroll-bar" scroll-with-animation="true" scroll-y="true">
<view v-for="m in unreadMembers" :key="m.userId">
<virtual-scroller class="scroll-bar" :items="unreadMembers">
<template v-slot="{ item }">
<view class="member-item">
<head-image :name="m.aliasName" :online="m.online" :url="m.headImage"
<head-image :name="item.showNickName" :online="item.online" :url="item.headImage"
:size="90"></head-image>
<view class="member-name">{{ m.aliasName }}</view>
</view>
<view class="member-name">{{ item.showNickName }}</view>
</view>
</scroll-view>
</template>
</virtual-scroller>
</view>
</view>
</view>

34
im-uniapp/components/group-member-selector/group-member-selector.vue

@ -9,7 +9,7 @@
</view>
<scroll-view v-show="checkedIds.length > 0" scroll-x="true" scroll-left="120">
<view class="checked-users">
<view v-for="m in members" v-show="m.checked" class="user-item" :key="m.userId">
<view v-for="m in checkedMembers" class="user-item" :key="m.userId">
<head-image :name="m.showNickName" :url="m.headImage" :size="60"></head-image>
</view>
</view>
@ -18,18 +18,20 @@
<uni-search-bar v-model="searchText" cancelButton="none" placeholder="搜索"></uni-search-bar>
</view>
<view class="member-items">
<scroll-view class="scroll-bar" scroll-with-animation="true" scroll-y="true">
<view v-for="m in members" v-show="!m.quit && m.showNickName.includes(searchText)" :key="m.userId">
<view class="member-item" @click="onSwitchChecked(m)">
<head-image :name="m.showNickName" :online="m.online" :url="m.headImage"
<virtual-scroller class="scroll-bar" :items="showMembers">
<template v-slot="{ item }">
<view class="member-item" @click="onSwitchChecked(item)">
<head-image :name="item.showNickName" :online="item.online" :url="item.headImage"
:size="90"></head-image>
<view class="member-name">{{ m.showNickName }}</view>
<view class="member-checked">
<radio :checked="m.checked" :disabled="m.locked" @click.stop="onSwitchChecked(m)" />
<view class="member-name">{{ item.showNickName }}
</view>
<view class="member-checked">
<radio :checked="item.checked" :disabled="item.locked"
@click.stop="onSwitchChecked(item)" />
</view>
</view>
</scroll-view>
</template>
</virtual-scroller>
</view>
</view>
</uni-popup>
@ -93,13 +95,13 @@ export default {
},
computed: {
checkedIds() {
let ids = [];
this.members.forEach((m) => {
if (m.checked) {
ids.push(m.userId);
}
})
return ids;
return this.members.filter((m) => m.checked).map(m => m.userId)
},
checkedMembers() {
return this.members.filter((m) => m.checked);
},
showMembers() {
return this.members.filter(m => !m.quit && m.showNickName.includes(this.searchText))
}
}
}

56
im-uniapp/components/virtual-scroller/virtual-scroller.vue

@ -0,0 +1,56 @@
<template>
<scroll-view scroll-y="true" upper-threshold="200" @scrolltolower="onScrollToBottom" scroll-with-animation="true">
<view v-for="(item, idx) in showItems" :key="idx">
<slot :item="item">
</slot>
</view>
</scroll-view>
</template>
<script>
export default {
name: "virtual-scroller",
data() {
return {
page: 1,
isInitEvent: false,
lockTip: false
}
},
props: {
items: {
type: Array
},
size: {
type: Number,
default: 30
}
},
methods: {
onScrollToBottom(e) {
console.log("onScrollToBottom")
if (this.showMaxIdx >= this.items.length) {
this.showTip();
} else {
this.page++;
}
},
showTip() {
uni.showToast({
title: "已滚动至底部",
icon: 'none'
});
}
},
computed: {
showMaxIdx() {
return Math.min(this.page * this.size, this.items.length);
},
showItems() {
return this.items.slice(0, this.showMaxIdx);
}
}
}
</script>
<style scoped></style>

5
im-uniapp/pages/chat/chat-box.vue

@ -71,7 +71,7 @@
<view class="tool-icon iconfont icon-microphone"></view>
<view class="tool-name">语音消息</view>
</view>
<view v-if="chat.type == 'GROUP'" class="chat-tools-item" @click="switchReceipt()">
<view v-if="chat.type == 'GROUP' && memberSize<=500" class="chat-tools-item" @click="switchReceipt()">
<view class="tool-icon iconfont icon-receipt" :class="isReceipt ? 'active' : ''"></view>
<view class="tool-name">回执消息</view>
</view>
@ -886,6 +886,9 @@ export default {
}
})
return atUsers;
},
memberSize() {
return this.groupMembers.filter(m => !m.quit).length;
}
},
watch: {

13
im-uniapp/pages/chat/chat.vue

@ -31,7 +31,6 @@
</template>
<script>
export default {
data() {
return {
@ -77,12 +76,6 @@ export default {
moveToTop(chatIdx) {
this.chatStore.moveTop(chatIdx);
},
isShowChat(chat) {
if (chat.delete) {
return false;
}
return !this.searchText || chat.showName.includes(this.searchText)
},
onSearch() {
this.showSearch = !this.showSearch;
this.searchText = "";
@ -114,8 +107,12 @@ export default {
loading() {
return this.chatStore.isLoading();
},
initializing(){
initializing() {
return !getApp().$vm.isInit;
},
showChats() {
this.chatStore.chats.filter((chat) => !chat.delete && chat.showName && chat.showName.includes(this
.searchText))
}
},
watch: {

20
im-uniapp/pages/common/external-link.vue

@ -0,0 +1,20 @@
<template>
<view>
<web-view :src="linkUrl"></web-view>
</view>
</template>
<script>
export default {
data() {
return {
linkUrl: ''
};
},
onLoad(options) {
this.linkUrl = decodeURIComponent(options.url);
}
}
</script>
<style></style>

35
im-uniapp/pages/group/group-member.vue

@ -8,24 +8,22 @@
</view>
</view>
<view class="member-items">
<scroll-view class="scroll-bar" scroll-with-animation="true" scroll-y="true">
<view v-for="(member, idx) in groupMembers"
v-show="!searchText || member.showNickName.includes(searchText)" :key="idx">
<view class="member-item" @click="onShowUserInfo(member.userId)">
<head-image :name="member.showNickName" :online="member.online"
:url="member.headImage"></head-image>
<view class="member-name">{{ member.showNickName }}
<uni-tag v-if="member.userId == group.ownerId" text="群主" size="small" circle type="error">
<virtual-scroller class="scroll-bar" :items="showMembers">
<template v-slot="{ item }">
<view class="member-item" @click="onShowUserInfo(item.userId)">
<head-image :name="item.showNickName" :online="item.online" :url="item.headImage"></head-image>
<view class="member-name">{{ item.showNickName }}
<uni-tag v-if="item.userId == group.ownerId" text="群主" size="small" circle type="error">
</uni-tag>
<uni-tag v-if="member.userId == userStore.userInfo.id" text="我" size="small" circle></uni-tag>
<uni-tag v-if="item.userId == userStore.userInfo.id" text="我" size="small" circle></uni-tag>
</view>
<view class="member-kick">
<button type="warn" plain v-show="isOwner && !isSelf(member.userId)" size="mini"
@click.stop="onKickOut(member, idx)">移出群聊</button>
<button type="warn" plain v-show="isOwner && !isSelf(item.userId)" size="mini"
@click.stop="onKickOut(item)">移出群聊</button>
</view>
</view>
</view>
</scroll-view>
</template>
</virtual-scroller>
</view>
</view>
</template>
@ -37,7 +35,7 @@ export default {
isModify: false,
searchText: "",
group: {},
groupMembers: []
members: []
}
},
methods: {
@ -46,7 +44,7 @@ export default {
url: "/pages/common/user-info?id=" + userId
})
},
onKickOut(member, idx) {
onKickOut(member) {
uni.showModal({
title: '确认移出?',
content: `确定将成员'${member.showNickName}'移出群聊吗?`,
@ -61,7 +59,7 @@ export default {
title: `已将${member.showNickName}移出群聊`,
icon: 'none'
})
this.groupMembers.splice(idx, 1);
member.quit = true;
this.isModify = true;
});
}
@ -80,7 +78,7 @@ export default {
url: `/group/members/${id}`,
method: "GET"
}).then((members) => {
this.groupMembers = members.filter(m => !m.quit);
this.members = members;
})
},
isSelf(userId) {
@ -90,6 +88,9 @@ export default {
computed: {
isOwner() {
return this.userStore.userInfo.id == this.group.ownerId;
},
showMembers() {
return this.members.filter(m => !m.quit && m.showNickName.includes(this.searchText))
}
},
onLoad(options) {

1
im-uniapp/store/chatStore.js

@ -1,6 +1,7 @@
import { defineStore } from 'pinia';
import { MESSAGE_TYPE, MESSAGE_STATUS } from '@/common/enums.js';
import useUserStore from './userStore';
import UNI_APP from '../.env';
let cacheChats = [];
export default defineStore('chatStore', {

4
im-web/src/components/chat/ChatAtBox.vue

@ -51,6 +51,10 @@ export default {
})
}
this.members.forEach((m) => {
// 100
if (this.showMembers.length > 100) {
return;
}
if (m.userId != userId && !m.quit && m.showNickName.startsWith(this.searchText)) {
this.showMembers.push(m);
}

14
im-web/src/components/chat/ChatBox.vue

@ -41,8 +41,9 @@
<i class="el-icon-wallet"></i>
</file-upload>
</div>
<div title="回执消息" v-show="chat.type == 'GROUP'" class="icon iconfont icon-receipt"
:class="isReceipt ? 'chat-tool-active' : ''" @click="onSwitchReceipt">
<div title="回执消息" v-show="chat.type == 'GROUP' && memberSize <= 500"
class="icon iconfont icon-receipt" :class="isReceipt ? 'chat-tool-active' : ''"
@click="onSwitchReceipt">
</div>
<div title="发送语音" class="el-icon-microphone" @click="showRecordBox()">
</div>
@ -67,7 +68,7 @@
</div>
</el-footer>
</el-container>
<el-aside class="chat-group-side-box" width="260px" v-if="showSide">
<el-aside class="chat-group-side-box" width="320px" v-if="showSide">
<chat-group-side :group="group" :groupMembers="groupMembers" @reload="loadGroup(group.id)">
</chat-group-side>
</el-aside>
@ -510,7 +511,7 @@ export default {
this.$http({
url: url,
method: 'put'
}).then(() => {})
}).then(() => { })
},
loadReaded(fId) {
this.$http({
@ -549,7 +550,7 @@ export default {
friend.showNickName = friend.remarkNickName ? friend.remarkNickName : friend.nickName;
this.$store.commit("updateChatFromFriend", friend);
this.$store.commit("updateFriend", friend);
}else {
} else {
this.$store.commit("updateChatFromUser", this.userInfo);
}
},
@ -681,6 +682,9 @@ export default {
isBanned() {
return (this.chat.type == "PRIVATE" && this.userInfo.isBanned) ||
(this.chat.type == "GROUP" && this.group.isBanned)
},
memberSize() {
return this.groupMembers.filter(m => !m.quit).length;
}
},
watch: {

32
im-web/src/components/chat/ChatGroupReaded.vue

@ -4,18 +4,18 @@
<div class="chat-group-readed" :style="{ 'left': pos.x + 'px', 'top': pos.y + 'px' }" @click.prevent="">
<el-tabs type="border-card" :stretch="true">
<el-tab-pane :label="`已读(${readedMembers.length})`">
<el-scrollbar class="scroll-box">
<div v-for="(member) in readedMembers" :key="member.id">
<chat-group-member :member="member"></chat-group-member>
</div>
</el-scrollbar>
<virtual-scroller class="scroll-box" :items="readedMembers">
<template v-slot="{ item }">
<chat-group-member :member="item"></chat-group-member>
</template>
</virtual-scroller>
</el-tab-pane>
<el-tab-pane :label="`未读(${unreadMembers.length})`">
<el-scrollbar class="scroll-box">
<div v-for="(member) in unreadMembers" :key="member.id">
<chat-group-member :member="member"></chat-group-member>
</div>
</el-scrollbar>
<virtual-scroller class="scroll-box" :items="unreadMembers">
<template v-slot="{ item }">
<chat-group-member :member="item"></chat-group-member>
</template>
</virtual-scroller>
</el-tab-pane>
</el-tabs>
<div v-show="msgInfo.selfSend" class="arrow-right" :style="{ 'top': pos.arrowY + 'px' }">
@ -34,12 +34,13 @@
<script>
import VirtualScroller from '../common/VirtualScroller.vue';
import ChatGroupMember from "./ChatGroupMember.vue";
export default {
name: "chatGroupReaded",
components: {
ChatGroupMember
ChatGroupMember, VirtualScroller
},
data() {
return {
@ -105,11 +106,16 @@ export default {
}
})
//
this.$store.commit("updateMessage", {
let msgInfo = {
id: this.msgInfo.id,
groupId: this.msgInfo.groupId,
readedCount: this.readedMembers.length
})
}
let chatInfo = {
type: 'GROUP',
targetId: this.msgInfo.groupId
}
this.$store.commit("updateMessage", [msgInfo, chatInfo])
})
}
}

34
im-web/src/components/chat/ChatGroupSide.vue

@ -6,7 +6,8 @@
</el-input>
</div>
<div class="group-side-scrollbar">
<div v-show="!group.quit" class="group-side-member-list">
<el-scrollbar v-show="!group.quit" ref="scrollbar" :style="'height: ' + scrollHeight + 'px'">
<div class="group-side-member-list">
<div class="group-side-invite">
<div class="invite-member-btn" title="邀请好友进群聊" @click="showAddGroupMember = true">
<i class="el-icon-plus"></i>
@ -15,11 +16,12 @@
<add-group-member :visible="showAddGroupMember" :groupId="group.id" :members="groupMembers"
@reload="$emit('reload')" @close="showAddGroupMember = false"></add-group-member>
</div>
<div v-for="(member) in groupMembers" :key="member.id">
<group-member class="group-side-member" v-show="!member.quit && member.showNickName.includes(searchText)"
:member="member" :showDel="false"></group-member>
<div v-for="(member, idx) in showMembers" :key="member.id">
<group-member v-if="idx < showMaxIdx" class="group-side-member" :member="member"
:showDel="false"></group-member>
</div>
</div>
</el-scrollbar>
<el-divider v-if="!group.quit" content-position="center"></el-divider>
<el-form labelPosition="top" class="group-side-form" :model="group" size="small">
<el-form-item label="群聊名称">
@ -62,7 +64,8 @@ export default {
return {
searchText: "",
editing: false,
showAddGroupMember: false
showAddGroupMember: false,
showMaxIdx: 50
}
},
props: {
@ -113,7 +116,15 @@ export default {
});
})
},
onScroll(e) {
const scrollbar = e.target;
//
if (scrollbar.scrollTop + scrollbar.clientHeight >= scrollbar.scrollHeight - 30) {
if (this.showMaxIdx < this.showMembers.length) {
this.showMaxIdx += 30;
}
}
}
},
computed: {
ownerName() {
@ -122,8 +133,17 @@ export default {
},
isOwner() {
return this.group.ownerId == this.$store.state.userStore.userInfo.id;
},
showMembers() {
return this.groupMembers.filter((m) => !m.quit && m.showNickName.includes(this.searchText))
},
scrollHeight() {
return Math.min(400, 80 + this.showMembers.length / 5 * 80);
}
},
mounted() {
let scrollWrap = this.$refs.scrollbar.$el.querySelector('.el-scrollbar__wrap');
scrollWrap.addEventListener('scroll', this.onScroll);
}
}
</script>

74
im-web/src/components/common/VirtualScroller.vue

@ -0,0 +1,74 @@
<template>
<el-scrollbar ref="scrollbar">
<div v-for="(item, idx) in items" :key="idx">
<slot :item="item" v-if=" idx < showMaxIdx">
</slot>
</div>
</el-scrollbar>
</template>
<script>
export default {
name: "virtualScroller",
data() {
return {
page: 1,
isInitEvent: false,
lockTip: false
}
},
props: {
items: {
type: Array
},
size: {
type: Number,
default: 30
}
},
methods: {
init() {
this.page = 1;
this.initEvent();
},
initEvent() {
if (!this.isInitEvent) {
let scrollWrap = this.$refs.scrollbar.$el.querySelector('.el-scrollbar__wrap');
scrollWrap.addEventListener('scroll', this.onScroll);
this.isInitEvent = true;
}
},
onScroll(e) {
const scrollbar = e.target;
//
if (scrollbar.scrollTop + scrollbar.clientHeight >= scrollbar.scrollHeight - 30) {
if(this.showMaxIdx >= this.items.length ){
this.showTip();
}else{
this.page++;
}
}
},
showTip(){
// 3
if(!this.lockTip){
this.$message.success("已到滚动到底部")
this.lockTip = true;
setTimeout(()=>{
this.lockTip = false;
},3000)
}
}
},
computed: {
showMaxIdx() {
return Math.min(this.page * this.size, this.items.length);
}
},
mounted(){
this.initEvent();
}
}
</script>
<style scoped></style>

27
im-web/src/components/group/GroupMemberSelector.vue

@ -5,15 +5,14 @@
<el-input placeholder="搜索" v-model="searchText">
<i class="el-icon-search el-input__icon" slot="suffix"> </i>
</el-input>
<el-scrollbar style="height:400px;">
<div v-for="m in members" :key="m.userId">
<group-member-item v-show="!m.quit && m.showNickName.includes(searchText)" :member="m"
@click.native="onClickMember(m)">
<el-checkbox :disabled="m.locked" v-model="m.checked" @change="onChange(m)"
<virtual-scroller class="scroll-box" :items="showMembers">
<template v-slot="{ item }">
<group-member-item :member="item" @click.native="onClickMember(item)">
<el-checkbox :disabled="item.locked" v-model="item.checked" @change="onChange(item)"
@click.native.stop=""></el-checkbox>
</group-member-item>
</div>
</el-scrollbar>
</template>
</virtual-scroller>
</div>
<div class="arrow el-icon-d-arrow-right"></div>
<div class="right-box">
@ -33,6 +32,7 @@
</template>
<script>
import VirtualScroller from '../common/VirtualScroller.vue';
import GroupMemberItem from './GroupMemberItem.vue';
import GroupMember from './GroupMember.vue';
@ -40,7 +40,8 @@ export default {
name: "addGroupMember",
components: {
GroupMemberItem,
GroupMember
GroupMember,
VirtualScroller
},
data() {
return {
@ -106,6 +107,9 @@ export default {
}
})
return ids;
},
showMembers() {
return this.members.filter((m) => !m.hide && !m.quit && m.showNickName.includes(this.searchText))
}
}
@ -116,11 +120,18 @@ export default {
.group-member-selector {
display: flex;
.left-box {
width: 48%;
overflow: hidden;
border: var(--im-border);
.scroll-box {
height: 400px;
}
.el-input__inner {
border: none;
border-bottom: var(--im-border);

32
im-web/src/view/Group.vue

@ -67,11 +67,8 @@
</el-form>
</div>
<el-divider content-position="center"></el-divider>
<el-scrollbar ref="scrollbar" :style="'height: ' + scrollHeight + 'px'">
<div class="group-member-list">
<div v-for="(member) in groupMembers" :key="member.id">
<group-member v-show="!member.quit" class="group-member" :member="member"
:showDel="isOwner && member.userId != activeGroup.ownerId" @del="onKick"></group-member>
</div>
<div class="group-invite">
<div class="invite-member-btn" title="邀请好友进群聊" @click="onInviteMember()">
<i class="el-icon-plus"></i>
@ -80,7 +77,12 @@
<add-group-member :visible="showAddGroupMember" :groupId="activeGroup.id" :members="groupMembers"
@reload="loadGroupMembers" @close="onCloseAddGroupMember"></add-group-member>
</div>
<div v-for="(member, idx) in showMembers" :key="member.id">
<group-member v-if="idx < showMaxIdx" class="group-member" :member="member"
:showDel="isOwner && member.userId != activeGroup.ownerId" @del="onKick"></group-member>
</div>
</div>
</el-scrollbar>
</div>
</div>
</el-container>
@ -112,6 +114,7 @@ export default {
activeGroup: {},
groupMembers: [],
showAddGroupMember: false,
showMaxIdx: 150,
rules: {
name: [{
required: true,
@ -143,6 +146,7 @@ export default {
})
},
onActiveItem(group) {
this.showMaxIdx = 150;
// store
this.activeGroup = JSON.parse(JSON.stringify(group));
//
@ -188,7 +192,6 @@ export default {
this.reset();
});
})
},
onKick(member) {
this.$confirm(`确定将成员'${member.showNickName}'移出群聊吗?`, '确认移出?', {
@ -237,6 +240,15 @@ export default {
this.$store.commit("activeChat", 0);
this.$router.push("/home/chat");
},
onScroll(e) {
const scrollbar = e.target;
//
if (scrollbar.scrollTop + scrollbar.clientHeight >= scrollbar.scrollHeight - 30) {
if (this.showMaxIdx < this.showMembers.length) {
this.showMaxIdx += 50;
}
}
},
loadGroupMembers() {
this.$http({
url: `/group/members/${this.activeGroup.id}`,
@ -311,7 +323,17 @@ export default {
},
groupValues() {
return Array.from(this.groupMap.values());
},
showMembers() {
return this.groupMembers.filter((m) => !m.quit)
},
scrollHeight() {
return Math.min(300, 80 + this.showMembers.length / 10 * 80);
}
},
mounted() {
let scrollWrap = this.$refs.scrollbar.$el.querySelector('.el-scrollbar__wrap');
scrollWrap.addEventListener('scroll', this.onScroll);
}
}
</script>

Loading…
Cancel
Save