Browse Source

版本升级:v_3.5.0

master
xsx 11 months ago
parent
commit
101552e058
  1. 18
      im-platform/src/main/java/com/bx/implatform/controller/GroupController.java
  2. 6
      im-platform/src/main/java/com/bx/implatform/dto/GroupInviteDTO.java
  3. 28
      im-platform/src/main/java/com/bx/implatform/dto/GroupMemberRemoveDTO.java
  4. 9
      im-platform/src/main/java/com/bx/implatform/service/GroupMemberService.java
  5. 13
      im-platform/src/main/java/com/bx/implatform/service/GroupService.java
  6. 30
      im-platform/src/main/java/com/bx/implatform/service/impl/GroupMemberServiceImpl.java
  7. 60
      im-platform/src/main/java/com/bx/implatform/service/impl/GroupServiceImpl.java
  8. 1
      im-uniapp/App.vue
  9. 7
      im-uniapp/common/recorder-h5.js
  10. 30
      im-uniapp/components/group-member-selector/group-member-selector.vue
  11. 1
      im-uniapp/components/image-upload/image-upload.vue
  12. 1
      im-uniapp/components/virtual-scroller/virtual-scroller.vue
  13. 5
      im-uniapp/pages/chat/chat-box.vue
  14. 1
      im-uniapp/pages/chat/chat-group-video.vue
  15. 70
      im-uniapp/pages/group/group-info.vue
  16. 34
      im-uniapp/pages/group/group-member.vue
  17. 1
      im-uniapp/pages/mine/mine.vue
  18. 1
      im-uniapp/pages/register/register.vue
  19. 42
      im-uniapp/static/icon/iconfont.css
  20. BIN
      im-uniapp/static/icon/iconfont.ttf
  21. 1
      im-uniapp/store/userStore.js
  22. 1
      im-web/src/api/camera.js
  23. 38
      im-web/src/assets/iconfont/iconfont.css
  24. BIN
      im-web/src/assets/iconfont/iconfont.ttf
  25. 127
      im-web/src/components/chat/ChatGroupSide.vue
  26. 4
      im-web/src/components/chat/ChatInput.vue
  27. 34
      im-web/src/components/chat/ChatItem.vue
  28. 18
      im-web/src/components/chat/ChatMessageItem.vue
  29. 14
      im-web/src/components/common/HeadImage.vue
  30. 28
      im-web/src/components/common/RightMenu.vue
  31. 101
      im-web/src/components/common/UserInfo.vue
  32. 36
      im-web/src/components/friend/FriendItem.vue
  33. 60
      im-web/src/components/group/AddGroupMember.vue
  34. 8
      im-web/src/components/group/GroupMember.vue
  35. 47
      im-web/src/components/group/GroupMemberSelector.vue
  36. 1
      im-web/src/components/rtc/RtcPrivateVideo.vue
  37. 1
      im-web/src/view/Friend.vue
  38. 75
      im-web/src/view/Group.vue
  39. 4
      im-web/src/view/Home.vue

18
im-platform/src/main/java/com/bx/implatform/controller/GroupController.java

@ -1,10 +1,11 @@
package com.bx.implatform.controller; package com.bx.implatform.controller;
import com.bx.implatform.annotation.RepeatSubmit; import com.bx.implatform.annotation.RepeatSubmit;
import com.bx.implatform.dto.GroupMemberRemoveDTO;
import com.bx.implatform.result.Result; import com.bx.implatform.result.Result;
import com.bx.implatform.result.ResultUtils; import com.bx.implatform.result.ResultUtils;
import com.bx.implatform.service.GroupService; import com.bx.implatform.service.GroupService;
import com.bx.implatform.vo.GroupInviteVO; import com.bx.implatform.dto.GroupInviteDTO;
import com.bx.implatform.vo.GroupMemberVO; import com.bx.implatform.vo.GroupMemberVO;
import com.bx.implatform.vo.GroupVO; import com.bx.implatform.vo.GroupVO;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
@ -62,8 +63,8 @@ public class GroupController {
@RepeatSubmit @RepeatSubmit
@Operation(summary = "邀请进群", description = "邀请好友进群") @Operation(summary = "邀请进群", description = "邀请好友进群")
@PostMapping("/invite") @PostMapping("/invite")
public Result invite(@Valid @RequestBody GroupInviteVO vo) { public Result invite(@Valid @RequestBody GroupInviteDTO dto) {
groupService.invite(vo); groupService.invite(dto);
return ResultUtils.success(); return ResultUtils.success();
} }
@ -74,6 +75,15 @@ public class GroupController {
return ResultUtils.success(groupService.findGroupMembers(groupId)); return ResultUtils.success(groupService.findGroupMembers(groupId));
} }
@RepeatSubmit
@Operation(summary = "将成员移出群聊", description = "将成员移出群聊")
@DeleteMapping("/members/remove")
public Result removeMembers(@Valid @RequestBody GroupMemberRemoveDTO dto) {
groupService.removeGroupMembers(dto);
return ResultUtils.success();
}
@RepeatSubmit @RepeatSubmit
@Operation(summary = "退出群聊", description = "退出群聊") @Operation(summary = "退出群聊", description = "退出群聊")
@DeleteMapping("/quit/{groupId}") @DeleteMapping("/quit/{groupId}")
@ -83,7 +93,7 @@ public class GroupController {
} }
@RepeatSubmit @RepeatSubmit
@Operation(summary = "踢出群聊", description = "将用户踢出群聊") @Operation(summary = "踢出群聊(已废弃)", description = "将用户踢出群聊")
@DeleteMapping("/kick/{groupId}") @DeleteMapping("/kick/{groupId}")
public Result kickGroup(@NotNull(message = "群聊id不能为空") @PathVariable Long groupId, public Result kickGroup(@NotNull(message = "群聊id不能为空") @PathVariable Long groupId,
@NotNull(message = "用户id不能为空") @RequestParam Long userId) { @NotNull(message = "用户id不能为空") @RequestParam Long userId) {

6
im-platform/src/main/java/com/bx/implatform/vo/GroupInviteVO.java → im-platform/src/main/java/com/bx/implatform/dto/GroupInviteDTO.java

@ -1,4 +1,4 @@
package com.bx.implatform.vo; package com.bx.implatform.dto;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotEmpty;
@ -9,8 +9,8 @@ import lombok.Data;
import java.util.List; import java.util.List;
@Data @Data
@Schema(description = "邀请好友进群请求VO") @Schema(description = "邀请好友进群请求DTO")
public class GroupInviteVO { public class GroupInviteDTO {
@NotNull(message = "群id不可为空") @NotNull(message = "群id不可为空")
@Schema(description = "群id") @Schema(description = "群id")

28
im-platform/src/main/java/com/bx/implatform/dto/GroupMemberRemoveDTO.java

@ -0,0 +1,28 @@
package com.bx.implatform.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.util.List;
/**
* @author Blue
* @version 1.0
* @date 2025-02-23
*/
@Data
@Schema(description = "移除群聊成员")
public class GroupMemberRemoveDTO {
@NotNull(message = "群id不可为空")
@Schema(description = "群组id")
private Long groupId;
@Size(max = 50, message = "一次最多只能选择50位用户")
@NotEmpty(message = "成员用户id不可为空")
@Schema(description = "成员用户id")
private List<Long> userIds;
}

9
im-platform/src/main/java/com/bx/implatform/service/GroupMemberService.java

@ -16,6 +16,7 @@ public interface GroupMemberService extends IService<GroupMember> {
*/ */
GroupMember findByGroupAndUserId(Long groupId, Long userId); GroupMember findByGroupAndUserId(Long groupId, Long userId);
/** /**
* 根据用户id查询群聊成员 * 根据用户id查询群聊成员
* *
@ -74,6 +75,14 @@ public interface GroupMemberService extends IService<GroupMember> {
*/ */
void removeByGroupAndUserId(Long groupId, Long userId); void removeByGroupAndUserId(Long groupId, Long userId);
/**
* 根据群聊id和用户id移除成员
*
* @param groupId 群聊id
* @param userIds 用户id
*/
void removeByGroupAndUserIds(Long groupId, List<Long> userIds);
/** /**
* 用户用户是否在群中 * 用户用户是否在群中
* *

13
im-platform/src/main/java/com/bx/implatform/service/GroupService.java

@ -1,8 +1,9 @@
package com.bx.implatform.service; package com.bx.implatform.service;
import com.baomidou.mybatisplus.extension.service.IService; import com.baomidou.mybatisplus.extension.service.IService;
import com.bx.implatform.dto.GroupMemberRemoveDTO;
import com.bx.implatform.entity.Group; import com.bx.implatform.entity.Group;
import com.bx.implatform.vo.GroupInviteVO; import com.bx.implatform.dto.GroupInviteDTO;
import com.bx.implatform.vo.GroupMemberVO; import com.bx.implatform.vo.GroupMemberVO;
import com.bx.implatform.vo.GroupVO; import com.bx.implatform.vo.GroupVO;
@ -48,6 +49,12 @@ public interface GroupService extends IService<Group> {
*/ */
void kickGroup(Long groupId, Long userId); void kickGroup(Long groupId, Long userId);
/**
* 将用户移出群聊
* @param dto dto
*/
void removeGroupMembers(GroupMemberRemoveDTO dto);
/** /**
* 查询当前用户的所有群聊 * 查询当前用户的所有群聊
* *
@ -58,9 +65,9 @@ public interface GroupService extends IService<Group> {
/** /**
* 邀请好友进群 * 邀请好友进群
* *
* @param vo 群id好友id列表 * @param dto 群id好友id列表
**/ **/
void invite(GroupInviteVO vo); void invite(GroupInviteDTO dto);
/** /**
* 根据id查找群聊并进行缓存 * 根据id查找群聊并进行缓存

30
im-platform/src/main/java/com/bx/implatform/service/impl/GroupMemberServiceImpl.java

@ -37,11 +37,13 @@ public class GroupMemberServiceImpl extends ServiceImpl<GroupMemberMapper, Group
@Override @Override
public GroupMember findByGroupAndUserId(Long groupId, Long userId) { public GroupMember findByGroupAndUserId(Long groupId, Long userId) {
QueryWrapper<GroupMember> wrapper = new QueryWrapper<>(); LambdaQueryWrapper<GroupMember> wrapper = Wrappers.lambdaQuery();
wrapper.lambda().eq(GroupMember::getGroupId, groupId).eq(GroupMember::getUserId, userId); wrapper.eq(GroupMember::getGroupId, groupId);
wrapper.eq(GroupMember::getUserId, userId);
return this.getOne(wrapper); return this.getOne(wrapper);
} }
@Override @Override
public List<GroupMember> findByUserId(Long userId) { public List<GroupMember> findByUserId(Long userId) {
LambdaQueryWrapper<GroupMember> memberWrapper = Wrappers.lambdaQuery(); LambdaQueryWrapper<GroupMember> memberWrapper = Wrappers.lambdaQuery();
@ -88,19 +90,35 @@ public class GroupMemberServiceImpl extends ServiceImpl<GroupMemberMapper, Group
@Override @Override
public void removeByGroupAndUserId(Long groupId, Long userId) { public void removeByGroupAndUserId(Long groupId, Long userId) {
LambdaUpdateWrapper<GroupMember> wrapper = Wrappers.lambdaUpdate(); LambdaUpdateWrapper<GroupMember> wrapper = Wrappers.lambdaUpdate();
wrapper.eq(GroupMember::getGroupId, groupId).eq(GroupMember::getUserId, userId).set(GroupMember::getQuit, true) wrapper.eq(GroupMember::getGroupId, groupId);
.set(GroupMember::getQuitTime, new Date()); wrapper.eq(GroupMember::getUserId, userId);
wrapper.set(GroupMember::getQuit, true);
wrapper.set(GroupMember::getQuitTime, new Date());
this.update(wrapper); this.update(wrapper);
} }
@CacheEvict(key = "#groupId")
@Override
public void removeByGroupAndUserIds(Long groupId, List<Long> userId) {
LambdaUpdateWrapper<GroupMember> wrapper = Wrappers.lambdaUpdate();
wrapper.eq(GroupMember::getGroupId, groupId);
wrapper.in(GroupMember::getUserId, userId);
wrapper.set(GroupMember::getQuit, true);
wrapper.set(GroupMember::getQuitTime, new Date());
this.update(wrapper);
}
@Override @Override
public Boolean isInGroup(Long groupId, List<Long> userIds) { public Boolean isInGroup(Long groupId, List<Long> userIds) {
if (CollectionUtils.isEmpty(userIds)) { if (CollectionUtils.isEmpty(userIds)) {
return true; return true;
} }
LambdaQueryWrapper<GroupMember> wrapper = Wrappers.lambdaQuery(); LambdaQueryWrapper<GroupMember> wrapper = Wrappers.lambdaQuery();
wrapper.eq(GroupMember::getGroupId, groupId).eq(GroupMember::getQuit, false) wrapper.eq(GroupMember::getGroupId, groupId);
.in(GroupMember::getUserId, userIds); wrapper.eq(GroupMember::getQuit, false);
wrapper.in(GroupMember::getUserId, userIds);
return userIds.size() == this.count(wrapper); return userIds.size() == this.count(wrapper);
} }
} }

60
im-platform/src/main/java/com/bx/implatform/service/impl/GroupServiceImpl.java

@ -12,6 +12,7 @@ import com.bx.imcommon.model.IMUserInfo;
import com.bx.imcommon.util.CommaTextUtils; import com.bx.imcommon.util.CommaTextUtils;
import com.bx.implatform.contant.Constant; import com.bx.implatform.contant.Constant;
import com.bx.implatform.contant.RedisKey; import com.bx.implatform.contant.RedisKey;
import com.bx.implatform.dto.GroupMemberRemoveDTO;
import com.bx.implatform.entity.*; import com.bx.implatform.entity.*;
import com.bx.implatform.enums.MessageStatus; import com.bx.implatform.enums.MessageStatus;
import com.bx.implatform.enums.MessageType; import com.bx.implatform.enums.MessageType;
@ -25,7 +26,7 @@ import com.bx.implatform.service.UserService;
import com.bx.implatform.session.SessionContext; import com.bx.implatform.session.SessionContext;
import com.bx.implatform.session.UserSession; import com.bx.implatform.session.UserSession;
import com.bx.implatform.util.BeanUtils; import com.bx.implatform.util.BeanUtils;
import com.bx.implatform.vo.GroupInviteVO; import com.bx.implatform.dto.GroupInviteDTO;
import com.bx.implatform.vo.GroupMemberVO; import com.bx.implatform.vo.GroupMemberVO;
import com.bx.implatform.vo.GroupMessageVO; import com.bx.implatform.vo.GroupMessageVO;
import com.bx.implatform.vo.GroupVO; import com.bx.implatform.vo.GroupVO;
@ -126,7 +127,7 @@ public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements
String content = String.format("'%s'解散了群聊", session.getNickName()); String content = String.format("'%s'解散了群聊", session.getNickName());
this.sendTipMessage(groupId, userIds, content, true); this.sendTipMessage(groupId, userIds, content, true);
// 推送同步消息 // 推送同步消息
this.sendDelGroupMessage(groupId, userIds, false); this.sendDelGroupMessage(groupId, userIds);
log.info("删除群聊,群聊id:{},群聊名称:{}", group.getId(), group.getName()); log.info("删除群聊,群聊id:{},群聊名称:{}", group.getId(), group.getName());
} }
@ -145,7 +146,7 @@ public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements
// 推送退出群聊提示 // 推送退出群聊提示
this.sendTipMessage(groupId, List.of(userId), "您已退出群聊", false); this.sendTipMessage(groupId, List.of(userId), "您已退出群聊", false);
// 推送同步消息 // 推送同步消息
this.sendDelGroupMessage(groupId, Lists.newArrayList(), true); this.sendDelGroupMessage(groupId, List.of(userId));
log.info("退出群聊,群聊id:{},群聊名称:{},用户id:{}", group.getId(), group.getName(), userId); log.info("退出群聊,群聊id:{},群聊名称:{},用户id:{}", group.getId(), group.getName(), userId);
} }
@ -167,10 +168,35 @@ public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements
// 推送踢出群聊提示 // 推送踢出群聊提示
this.sendTipMessage(groupId, List.of(userId), "您已被移出群聊", false); this.sendTipMessage(groupId, List.of(userId), "您已被移出群聊", false);
// 推送同步消息 // 推送同步消息
this.sendDelGroupMessage(groupId, List.of(userId), false); this.sendDelGroupMessage(groupId, List.of(userId));
log.info("踢出群聊,群聊id:{},群聊名称:{},用户id:{}", group.getId(), group.getName(), userId); log.info("踢出群聊,群聊id:{},群聊名称:{},用户id:{}", group.getId(), group.getName(), userId);
} }
@Override
public void removeGroupMembers(GroupMemberRemoveDTO dto) {
UserSession session = SessionContext.getSession();
Group group = this.getAndCheckById(dto.getGroupId());
if (!group.getOwnerId().equals(session.getUserId())) {
throw new GlobalException("您没有权限");
}
if (dto.getUserIds().contains(group.getOwnerId())) {
throw new GlobalException("不允许移除群主");
}
if (dto.getUserIds().contains(session.getUserId())) {
throw new GlobalException("不允许移除自己");
}
// 删除群聊成员
groupMemberService.removeByGroupAndUserIds(dto.getGroupId(), dto.getUserIds());
// 清理已读缓存
String key = StrUtil.join(":", RedisKey.IM_GROUP_READED_POSITION, dto.getGroupId());
dto.getUserIds().forEach(id -> redisTemplate.opsForHash().delete(key, id.toString()));
// 推送踢出群聊提示
this.sendTipMessage(dto.getGroupId(), dto.getUserIds(), "您已被移出群聊", false);
// 推送同步消息
this.sendDelGroupMessage(dto.getGroupId(), dto.getUserIds());
log.info("踢出群聊,群聊id:{},群聊名称:{},用户id:{}", group.getId(), group.getName(), dto.getUserIds());
}
@Override @Override
public GroupVO findById(Long groupId) { public GroupVO findById(Long groupId) {
UserSession session = SessionContext.getSession(); UserSession session = SessionContext.getSession();
@ -225,22 +251,22 @@ public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements
} }
@Override @Override
public void invite(GroupInviteVO vo) { public void invite(GroupInviteDTO dto) {
UserSession session = SessionContext.getSession(); UserSession session = SessionContext.getSession();
Group group = this.getAndCheckById(vo.getGroupId()); Group group = this.getAndCheckById(dto.getGroupId());
GroupMember member = groupMemberService.findByGroupAndUserId(vo.getGroupId(), session.getUserId()); GroupMember member = groupMemberService.findByGroupAndUserId(dto.getGroupId(), session.getUserId());
if (Objects.isNull(group) || member.getQuit()) { if (Objects.isNull(group) || member.getQuit()) {
throw new GlobalException("您不在群聊中,邀请失败"); throw new GlobalException("您不在群聊中,邀请失败");
} }
// 群聊人数校验 // 群聊人数校验
List<GroupMember> members = groupMemberService.findByGroupId(vo.getGroupId()); List<GroupMember> members = groupMemberService.findByGroupId(dto.getGroupId());
long size = members.stream().filter(m -> !m.getQuit()).count(); long size = members.stream().filter(m -> !m.getQuit()).count();
if (vo.getFriendIds().size() + size > Constant.MAX_LARGE_GROUP_MEMBER) { if (dto.getFriendIds().size() + size > Constant.MAX_LARGE_GROUP_MEMBER) {
throw new GlobalException("群聊人数不能大于" + Constant.MAX_LARGE_GROUP_MEMBER + "人"); throw new GlobalException("群聊人数不能大于" + Constant.MAX_LARGE_GROUP_MEMBER + "人");
} }
// 找出好友信息 // 找出好友信息
List<Friend> friends = friendsService.findByFriendIds(vo.getFriendIds()); List<Friend> friends = friendsService.findByFriendIds(dto.getFriendIds());
if (vo.getFriendIds().size() != friends.size()) { if (dto.getFriendIds().size() != friends.size()) {
throw new GlobalException("部分用户不是您的好友,邀请失败"); throw new GlobalException("部分用户不是您的好友,邀请失败");
} }
// 批量保存成员数据 // 批量保存成员数据
@ -248,7 +274,7 @@ public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements
Optional<GroupMember> optional = Optional<GroupMember> optional =
members.stream().filter(m -> m.getUserId().equals(f.getFriendId())).findFirst(); members.stream().filter(m -> m.getUserId().equals(f.getFriendId())).findFirst();
GroupMember groupMember = optional.orElseGet(GroupMember::new); GroupMember groupMember = optional.orElseGet(GroupMember::new);
groupMember.setGroupId(vo.getGroupId()); groupMember.setGroupId(dto.getGroupId());
groupMember.setUserId(f.getFriendId()); groupMember.setUserId(f.getFriendId());
groupMember.setUserNickName(f.getFriendNickName()); groupMember.setUserNickName(f.getFriendNickName());
groupMember.setHeadImage(f.getFriendHeadImage()); groupMember.setHeadImage(f.getFriendHeadImage());
@ -265,12 +291,12 @@ public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements
sendAddGroupMessage(groupVo, List.of(m.getUserId()), false); sendAddGroupMessage(groupVo, List.of(m.getUserId()), false);
} }
// 推送进入群聊消息 // 推送进入群聊消息
List<Long> userIds = groupMemberService.findUserIdsByGroupId(vo.getGroupId()); List<Long> userIds = groupMemberService.findUserIdsByGroupId(dto.getGroupId());
String memberNames = groupMembers.stream().map(GroupMember::getShowNickName).collect(Collectors.joining(",")); String memberNames = groupMembers.stream().map(GroupMember::getShowNickName).collect(Collectors.joining(","));
String content = String.format("'%s'邀请'%s'加入了群聊", session.getNickName(), memberNames); String content = String.format("'%s'邀请'%s'加入了群聊", session.getNickName(), memberNames);
this.sendTipMessage(vo.getGroupId(), userIds, content, true); this.sendTipMessage(dto.getGroupId(), userIds, content, true);
log.info("邀请进入群聊,群聊id:{},群聊名称:{},被邀请用户id:{}", group.getId(), group.getName(), log.info("邀请进入群聊,群聊id:{},群聊名称:{},被邀请用户id:{}", group.getId(), group.getName(),
vo.getFriendIds()); dto.getFriendIds());
} }
@Override @Override
@ -345,7 +371,7 @@ public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements
imClient.sendGroupMessage(sendMessage); imClient.sendGroupMessage(sendMessage);
} }
private void sendDelGroupMessage(Long groupId, List<Long> recvIds, Boolean sendToSelf) { private void sendDelGroupMessage(Long groupId, List<Long> recvIds) {
UserSession session = SessionContext.getSession(); UserSession session = SessionContext.getSession();
GroupMessageVO msgInfo = new GroupMessageVO(); GroupMessageVO msgInfo = new GroupMessageVO();
msgInfo.setType(MessageType.GROUP_DEL.code()); msgInfo.setType(MessageType.GROUP_DEL.code());
@ -357,7 +383,7 @@ public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements
sendMessage.setRecvIds(recvIds); sendMessage.setRecvIds(recvIds);
sendMessage.setData(msgInfo); sendMessage.setData(msgInfo);
sendMessage.setSendResult(false); sendMessage.setSendResult(false);
sendMessage.setSendToSelf(sendToSelf); sendMessage.setSendToSelf(false);
imClient.sendGroupMessage(sendMessage); imClient.sendGroupMessage(sendMessage);
} }
} }

1
im-uniapp/App.vue

@ -407,7 +407,6 @@ export default {
// #ifdef APP-PLUS // #ifdef APP-PLUS
// //
setTimeout(() => { setTimeout(() => {
console.log("plus.navigator.closeSplashscreen()")
plus.navigator.closeSplashscreen() plus.navigator.closeSplashscreen()
}, delay) }, delay)
// #endif // #endif

7
im-uniapp/common/recorder-h5.js

@ -26,7 +26,6 @@ let checkIsEnable = () => {
let start = () => { let start = () => {
return navigator.mediaDevices.getUserMedia({ audio: true }).then(audioStream => { return navigator.mediaDevices.getUserMedia({ audio: true }).then(audioStream => {
console.log("start record")
startTime = new Date().getTime(); startTime = new Date().getTime();
chunks = []; chunks = [];
stream = audioStream; stream = audioStream;
@ -36,7 +35,6 @@ let start = () => {
} }
let close = () => { let close = () => {
console.log("stream:", stream)
stream.getTracks().forEach((track) => { stream.getTracks().forEach((track) => {
track.stop() track.stop()
}) })
@ -47,9 +45,6 @@ let close = () => {
let upload = () => { let upload = () => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
rc.ondataavailable = (e) => { rc.ondataavailable = (e) => {
console.log("ondataavailable:",e.data)
console.log("size:",e.data.size)
console.log("type:",e.data.type)
chunks.push(e.data) chunks.push(e.data)
} }
rc.onstop = () => { rc.onstop = () => {
@ -58,8 +53,6 @@ let upload = () => {
return; return;
} }
duration = (new Date().getTime() - startTime) / 1000; duration = (new Date().getTime() - startTime) / 1000;
console.log("时长:", duration)
console.log("上传,chunks:", chunks.length)
const newbolb = new Blob(chunks, { 'type': 'audio/mpeg' }); const newbolb = new Blob(chunks, { 'type': 'audio/mpeg' });
const name = new Date().getDate() + '.mp3'; const name = new Date().getDate() + '.mp3';
const file = new File([newbolb], name) const file = new File([newbolb], name)

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

@ -42,12 +42,15 @@
export default { export default {
name: "chat-group-member-choose", name: "chat-group-member-choose",
props: { props: {
group: {
type: Object
},
members: { members: {
type: Array type: Array
}, },
maxSize: { maxSize: {
type: Number, type: Number,
default: -1 default: 50
} }
}, },
data() { data() {
@ -56,10 +59,11 @@ export default {
}; };
}, },
methods: { methods: {
init(checkedIds, lockedIds) { init(checkedIds, lockedIds, hideIds) {
this.members.forEach((m) => { this.members.forEach((m) => {
m.checked = checkedIds.indexOf(m.userId) >= 0; m.checked = checkedIds.indexOf(m.userId) >= 0;
m.locked = lockedIds.indexOf(m.userId) >= 0; m.locked = lockedIds.indexOf(m.userId) >= 0;
m.hide = hideIds.indexOf(m.userId) >= 0;
}); });
}, },
open() { open() {
@ -80,7 +84,7 @@ export default {
}, },
onClean() { onClean() {
this.members.forEach((m) => { this.members.forEach((m) => {
if (!m.locked) { if (!m.locked && m.checked) {
m.checked = false; m.checked = false;
} }
}) })
@ -88,20 +92,17 @@ export default {
onOk() { onOk() {
this.$refs.popup.close(); this.$refs.popup.close();
this.$emit("complete", this.checkedIds) this.$emit("complete", this.checkedIds)
},
isChecked(m) {
return this.checkedIds.indexOf(m.userId) >= 0;
} }
}, },
computed: { computed: {
checkedIds() { checkedIds() {
return this.members.filter((m) => m.checked).map(m => m.userId) return this.checkedMembers.map(m => m.userId)
}, },
checkedMembers() { checkedMembers() {
return this.members.filter((m) => m.checked); return this.members.filter((m) => !m.quit && !m.hide && m.checked);
}, },
showMembers() { showMembers() {
return this.members.filter(m => !m.quit && m.showNickName.includes(this.searchText)) return this.members.filter(m => !m.quit && !m.hide && m.showNickName.includes(this.searchText))
} }
} }
} }
@ -115,7 +116,8 @@ export default {
flex-direction: column; flex-direction: column;
background-color: white; background-color: white;
padding: 10rpx; padding: 10rpx;
border-radius: 15rpx; border-radius: 15rpx 15rpx 0 0;
overflow: hidden;
.top-bar { .top-bar {
display: flex; display: flex;
@ -158,6 +160,8 @@ export default {
white-space: nowrap; white-space: nowrap;
.member-name { .member-name {
display: flex;
align-items: center;
flex: 1; flex: 1;
padding-left: 20rpx; padding-left: 20rpx;
font-size: 30rpx; font-size: 30rpx;
@ -165,11 +169,15 @@ export default {
line-height: 60rpx; line-height: 60rpx;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
.uni-tag {
margin-left: 5rpx;
}
} }
} }
.scroll-bar { .scroll-bar {
height: 800rpx; height: 65vh;
} }
} }
} }

1
im-uniapp/components/image-upload/image-upload.vue

@ -54,7 +54,6 @@ export default {
sizeType: ['original'], //original compressed sizeType: ['original'], //original compressed
success: (res) => { success: (res) => {
res.tempFiles.forEach((file) => { res.tempFiles.forEach((file) => {
console.log("文件:", file)
if (!this.onBefore || this.onBefore(file)) { if (!this.onBefore || this.onBefore(file)) {
// //
this.uploadImage(file); this.uploadImage(file);

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

@ -28,7 +28,6 @@ export default {
}, },
methods: { methods: {
onScrollToBottom(e) { onScrollToBottom(e) {
console.log("onScrollToBottom")
if (this.showMaxIdx >= this.items.length) { if (this.showMaxIdx >= this.items.length) {
this.showTip(); this.showTip();
} else { } else {

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

@ -733,7 +733,6 @@ export default {
h -= 50; h -= 50;
// //
if (this.isShowKeyBoard || this.chatTabBox != 'none') { if (this.isShowKeyBoard || this.chatTabBox != 'none') {
console.log("减去键盘高度:", this.keyboardHeight)
h -= this.keyboardHeight; h -= this.keyboardHeight;
this.scrollToBottom(); this.scrollToBottom();
} }
@ -742,7 +741,6 @@ export default {
h -= uni.getSystemInfoSync().statusBarHeight; h -= uni.getSystemInfoSync().statusBarHeight;
// #endif // #endif
this.chatMainHeight = h; this.chatMainHeight = h;
console.log("窗口高度:", this.chatMainHeight)
if (this.isShowKeyBoard || this.chatTabBox != 'none') { if (this.isShowKeyBoard || this.chatTabBox != 'none') {
this.scrollToBottom(); this.scrollToBottom();
} }
@ -806,7 +804,6 @@ export default {
this.reCalChatMainHeight(); this.reCalChatMainHeight();
}, },
resizeListener() { resizeListener() {
console.log("resize")
let keyboardHeight = this.initHeight - window.innerHeight; let keyboardHeight = this.initHeight - window.innerHeight;
this.isShowKeyBoard = keyboardHeight > 150; this.isShowKeyBoard = keyboardHeight > 150;
if (this.isShowKeyBoard) { if (this.isShowKeyBoard) {
@ -815,12 +812,10 @@ export default {
this.reCalChatMainHeight(); this.reCalChatMainHeight();
}, },
focusInListener() { focusInListener() {
console.log("focusInListener")
this.isShowKeyBoard = true; this.isShowKeyBoard = true;
this.reCalChatMainHeight(); this.reCalChatMainHeight();
}, },
focusOutListener() { focusOutListener() {
console.log("focusOutListener")
this.isShowKeyBoard = false; this.isShowKeyBoard = false;
this.reCalChatMainHeight(); this.reCalChatMainHeight();
}, },

1
im-uniapp/pages/chat/chat-group-video.vue

@ -76,7 +76,6 @@ export default {
}, },
}, },
onBackPress() { onBackPress() {
console.log("onBackPress")
this.sendMessageToWebView("NAV_BACK", {}) this.sendMessageToWebView("NAV_BACK", {})
}, },
onLoad(options) { onLoad(options) {

70
im-uniapp/pages/group/group-info.vue

@ -4,7 +4,7 @@
<view v-if="!group.quit" class="group-members"> <view v-if="!group.quit" class="group-members">
<view class="member-items"> <view class="member-items">
<view v-for="(member, idx) in groupMembers" :key="idx"> <view v-for="(member, idx) in groupMembers" :key="idx">
<view class="member-item" v-if="idx < 9"> <view class="member-item" v-if="idx < showMaxIdx">
<head-image :id="member.userId" :name="member.showNickName" :url="member.headImage" size="small" <head-image :id="member.userId" :name="member.showNickName" :url="member.headImage" size="small"
:online="member.online"></head-image> :online="member.online"></head-image>
<view class="member-name"> <view class="member-name">
@ -12,8 +12,17 @@
</view> </view>
</view> </view>
</view> </view>
<view class="invite-btn" @click="onInviteMember()"> <view class="member-item" @click="onInviteMember()">
<uni-icons type="plusempty" size="20" color="#888888"></uni-icons> <view class="tools-btn">
<uni-icons class="icon" type="plusempty" color="#888888"></uni-icons>
</view>
<view class="member-name">邀请</view>
</view>
<view v-if="isOwner" class="member-item" @click="onRemoveMember()">
<view class="tools-btn">
<text class="icon iconfont icon-remove"></text>
</view>
<view class="member-name">移除</view>
</view> </view>
</view> </view>
<view class="member-more" @click="onShowMoreMmeber()">{{ `查看全部群成员${groupMembers.length}` }}></view> <view class="member-more" @click="onShowMoreMmeber()">{{ `查看全部群成员${groupMembers.length}` }}></view>
@ -41,15 +50,15 @@
<view v-if="group.notice" class="form-item"> <view v-if="group.notice" class="form-item">
<uni-notice-bar :text="group.notice" /> <uni-notice-bar :text="group.notice" />
</view> </view>
<view v-if="!group.quit" class="group-edit" @click="onEditGroup()">修改群聊资料 > </view> <view v-if="!group.quit" class="group-edit" @click="onEditGroup()">修改群聊资料 > </view>
</view> </view>
<bar-group v-if="!group.quit"> <bar-group v-if="!group.quit">
<btn-bar type="primary" title="发送消息" @tap="onSendMessage()"></btn-bar> <btn-bar type="primary" title="发送消息" @tap="onSendMessage()"></btn-bar>
<btn-bar v-if="!isOwner" type="danger" title="退出群聊" @tap="onQuitGroup()"></btn-bar> <btn-bar v-if="!isOwner" type="danger" title="退出群聊" @tap="onQuitGroup()"></btn-bar>
<btn-bar v-if="isOwner" type="danger" title="解散群聊" @tap="onDissolveGroup()"></btn-bar> <btn-bar v-if="isOwner" type="danger" title="解散群聊" @tap="onDissolveGroup()"></btn-bar>
</bar-group> </bar-group>
<group-member-selector ref="removeSelector" :members="groupMembers" :group="group"
@complete="onRemoveComplete"></group-member-selector>
</view> </view>
</template> </template>
@ -69,6 +78,29 @@ export default {
url: `/pages/group/group-invite?id=${this.groupId}` url: `/pages/group/group-invite?id=${this.groupId}`
}) })
}, },
onRemoveMember() {
//
let hideIds = [this.group.ownerId];
this.$refs.removeSelector.init([], [], hideIds);
this.$refs.removeSelector.open();
},
onRemoveComplete(userIds) {
let data = {
groupId: this.group.id,
userIds: userIds
}
this.$http({
url: "/group/members/remove",
method: 'DELETE',
data: data
}).then(() => {
this.loadGroupMembers();
uni.showToast({
title: `您移除了${userIds.length}位成员`,
icon: 'none'
})
})
},
onShowMoreMmeber() { onShowMoreMmeber() {
uni.navigateTo({ uni.navigateTo({
url: `/pages/group/group-member?id=${this.groupId}` url: `/pages/group/group-member?id=${this.groupId}`
@ -160,7 +192,6 @@ export default {
}); });
}, },
loadGroupMembers() { loadGroupMembers() {
console.log("loadGroupMembers")
this.$http({ this.$http({
url: `/group/members/${this.groupId}`, url: `/group/members/${this.groupId}`,
method: "GET" method: "GET"
@ -176,6 +207,9 @@ export default {
}, },
isOwner() { isOwner() {
return this.group.ownerId == this.userStore.userInfo.id; return this.group.ownerId == this.userStore.userInfo.id;
},
showMaxIdx() {
return this.isOwner ? 8 : 9;
} }
}, },
onLoad(options) { onLoad(options) {
@ -218,17 +252,21 @@ export default {
padding-top: 8rpx; padding-top: 8rpx;
font-size: $im-font-size-smaller; font-size: $im-font-size-smaller;
} }
}
.invite-btn { .tools-btn {
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
width: 86rpx; border: $im-border solid 1rpx;
height: 86rpx; border-radius: 10%;
margin: 10rpx; width: 80rpx;
border: $im-border solid 2rpx; height: 80rpx;
border-radius: 10%;
.icon {
font-size: 40rpx !important;
color: $im-text-color-lighter !important;
}
}
} }
} }

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

@ -13,14 +13,9 @@
<view class="member-item" @click="onShowUserInfo(item.userId)"> <view class="member-item" @click="onShowUserInfo(item.userId)">
<head-image :name="item.showNickName" :online="item.online" :url="item.headImage"></head-image> <head-image :name="item.showNickName" :online="item.online" :url="item.headImage"></head-image>
<view class="member-name">{{ item.showNickName }} <view class="member-name">{{ item.showNickName }}
<uni-tag v-if="item.userId == group.ownerId" text="群主" size="small" circle type="error"> <uni-tag v-if="item.userId == group.ownerId" text="群主" size="small" circle type="error"></uni-tag>
</uni-tag>
<uni-tag v-if="item.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>
<view class="member-kick">
<button type="warn" plain v-show="isOwner && !isSelf(item.userId)" size="mini"
@click.stop="onKickOut(item)">移出群聊</button>
</view>
</view> </view>
</template> </template>
</virtual-scroller> </virtual-scroller>
@ -44,27 +39,6 @@ export default {
url: "/pages/common/user-info?id=" + userId url: "/pages/common/user-info?id=" + userId
}) })
}, },
onKickOut(member) {
uni.showModal({
title: '确认移出?',
content: `确定将成员'${member.showNickName}'移出群聊吗?`,
success: (res) => {
if (res.cancel)
return;
this.$http({
url: `/group/kick/${this.group.id}?userId=${member.userId}`,
method: 'DELETE'
}).then(() => {
uni.showToast({
title: `已将${member.showNickName}移出群聊`,
icon: 'none'
})
member.quit = true;
this.isModify = true;
});
}
})
},
loadGroupInfo(id) { loadGroupInfo(id) {
this.$http({ this.$http({
url: `/group/find/${id}`, url: `/group/find/${id}`,
@ -141,12 +115,6 @@ export default {
.uni-tag { .uni-tag {
margin-left: 5rpx; margin-left: 5rpx;
width: 40rpx;
border: 0;
height: 30rpx;
line-height: 30rpx;
font-size: 20rpx;
text-align: center;
} }
} }
} }

1
im-uniapp/pages/mine/mine.vue

@ -56,7 +56,6 @@ export default {
title: '确认退出?', title: '确认退出?',
success: (res) => { success: (res) => {
if (res.confirm) { if (res.confirm) {
console.log(getApp())
getApp().$vm.exit() getApp().$vm.exit()
} }
} }

1
im-uniapp/pages/register/register.vue

@ -59,7 +59,6 @@ export default {
errorMessage: '请输入确认密码', errorMessage: '请输入确认密码',
}, { }, {
validateFunction: (rule, value, data, callback) => { validateFunction: (rule, value, data, callback) => {
console.log("validateFunction")
if (data.password != value) { if (data.password != value) {
callback('两次密码输入不一致') callback('两次密码输入不一致')
} }

42
im-uniapp/static/icon/iconfont.css

@ -1,6 +1,6 @@
@font-face { @font-face {
font-family: "iconfont"; /* Project id 4272106 */ font-family: "iconfont"; /* Project id 4272106 */
src: url('iconfont.ttf?t=1739084401359') format('truetype'); src: url('iconfont.ttf?t=1746119818070') format('truetype');
} }
.iconfont { .iconfont {
@ -11,6 +11,46 @@
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
.icon-remove:before {
content: "\e603";
}
.icon-doc:before {
content: "\e61c";
}
.icon-image:before {
content: "\e7f7";
}
.icon-top-message:before {
content: "\e6ff";
}
.icon-setting:before {
content: "\e851";
}
.icon-phone:before {
content: "\e692";
}
.icon-email:before {
content: "\e611";
}
.icon-username:before {
content: "\e60f";
}
.icon-chat-muted:before {
content: "\e634";
}
.icon-chat-unmuted:before {
content: "\ec44";
}
.icon-privacy-protocol:before { .icon-privacy-protocol:before {
content: "\e70a"; content: "\e70a";
} }

BIN
im-uniapp/static/icon/iconfont.ttf

Binary file not shown.

1
im-uniapp/store/userStore.js

@ -20,7 +20,6 @@ export default defineStore('userStore', {
url: '/user/self', url: '/user/self',
method: 'GET' method: 'GET'
}).then((userInfo) => { }).then((userInfo) => {
console.log(userInfo)
this.setUserInfo(userInfo); this.setUserInfo(userInfo);
resolve(); resolve();
}).catch((res) => { }).catch((res) => {

1
im-web/src/api/camera.js

@ -21,7 +21,6 @@ ImCamera.prototype.openVideo = function () {
noiseSuppression: true // 开启降噪 noiseSuppression: true // 开启降噪
} }
} }
console.log("getUserMedia")
navigator.mediaDevices.getUserMedia(constraints).then((stream) => { navigator.mediaDevices.getUserMedia(constraints).then((stream) => {
console.log("摄像头打开") console.log("摄像头打开")
this.stream = stream; this.stream = stream;

38
im-web/src/assets/iconfont/iconfont.css

@ -1,6 +1,6 @@
@font-face { @font-face {
font-family: "iconfont"; /* Project id 3791506 */ font-family: "iconfont"; /* Project id 3791506 */
src: url('iconfont.ttf?t=1718373714629') format('truetype'); src: url('iconfont.ttf?t=1745933248800') format('truetype');
} }
.iconfont { .iconfont {
@ -11,6 +11,42 @@
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
.icon-man:before {
content: "\e615";
}
.icon-girl:before {
content: "\e602";
}
.icon-no-data:before {
content: "\e61b";
}
.icon-phone:before {
content: "\e692";
}
.icon-email:before {
content: "\e610";
}
.icon-username:before {
content: "\e60f";
}
.icon-chat-unmuted:before {
content: "\ec44";
}
.icon-chat-muted:before {
content: "\e634";
}
.icon-modify:before {
content: "\e60d";
}
.icon-invite-rtc:before { .icon-invite-rtc:before {
content: "\e65f"; content: "\e65f";
} }

BIN
im-web/src/assets/iconfont/iconfont.ttf

Binary file not shown.

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

@ -5,66 +5,71 @@
<i class="el-icon-search el-input__icon" slot="prefix"> </i> <i class="el-icon-search el-input__icon" slot="prefix"> </i>
</el-input> </el-input>
</div> </div>
<div class="group-side-scrollbar"> <el-scrollbar v-show="!group.quit" ref="scrollbar" :style="'height: ' + scrollHeight + 'px'">
<el-scrollbar v-show="!group.quit" ref="scrollbar" :style="'height: ' + scrollHeight + 'px'"> <div class="member-list">
<div class="group-side-member-list"> <div class="member-tools">
<div class="group-side-invite"> <div class="tool-btn" title="邀请好友进群聊" @click="onInvite()">
<div class="invite-member-btn" title="邀请好友进群聊" @click="showAddGroupMember = true"> <i class="el-icon-plus"></i>
<i class="el-icon-plus"></i>
</div>
<div class="invite-member-text">邀请</div>
<add-group-member :visible="showAddGroupMember" :groupId="group.id" :members="groupMembers"
@reload="$emit('reload')" @close="showAddGroupMember = false"></add-group-member>
</div> </div>
<div v-for="(member, idx) in showMembers" :key="member.id"> <div class="tool-text">邀请</div>
<group-member v-if="idx < showMaxIdx" class="group-side-member" :member="member" <add-group-member ref="addGroupMember" :groupId="group.id" :members="groupMembers"
:showDel="false"></group-member> @reload="$emit('reload')"></add-group-member>
</div>
<div class="member-tools" v-if="isOwner">
<div class="tool-btn" title="选择成员移出群聊" @click="onRemove()">
<i class="el-icon-minus"></i>
</div> </div>
<div class="tool-text">移除</div>
<group-member-selector ref="removeSelector" title="选择成员进行移除" :group="group"
@complete="onRemoveComplete"></group-member-selector>
</div> </div>
</el-scrollbar> <div v-for="(member, idx) in showMembers" :key="member.id">
<el-divider v-if="!group.quit" content-position="center"></el-divider> <group-member v-if="idx < showMaxIdx" class="group-side-member" :member="member"></group-member>
<el-form labelPosition="top" class="group-side-form" :model="group" size="small">
<el-form-item label="群聊名称">
<el-input v-model="group.name" disabled maxlength="20"></el-input>
</el-form-item>
<el-form-item label="群主">
<el-input :value="ownerName" disabled></el-input>
</el-form-item>
<el-form-item label="群公告">
<el-input v-model="group.notice" disabled type="textarea" maxlength="1024"></el-input>
</el-form-item>
<el-form-item label="备注">
<el-input v-model="group.remarkGroupName" :disabled="!editing" maxlength="20"></el-input>
</el-form-item>
<el-form-item label="我在本群的昵称">
<el-input v-model="group.remarkNickName" :disabled="!editing" maxlength="20"></el-input>
</el-form-item>
<div v-show="!group.quit" class="btn-group">
<el-button v-if="editing" type="success" @click="onSaveGroup()">保存</el-button>
<el-button v-if="!editing" type="primary" @click="editing = !editing">编辑</el-button>
<el-button type="danger" v-show="!isOwner" @click="onQuit()">退出群聊</el-button>
</div> </div>
</el-form> </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="群聊名称">
<el-input v-model="group.name" disabled maxlength="20"></el-input>
</el-form-item>
<el-form-item label="群主">
<el-input :value="ownerName" disabled></el-input>
</el-form-item>
<el-form-item label="群公告">
<el-input v-model="group.notice" disabled type="textarea" maxlength="1024"></el-input>
</el-form-item>
<el-form-item label="备注">
<el-input v-model="group.remarkGroupName" :disabled="!editing" maxlength="20"></el-input>
</el-form-item>
<el-form-item label="我在本群的昵称">
<el-input v-model="group.remarkNickName" :disabled="!editing" maxlength="20"></el-input>
</el-form-item>
<div v-show="!group.quit" class="btn-group">
<el-button v-if="editing" type="success" @click="onSaveGroup()">保存</el-button>
<el-button v-if="!editing" type="primary" @click="editing = !editing">编辑</el-button>
<el-button type="danger" v-show="!isOwner" @click="onQuit()">退出群聊</el-button>
</div>
</el-form>
</div> </div>
</template> </template>
<script> <script>
import AddGroupMember from '../group/AddGroupMember.vue'; import AddGroupMember from '../group/AddGroupMember.vue';
import GroupMember from '../group/GroupMember.vue'; import GroupMember from '../group/GroupMember.vue';
import GroupMemberSelector from '../group/GroupMemberSelector.vue';
export default { export default {
name: "chatGroupSide", name: "chatGroupSide",
components: { components: {
AddGroupMember, AddGroupMember,
GroupMember GroupMember,
GroupMemberSelector
}, },
data() { data() {
return { return {
searchText: "", searchText: "",
editing: false, editing: false,
showAddGroupMember: false,
showMaxIdx: 50 showMaxIdx: 50
} }
}, },
@ -80,6 +85,29 @@ export default {
onClose() { onClose() {
this.$emit('close'); this.$emit('close');
}, },
onInvite() {
this.$refs.addGroupMember.open()
},
onRemove() {
//
let hideIds = [this.group.ownerId];
this.$refs.removeSelector.open(50, [], [], hideIds);
},
onRemoveComplete(members) {
let userIds = members.map(m => m.userId);
let data = {
groupId: this.group.id,
userIds: userIds
}
this.$http({
url: "/group/members/remove",
method: 'delete',
data: data
}).then(() => {
this.$emit('reload');
this.$message.success(`您移除了${userIds.length}位成员`);
})
},
loadGroupMembers() { loadGroupMembers() {
this.$http({ this.$http({
url: `/group/members/${this.group.id}`, url: `/group/members/${this.group.id}`,
@ -127,12 +155,15 @@ export default {
} }
}, },
computed: { computed: {
mine() {
return this.$store.state.userStore.userInfo;
},
ownerName() { ownerName() {
let member = this.groupMembers.find((m) => m.userId == this.group.ownerId); let member = this.groupMembers.find((m) => m.userId == this.group.ownerId);
return member && member.showNickName; return member && member.showNickName;
}, },
isOwner() { isOwner() {
return this.group.ownerId == this.$store.state.userStore.userInfo.id; return this.group.ownerId == this.mine.id;
}, },
showMembers() { showMembers() {
return this.groupMembers.filter((m) => !m.quit && m.showNickName.includes(this.searchText)) return this.groupMembers.filter((m) => !m.quit && m.showNickName.includes(this.searchText))
@ -156,9 +187,6 @@ export default {
padding: 10px; padding: 10px;
} }
.group-side-scrollbar {
overflow: auto;
}
.el-divider--horizontal { .el-divider--horizontal {
margin: 0; margin: 0;
@ -168,7 +196,7 @@ export default {
margin-bottom: 0px !important; margin-bottom: 0px !important;
} }
.group-side-member-list { .member-list {
padding: 10px; padding: 10px;
display: flex; display: flex;
align-items: center; align-items: center;
@ -180,14 +208,15 @@ export default {
margin-left: 5px; margin-left: 5px;
} }
.group-side-invite {
.member-tools {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
width: 50px; width: 54px;
margin-left: 5px; margin-left: 5px;
.invite-member-btn { .tool-btn {
width: 38px; width: 38px;
height: 38px; height: 38px;
line-height: 38px; line-height: 38px;
@ -201,7 +230,7 @@ export default {
} }
} }
.invite-member-text { .tool-text {
font-size: 12px; font-size: 12px;
text-align: center; text-align: center;
width: 100%; width: 100%;

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

@ -117,7 +117,6 @@ export default {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
if (this.atIng) { if (this.atIng) {
console.log('选中at的人')
this.$refs.atBox.select(); this.$refs.atBox.select();
return; return;
} }
@ -137,12 +136,10 @@ export default {
} }
// //
if (e.keyCode === 8) { if (e.keyCode === 8) {
console.log("delete")
// dom // dom
setTimeout(() => { setTimeout(() => {
let s = this.$refs.content.innerHTML.trim(); let s = this.$refs.content.innerHTML.trim();
// domdom // domdom
console.log(s);
if (s === '' || s === '<br>' || s === '<div>&nbsp;</div>') { if (s === '' || s === '<br>' || s === '<div>&nbsp;</div>') {
// dom // dom
this.empty(); this.empty();
@ -179,7 +176,6 @@ export default {
blurRange.setEnd(blurRange.endContainer, endOffset); blurRange.setEnd(blurRange.endContainer, endOffset);
blurRange.deleteContents() blurRange.deleteContents()
blurRange.collapse(); blurRange.collapse();
console.log("onAtSelect")
this.focus(); this.focus();
// //
let element = document.createElement('SPAN') let element = document.createElement('SPAN')

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

@ -19,8 +19,7 @@
<div class="chat-content-text" v-html="$emo.transform(chat.lastContent,'emoji-small')"></div> <div class="chat-content-text" v-html="$emo.transform(chat.lastContent,'emoji-small')"></div>
</div> </div>
</div> </div>
<right-menu v-show="rightMenu.show" :pos="rightMenu.pos" :items="rightMenu.items" <right-menu ref="rightMenu" @select="onSelectMenu"></right-menu>
@close="rightMenu.show = false" @select="onSelectMenu"></right-menu>
</div> </div>
</template> </template>
@ -37,22 +36,15 @@ export default {
}, },
data() { data() {
return { return {
rightMenu: { menuItems: [{
show: false, key: 'TOP',
pos: { name: '置顶',
x: 0, icon: 'el-icon-top'
y: 0 }, {
}, key: 'DELETE',
items: [{ name: '删除',
key: 'TOP', icon: 'el-icon-delete'
name: '置顶', }]
icon: 'el-icon-top'
}, {
key: 'DELETE',
name: '删除',
icon: 'el-icon-delete'
}]
}
} }
}, },
props: { props: {
@ -68,11 +60,7 @@ export default {
}, },
methods: { methods: {
showRightMenu(e) { showRightMenu(e) {
this.rightMenu.pos = { this.$refs.rightMenu.open(e, this.menuItems);
x: e.x,
y: e.y
};
this.rightMenu.show = "true";
}, },
onSelectMenu(item) { onSelectMenu(item) {
this.$emit(item.key.toLowerCase(), this.msgInfo); this.$emit(item.key.toLowerCase(), this.msgInfo);

18
im-web/src/components/chat/ChatMessageItem.vue

@ -70,8 +70,7 @@
</div> </div>
</div> </div>
</div> </div>
<right-menu v-show="menu && rightMenu.show" :pos="rightMenu.pos" :items="menuItems" <right-menu ref="rightMenu" @select="onSelectMenu"></right-menu>
@close="rightMenu.show = false" @select="onSelectMenu"></right-menu>
<chat-group-readed ref="chatGroupReadedBox" :msgInfo="msgInfo" :groupMembers="groupMembers"></chat-group-readed> <chat-group-readed ref="chatGroupReadedBox" :msgInfo="msgInfo" :groupMembers="groupMembers"></chat-group-readed>
</div> </div>
</template> </template>
@ -118,14 +117,7 @@ export default {
}, },
data() { data() {
return { return {
audioPlayState: 'STOP', audioPlayState: 'STOP'
rightMenu: {
show: false,
pos: {
x: 0,
y: 0
}
}
} }
}, },
methods: { methods: {
@ -147,11 +139,7 @@ export default {
this.onPlayVoice = 'RUNNING'; this.onPlayVoice = 'RUNNING';
}, },
showRightMenu(e) { showRightMenu(e) {
this.rightMenu.pos = { this.$refs.rightMenu.open(e, this.menuItems);
x: e.x,
y: e.y
};
this.rightMenu.show = "true";
}, },
onSelectMenu(item) { onSelectMenu(item) {
this.$emit(item.key.toLowerCase(), this.msgInfo); this.$emit(item.key.toLowerCase(), this.msgInfo);

14
im-web/src/components/common/HeadImage.vue

@ -2,8 +2,7 @@
<div class="head-image" @click="showUserInfo($event)" :style="{ cursor: isShowUserInfo ? 'pointer' : null }"> <div class="head-image" @click="showUserInfo($event)" :style="{ cursor: isShowUserInfo ? 'pointer' : null }">
<img class="avatar-image" v-show="url" :src="url" :style="avatarImageStyle" loading="lazy" /> <img class="avatar-image" v-show="url" :src="url" :style="avatarImageStyle" loading="lazy" />
<div class="avatar-text" v-show="!url" :style="avatarTextStyle"> <div class="avatar-text" v-show="!url" :style="avatarTextStyle">
{{ name?.substring(0, 2).toUpperCase() }} {{ name?.substring(0, 2).toUpperCase() }}</div>
</div>
<div v-show="online" class="online" title="用户当前在线"></div> <div v-show="online" class="online" title="用户当前在线"></div>
<slot></slot> <slot></slot>
</div> </div>
@ -15,8 +14,7 @@ export default {
data() { data() {
return { return {
colors: ["#5daa31", "#c7515a", "#e03697", "#85029b", colors: ["#5daa31", "#c7515a", "#e03697", "#85029b",
"#c9b455", "#326eb6" "#c9b455", "#326eb6"]
]
} }
}, },
props: { props: {
@ -61,7 +59,11 @@ export default {
url: `/user/find/${this.id}`, url: `/user/find/${this.id}`,
method: 'get' method: 'get'
}).then((user) => { }).then((user) => {
this.$store.commit("setUserInfoBoxPos", e); let pos = {
x: e.x + 30,
y: e.y
}
this.$store.commit("setUserInfoBoxPos", pos);
this.$store.commit("showUserInfoBox", user); this.$store.commit("showUserInfoBox", user);
}) })
} }
@ -111,8 +113,6 @@ export default {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
//border: 1px solid #ccc;
//box-shadow: var(--im-box-shadow);
} }
.online { .online {

28
im-web/src/components/common/RightMenu.vue

@ -1,10 +1,9 @@
<template> <template>
<div class="right-menu-mask" @click.stop="close()" @contextmenu.prevent="close()"> <div v-if="show" class="right-menu-mask" @click.stop="close()" @contextmenu.prevent="close()">
<div class="right-menu" :style="{ 'left': pos.x + 'px', 'top': pos.y + 'px' }"> <div class="right-menu" :style="{ 'left': pos.x + 'px', 'top': pos.y + 'px' }">
<el-menu text-color="#333333"> <el-menu text-color="#333333">
<el-menu-item v-for="(item) in items" :key="item.key" :title="item.name" <el-menu-item v-for="(item) in items" :key="item.key" :title="item.name"
@click.native.stop="onSelectMenu(item)"> @click.native.stop="onSelectMenu(item)">
<!-- <span :class="item.icon"></span>-->
<span>{{ item.name }}</span> <span>{{ item.name }}</span>
</el-menu-item> </el-menu-item>
</el-menu> </el-menu>
@ -16,19 +15,23 @@
export default { export default {
name: "rightMenu", name: "rightMenu",
data() { data() {
return {} return {
}, show: false,
props: { pos: {
pos: { x: 0,
type: Object y: 0,
}, },
items: { items: []
type: Array
} }
}, },
methods: { methods: {
open(pos, items) {
this.pos = pos;
this.items = items;
this.show = true;
},
close() { close() {
this.$emit("close"); this.show = false;
}, },
onSelectMenu(item) { onSelectMenu(item) {
this.$emit("select", item); this.$emit("select", item);
@ -64,8 +67,7 @@ export default {
height: 36px; height: 36px;
line-height: 36px; line-height: 36px;
min-width: 100px; min-width: 100px;
text-align: left; text-align: center;
padding: 0 0 0 20px;
&:hover { &:hover {
background-color: var(--im-background-active); background-color: var(--im-background-active);

101
im-web/src/components/common/UserInfo.vue

@ -1,27 +1,32 @@
<template> <template>
<div class="user-info-mask" @click="$emit('close')"> <div class="user-info" :style="{ left: pos.x + 'px', top: pos.y + 'px' }" @click.stop>
<div class="user-info" :style="{ left: pos.x + 'px', top: pos.y + 'px' }" @click.stop> <div class="user-info-box">
<div class="user-info-box"> <div class="avatar">
<div class="avatar"> <head-image :name="user.nickName" :url="user.headImageThumb" :size="60" :online="user.online"
<head-image :name="user.nickName" :url="user.headImageThumb" :size="70" :online="user.online" @click.native="showFullImage()" radius="10%"> </head-image>
radius="10%" @click.native="showFullImage()"> </head-image> </div>
<div class="info-card">
<div class="header">
<div class="nick-name">{{ user.nickName }}</div>
<div v-if="user.sex == 0" class="icon iconfont icon-man" style="color: darkblue;"></div>
<div v-if="user.sex == 1" class="icon iconfont icon-girl" style="color: darkred;"></div>
</div> </div>
<div> <div class="info-item">
<el-descriptions :column="1" :title="user.nickName" class="user-info-items"> 用户名: {{ user.userName }}
<el-descriptions-item label="用户名">{{ user.userName }} </div>
</el-descriptions-item> <div class="info-item">
<el-descriptions-item label="签名">{{ user.signature }} 个性签名: {{ user.signature }}
</el-descriptions-item>
</el-descriptions>
</div> </div>
</div>
<el-divider content-position="center"></el-divider>
<div class="user-btn-group">
<el-button v-show="isFriend" type="primary" @click="onSendMessage()">发消息</el-button>
<el-button v-show="!isFriend" type="primary" @click="onAddFriend()">加为好友</el-button>
</div> </div>
</div> </div>
<el-divider content-position="center"></el-divider>
<div class="user-btn-group">
<el-button v-if="isFriend" type="primary" @click="onSendMessage()">发消息</el-button>
<el-button v-else type="primary" @click="onAddFriend()">加为好友</el-button>
</div>
</div> </div>
</template> </template>
<script> <script>
@ -62,23 +67,7 @@ export default {
this.$emit("close"); this.$emit("close");
}, },
onAddFriend() { onAddFriend() {
this.$http({ this.$refs.applyRef.open(this.user);
url: "/friend/add",
method: "post",
params: {
friendId: this.user.id
}
}).then(() => {
this.$message.success("添加成功,对方已成为您的好友");
let friend = {
id: this.user.id,
nickName: this.user.nickName,
headImage: this.user.headImageThumb,
online: this.user.online,
deleted: false
}
this.$store.commit("addFriend", friend);
})
}, },
showFullImage() { showFullImage() {
if (this.user.headImage) { if (this.user.headImage) {
@ -89,6 +78,9 @@ export default {
computed: { computed: {
isFriend() { isFriend() {
return this.$store.getters.isFriend(this.user.id); return this.$store.getters.isFriend(this.user.id);
},
isWaitingApprove() {
return this.$store.getters.isInRecvRequest(this.user.id);
} }
} }
} }
@ -96,7 +88,7 @@ export default {
<style lang="scss"> <style lang="scss">
.user-info-mask { .user-info-mask {
background-color: rgba($color: #000000, $alpha: 0); background-color: rgba($color: #f4f4f4, $alpha: 0);
position: fixed; position: fixed;
left: 0; left: 0;
top: 0; top: 0;
@ -116,23 +108,33 @@ export default {
display: flex; display: flex;
align-items: center; align-items: center;
.user-info-items { .info-card {
margin-left: 10px; flex: 1;
white-space: nowrap; padding-left: 10px;
overflow: hidden;
.el-descriptions__header { .header {
margin-bottom: 5px; display: flex;
} align-items: center;
.nick-name {
font-size: var(--im-font-size-large);
font-weight: 600;
}
.el-descriptions__title { .icon {
font-size: 18px; margin-left: 3px;
font-size: var(--im-font-size);
}
} }
.el-descriptions-item__cell { .info-item {
padding-bottom: 1px; font-size: var(--im-font-size);
margin-top: 5px;
word-break: break-all;
} }
} }
} }
.el-divider--horizontal { .el-divider--horizontal {
@ -141,6 +143,11 @@ export default {
.user-btn-group { .user-btn-group {
text-align: center; text-align: center;
.wait-text {
font-size: 14px;
color: var(--im-text-color-light);
}
} }
} }
</style> </style>

36
im-web/src/components/friend/FriendItem.vue

@ -15,8 +15,7 @@
</i> </i>
</div> </div>
</div> </div>
<right-menu v-show="menu && rightMenu.show" :pos="rightMenu.pos" :items="rightMenu.items" <right-menu ref="rightMenu" @select="onSelectMenu"></right-menu>
@close="rightMenu.show = false" @select="onSelectMenu"></right-menu>
<slot></slot> <slot></slot>
</div> </div>
</template> </template>
@ -33,31 +32,22 @@ export default {
}, },
data() { data() {
return { return {
rightMenu: { menuItems: [{
show: false, key: 'CHAT',
pos: { name: '发送消息',
x: 0, icon: 'el-icon-chat-dot-round'
y: 0 }, {
}, key: 'DELETE',
items: [{ name: '删除好友',
key: 'CHAT', icon: 'el-icon-delete'
name: '发送消息', }]
icon: 'el-icon-chat-dot-round'
}, {
key: 'DELETE',
name: '删除好友',
icon: 'el-icon-delete'
}]
}
} }
}, },
methods: { methods: {
showRightMenu(e) { showRightMenu(e) {
this.rightMenu.pos = { if (this.menu) {
x: e.x, this.$refs.rightMenu.open(e, this.menuItems);
y: e.y }
};
this.rightMenu.show = "true";
}, },
onSelectMenu(item) { onSelectMenu(item) {
this.$emit(item.key.toLowerCase(), this.msgInfo); this.$emit(item.key.toLowerCase(), this.msgInfo);

60
im-web/src/components/group/AddGroupMember.vue

@ -1,5 +1,5 @@
<template> <template>
<el-dialog title="邀请好友" :visible.sync="visible" width="620px" :before-close="onClose"> <el-dialog title="邀请好友" :visible.sync="show" width="620px" :before-close="close">
<div class="agm-container"> <div class="agm-container">
<div class="agm-l-box"> <div class="agm-l-box">
<div class="search"> <div class="search">
@ -30,7 +30,7 @@
</div> </div>
</div> </div>
<span slot="footer" class="dialog-footer"> <span slot="footer" class="dialog-footer">
<el-button @click="onClose()"> </el-button> <el-button @click="close()"> </el-button>
<el-button type="primary" @click="onOk()"> </el-button> <el-button type="primary" @click="onOk()"> </el-button>
</span> </span>
</el-dialog> </el-dialog>
@ -46,13 +46,35 @@ export default {
}, },
data() { data() {
return { return {
show: false,
searchText: "", searchText: "",
friends: [] friends: []
} }
}, },
methods: { methods: {
onClose() { open() {
this.$emit("close"); this.show = true;
this.friends = [];
this.$store.state.friendStore.friends.forEach((f) => {
if (f.deleted) {
return;
}
let friend = JSON.parse(JSON.stringify(f))
let m = this.members.filter((m) => !m.quit)
.find((m) => m.userId == f.id);
if (m) {
//
friend.disabled = true;
friend.isCheck = true
} else {
friend.disabled = false;
friend.isCheck = false;
}
this.friends.push(friend);
})
},
close() {
this.show = false;
}, },
onOk() { onOk() {
let inviteVO = { let inviteVO = {
@ -72,7 +94,7 @@ export default {
}).then(() => { }).then(() => {
this.$message.success("邀请成功"); this.$message.success("邀请成功");
this.$emit("reload"); this.$emit("reload");
this.$emit("close"); this.close()
}) })
} }
}, },
@ -86,9 +108,6 @@ export default {
} }
}, },
props: { props: {
visible: {
type: Boolean
},
groupId: { groupId: {
type: Number type: Number
}, },
@ -100,32 +119,7 @@ export default {
checkCount() { checkCount() {
return this.friends.filter((f) => f.isCheck && !f.disabled).length; return this.friends.filter((f) => f.isCheck && !f.disabled).length;
} }
},
watch: {
visible: function (newData, oldData) {
if (newData) {
this.friends = [];
this.$store.state.friendStore.friends.forEach((f) => {
if (f.deleted) {
return;
}
let friend = JSON.parse(JSON.stringify(f))
let m = this.members.filter((m) => !m.quit)
.find((m) => m.userId == f.id);
if (m) {
//
friend.disabled = true;
friend.isCheck = true
} else {
friend.disabled = false;
friend.isCheck = false;
}
this.friends.push(friend);
})
}
}
} }
} }
</script> </script>

8
im-web/src/components/group/GroupMember.vue

@ -2,7 +2,6 @@
<div class="group-member"> <div class="group-member">
<head-image :id="member.userId" :name="member.showNickName" :url="member.headImage" :size="38" <head-image :id="member.userId" :name="member.showNickName" :url="member.headImage" :size="38"
:online="member.online"> :online="member.online">
<div v-if="showDel" @click.stop="onDelete()" class="btn-kick el-icon-error"></div>
</head-image> </head-image>
<div class="member-name">{{ member.showNickName }}</div> <div class="member-name">{{ member.showNickName }}</div>
</div> </div>
@ -20,16 +19,9 @@ export default {
member: { member: {
type: Object, type: Object,
required: true required: true
},
showDel: {
type: Boolean,
default: false
} }
}, },
methods: { methods: {
onDelete() {
this.$emit("del", this.member);
}
} }
} }
</script> </script>

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

@ -1,5 +1,5 @@
<template> <template>
<el-dialog title="选择成员" :visible.sync="isShow" width="700px"> <el-dialog :title="title" :visible.sync="isShow" width="700px">
<div class="group-member-selector"> <div class="group-member-selector">
<div class="left-box"> <div class="left-box">
<el-input placeholder="搜索" v-model="searchText"> <el-input placeholder="搜索" v-model="searchText">
@ -7,7 +7,8 @@
</el-input> </el-input>
<virtual-scroller class="scroll-box" :items="showMembers"> <virtual-scroller class="scroll-box" :items="showMembers">
<template v-slot="{ item }"> <template v-slot="{ item }">
<group-member-item :member="item" @click.native="onClickMember(item)"> <group-member-item :group="group" :groupMembers="showMembers" :member="item" :menu="false"
@click.native="onClickMember(item)">
<el-checkbox :disabled="item.locked" v-model="item.checked" @change="onChange(item)" <el-checkbox :disabled="item.locked" v-model="item.checked" @change="onChange(item)"
@click.native.stop=""></el-checkbox> @click.native.stop=""></el-checkbox>
</group-member-item> </group-member-item>
@ -17,11 +18,13 @@
<div class="arrow el-icon-d-arrow-right"></div> <div class="arrow el-icon-d-arrow-right"></div>
<div class="right-box"> <div class="right-box">
<div class="select-tip"> 已勾选{{ checkedMembers.length }}位成员</div> <div class="select-tip"> 已勾选{{ checkedMembers.length }}位成员</div>
<div class="checked-member-list"> <el-scrollbar class="scroll-box">
<div v-for="m in members" :key="m.userId"> <div class="checked-member-list">
<group-member class="member-item" v-if="m.checked" :member="m"></group-member> <div v-for="m in members" :key="m.userId">
<group-member class="member-item" v-if="m.checked" :member="m"></group-member>
</div>
</div> </div>
</div> </el-scrollbar>
</div> </div>
</div> </div>
<span slot="footer" class="dialog-footer"> <span slot="footer" class="dialog-footer">
@ -52,25 +55,30 @@ export default {
} }
}, },
props: { props: {
groupId: { group: {
type: Number type: Object
},
title: {
type: String,
default: "选择成员"
} }
}, },
methods: { methods: {
open(maxSize, checkedIds, lockedIds) { open(maxSize, checkedIds, lockedIds, hideIds) {
this.maxSize = maxSize; this.maxSize = maxSize;
this.isShow = true; this.isShow = true;
this.loadGroupMembers(checkedIds, lockedIds); this.loadGroupMembers(checkedIds, lockedIds, hideIds);
}, },
loadGroupMembers(checkedIds, lockedIds) { loadGroupMembers(checkedIds, lockedIds, hideIds) {
this.$http({ this.$http({
url: `/group/members/${this.groupId}`, url: `/group/members/${this.group.id}`,
method: 'get' method: 'get'
}).then((members) => { }).then((members) => {
members.forEach((m) => { members.forEach((m) => {
// //
m.checked = checkedIds.indexOf(m.userId) >= 0; m.checked = checkedIds.indexOf(m.userId) >= 0;
m.locked = lockedIds.indexOf(m.userId) >= 0; m.locked = lockedIds.indexOf(m.userId) >= 0;
m.hide = hideIds.indexOf(m.userId) >= 0;
}); });
this.members = members; this.members = members;
}); });
@ -79,13 +87,13 @@ export default {
if (!m.locked) { if (!m.locked) {
m.checked = !m.checked; m.checked = !m.checked;
} }
if (this.checkedMembers.length > this.maxSize) { if (this.maxSize > 0 && this.checkedMembers.length > this.maxSize) {
this.$message.error(`最多选择${this.maxSize}位成员`) this.$message.error(`最多选择${this.maxSize}位成员`)
m.checked = false; m.checked = false;
} }
}, },
onChange(m) { onChange(m) {
if (this.checkedMembers.length > this.maxSize) { if (this.maxSize > 0 && this.checkedMembers.length > this.maxSize) {
this.$message.error(`最多选择${this.maxSize}位成员`) this.$message.error(`最多选择${this.maxSize}位成员`)
m.checked = false; m.checked = false;
} }
@ -112,7 +120,6 @@ export default {
return this.members.filter((m) => !m.hide && !m.quit && m.showNickName.includes(this.searchText)) return this.members.filter((m) => !m.hide && !m.quit && m.showNickName.includes(this.searchText))
} }
} }
} }
</script> </script>
@ -120,25 +127,21 @@ export default {
.group-member-selector { .group-member-selector {
display: flex; display: flex;
.scroll-box {
height: 400px;
}
.left-box { .left-box {
width: 48%; width: 48%;
overflow: hidden; overflow: hidden;
border: var(--im-border); border: var(--im-border);
.scroll-box {
height: 400px;
}
.el-input__inner { .el-input__inner {
border: none; border: none;
border-bottom: var(--im-border); border-bottom: var(--im-border);
} }
} }
.arrow { .arrow {
display: flex; display: flex;
align-items: center; align-items: center;

1
im-web/src/components/rtc/RtcPrivateVideo.vue

@ -157,7 +157,6 @@ export default {
}) })
}, },
onReject() { onReject() {
console.log("onReject")
// 退 // 退
this.API.reject(this.friend.id); this.API.reject(this.friend.id);
// 退 // 退

1
im-web/src/view/Friend.vue

@ -131,7 +131,6 @@ export default {
showName: user.nickName, showName: user.nickName,
headImage: user.headImageThumb, headImage: user.headImageThumb,
}; };
console.log("chat:", chat)
this.$store.commit("openChat", chat); this.$store.commit("openChat", chat);
this.$store.commit("activeChat", 0); this.$store.commit("activeChat", 0);
this.$router.push("/home/chat"); this.$router.push("/home/chat");

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

@ -71,19 +71,24 @@
<el-divider content-position="center"></el-divider> <el-divider content-position="center"></el-divider>
<el-scrollbar ref="scrollbar" :style="'height: ' + scrollHeight + 'px'"> <el-scrollbar ref="scrollbar" :style="'height: ' + scrollHeight + 'px'">
<div class="group-member-list"> <div class="group-member-list">
<div class="group-invite"> <div class="member-tools">
<div class="invite-member-btn" title="邀请好友进群聊" @click="onInviteMember()"> <div class="tool-btn" title="邀请好友进群聊" @click="onInvite()">
<i class="el-icon-plus"></i> <i class="el-icon-plus"></i>
</div> </div>
<div class="invite-member-text">邀请</div> <div class="tool-text">邀请</div>
<add-group-member :visible="showAddGroupMember" :groupId="activeGroup.id" <add-group-member ref="addGroupMember" :groupId="activeGroup.id" :members="groupMembers"
:members="groupMembers" @reload="loadGroupMembers" @reload="$emit('reload')"></add-group-member>
@close="onCloseAddGroupMember"></add-group-member> </div>
<div class="member-tools" v-if="isOwner">
<div class="tool-btn" title="选择成员移出群聊" @click="onRemove()">
<i class="el-icon-minus"></i>
</div>
<div class="tool-text">移除</div>
<group-member-selector ref="removeSelector" title="选择成员进行移除" :group="activeGroup"
@complete="onRemoveComplete"></group-member-selector>
</div> </div>
<div v-for="(member, idx) in showMembers" :key="member.id"> <div v-for="(member, idx) in showMembers" :key="member.id">
<group-member v-if="idx < showMaxIdx" class="group-member" :member="member" <group-member v-if="idx < showMaxIdx" class="group-member" :member="member"></group-member>
:showDel="isOwner && member.userId != activeGroup.ownerId"
@del="onKick"></group-member>
</div> </div>
</div> </div>
</el-scrollbar> </el-scrollbar>
@ -99,6 +104,7 @@ import GroupItem from '../components/group/GroupItem';
import FileUpload from '../components/common/FileUpload'; import FileUpload from '../components/common/FileUpload';
import GroupMember from '../components/group/GroupMember.vue'; import GroupMember from '../components/group/GroupMember.vue';
import AddGroupMember from '../components/group/AddGroupMember.vue'; import AddGroupMember from '../components/group/AddGroupMember.vue';
import GroupMemberSelector from '../components/group/GroupMemberSelector.vue';
import HeadImage from '../components/common/HeadImage.vue'; import HeadImage from '../components/common/HeadImage.vue';
import { pinyin } from 'pinyin-pro'; import { pinyin } from 'pinyin-pro';
@ -109,6 +115,7 @@ export default {
GroupMember, GroupMember,
FileUpload, FileUpload,
AddGroupMember, AddGroupMember,
GroupMemberSelector,
HeadImage HeadImage
}, },
data() { data() {
@ -156,11 +163,28 @@ export default {
// //
this.loadGroupMembers(); this.loadGroupMembers();
}, },
onInviteMember() { onInvite() {
this.showAddGroupMember = true; this.$refs.addGroupMember.open();
}, },
onCloseAddGroupMember() { onRemove() {
this.showAddGroupMember = false; //
let hideIds = [this.activeGroup.ownerId];
this.$refs.removeSelector.open(50, [], [], hideIds);
},
onRemoveComplete(members) {
let userIds = members.map(m => m.userId);
let data = {
groupId: this.activeGroup.id,
userIds: userIds
}
this.$http({
url: "/group/members/remove",
method: 'delete',
data: data
}).then(() => {
this.loadGroupMembers();
this.$message.success(`您移除了${userIds.length}位成员`);
})
}, },
onUploadSuccess(data) { onUploadSuccess(data) {
this.activeGroup.headImage = data.originUrl; this.activeGroup.headImage = data.originUrl;
@ -197,25 +221,6 @@ export default {
}); });
}) })
}, },
onKick(member) {
this.$confirm(`确定将成员'${member.showNickName}'移出群聊吗?`, '确认移出?', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.$http({
url: `/group/kick/${this.activeGroup.id}`,
method: 'delete',
params: {
userId: member.userId
}
}).then(() => {
this.$message.success(`已将${member.showNickName}移出群聊`);
member.quit = true;
});
})
},
onQuit() { onQuit() {
this.$confirm(`确认退出'${this.activeGroup.showGroupName}',并清空聊天记录吗?`, '确认退出?', { this.$confirm(`确认退出'${this.activeGroup.showGroupName}',并清空聊天记录吗?`, '确认退出?', {
confirmButtonText: '确定', confirmButtonText: '确定',
@ -455,13 +460,13 @@ export default {
margin-right: 5px; margin-right: 5px;
} }
.group-invite { .member-tools {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
width: 60px; width: 60px;
.invite-member-btn { .tool-btn {
width: 38px; width: 38px;
height: 38px; height: 38px;
line-height: 38px; line-height: 38px;
@ -475,7 +480,7 @@ export default {
} }
} }
.invite-member-text { .tool-text {
font-size: var(--im-font-size-smaller); font-size: var(--im-font-size-smaller);
text-align: center; text-align: center;
width: 100%; width: 100%;

4
im-web/src/view/Home.vue

@ -1,5 +1,5 @@
<template> <template>
<div class="home-page"> <div class="home-page" @click="$store.commit('closeUserInfoBox')">
<div class="app-container" :class="{ fullscreen: isFullscreen }"> <div class="app-container" :class="{ fullscreen: isFullscreen }">
<div class="navi-bar"> <div class="navi-bar">
<div class="navi-bar-box"> <div class="navi-bar-box">
@ -132,7 +132,6 @@ export default {
} }
}); });
this.$wsApi.onClose((e) => { this.$wsApi.onClose((e) => {
console.log(e);
if (e.code != 3000) { if (e.code != 3000) {
// 线 // 线
this.reconnectWs(); this.reconnectWs();
@ -303,7 +302,6 @@ export default {
} }
// //
if (msg.type == this.$enums.MESSAGE_TYPE.GROUP_DEL) { if (msg.type == this.$enums.MESSAGE_TYPE.GROUP_DEL) {
console.log("this.$enums.MESSAGE_TYPE.GROUP_DE")
this.$store.commit("removeGroup", msg.groupId); this.$store.commit("removeGroup", msg.groupId);
return; return;
} }

Loading…
Cancel
Save