Browse Source

feat: 消息免打扰

master
xsx 10 months ago
parent
commit
450c177b97
  1. 2
      db/im-platform.sql
  2. 9
      im-platform/src/main/java/com/bx/implatform/controller/FriendController.java
  3. 8
      im-platform/src/main/java/com/bx/implatform/controller/GroupController.java
  4. 23
      im-platform/src/main/java/com/bx/implatform/dto/FriendDndDTO.java
  5. 23
      im-platform/src/main/java/com/bx/implatform/dto/GroupDndDTO.java
  6. 5
      im-platform/src/main/java/com/bx/implatform/entity/Friend.java
  7. 6
      im-platform/src/main/java/com/bx/implatform/entity/GroupMember.java
  8. 2
      im-platform/src/main/java/com/bx/implatform/enums/MessageType.java
  9. 7
      im-platform/src/main/java/com/bx/implatform/service/FriendService.java
  10. 9
      im-platform/src/main/java/com/bx/implatform/service/GroupMemberService.java
  11. 7
      im-platform/src/main/java/com/bx/implatform/service/GroupService.java
  12. 31
      im-platform/src/main/java/com/bx/implatform/service/impl/FriendServiceImpl.java
  13. 8
      im-platform/src/main/java/com/bx/implatform/service/impl/GroupMemberServiceImpl.java
  14. 27
      im-platform/src/main/java/com/bx/implatform/service/impl/GroupServiceImpl.java
  15. 4
      im-platform/src/main/java/com/bx/implatform/vo/FriendVO.java
  16. 3
      im-platform/src/main/java/com/bx/implatform/vo/GroupVO.java
  17. 31
      im-uniapp/App.vue
  18. 4
      im-uniapp/common/enums.js
  19. 10
      im-uniapp/components/chat-item/chat-item.vue
  20. 38
      im-uniapp/im.scss
  21. 35
      im-uniapp/pages/chat/chat-box.vue
  22. 4
      im-uniapp/pages/chat/chat.vue
  23. 55
      im-uniapp/pages/common/user-info.vue
  24. 50
      im-uniapp/pages/group/group-info.vue
  25. 34
      im-uniapp/static/icon/iconfont.css
  26. BIN
      im-uniapp/static/icon/iconfont.ttf
  27. 46
      im-uniapp/store/chatStore.js
  28. 4
      im-uniapp/store/friendStore.js
  29. 4
      im-uniapp/store/groupStore.js
  30. 2
      im-web/src/api/enums.js
  31. 26
      im-web/src/assets/iconfont/iconfont.css
  32. BIN
      im-web/src/assets/iconfont/iconfont.ttf
  33. 43
      im-web/src/components/chat/ChatItem.vue
  34. 8
      im-web/src/components/common/UserInfo.vue
  35. 31
      im-web/src/store/chatStore.js
  36. 4
      im-web/src/store/friendStore.js
  37. 4
      im-web/src/store/groupStore.js
  38. 37
      im-web/src/view/Chat.vue
  39. 11
      im-web/src/view/Friend.vue
  40. 1
      im-web/src/view/Group.vue
  41. 25
      im-web/src/view/Home.vue

2
db/im-platform.sql

@ -22,6 +22,7 @@ create table `im_friend`(
`friend_id` bigint not null comment '好友id',
`friend_nick_name` varchar(255) not null comment '好友昵称',
`friend_head_image` varchar(255) default '' comment '好友头像',
`is_dnd` tinyint comment '免打扰标识(Do Not Disturb) 0:关闭 1:开启',
`deleted` tinyint comment '删除标识 0:正常 1:已删除',
`created_time` datetime DEFAULT CURRENT_TIMESTAMP comment '创建时间',
key `idx_user_id` (`user_id`),
@ -62,6 +63,7 @@ create table `im_group_member`(
`remark_nick_name` varchar(255) DEFAULT '' comment '显示昵称备注',
`head_image` varchar(255) DEFAULT '' comment '用户头像',
`remark_group_name` varchar(255) DEFAULT '' comment '显示群名备注',
`is_dnd` tinyint comment '免打扰标识(Do Not Disturb) 0:关闭 1:开启',
`quit` tinyint(1) DEFAULT 0 comment '是否已退出',
`quit_time` datetime DEFAULT NULL comment '退出时间',
`created_time` datetime DEFAULT CURRENT_TIMESTAMP comment '创建时间',

9
im-platform/src/main/java/com/bx/implatform/controller/FriendController.java

@ -1,12 +1,14 @@
package com.bx.implatform.controller;
import com.bx.implatform.annotation.RepeatSubmit;
import com.bx.implatform.dto.FriendDndDTO;
import com.bx.implatform.result.Result;
import com.bx.implatform.result.ResultUtils;
import com.bx.implatform.service.FriendService;
import com.bx.implatform.vo.FriendVO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
@ -50,5 +52,12 @@ public class FriendController {
return ResultUtils.success();
}
@PutMapping("/dnd")
@Operation(summary = "开启/关闭免打扰状态", description = "开启/关闭免打扰状态")
public Result setFriendDnd(@Valid @RequestBody FriendDndDTO dto) {
friendService.setDnd(dto);
return ResultUtils.success();
}
}

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

@ -1,6 +1,7 @@
package com.bx.implatform.controller;
import com.bx.implatform.annotation.RepeatSubmit;
import com.bx.implatform.dto.GroupDndDTO;
import com.bx.implatform.dto.GroupInviteDTO;
import com.bx.implatform.dto.GroupMemberRemoveDTO;
import com.bx.implatform.result.Result;
@ -101,5 +102,12 @@ public class GroupController {
return ResultUtils.success();
}
@Operation(summary = "开启/关闭免打扰", description = "开启/关闭免打扰")
@PutMapping("/dnd")
public Result setGroupDnd(@Valid @RequestBody GroupDndDTO dto) {
groupService.setDnd(dto);
return ResultUtils.success();
}
}

23
im-platform/src/main/java/com/bx/implatform/dto/FriendDndDTO.java

@ -0,0 +1,23 @@
package com.bx.implatform.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* @author Blue
* @version 1.0
*/
@Data
@Schema(description = "好友免打扰")
public class FriendDndDTO {
@NotNull(message = "好友id不可为空")
@Schema(description = "好友用户id")
private Long friendId;
@NotNull(message = "消息免打扰状态不可为空")
@Schema(description = "消息免打扰状态")
private Boolean isDnd;
}

23
im-platform/src/main/java/com/bx/implatform/dto/GroupDndDTO.java

@ -0,0 +1,23 @@
package com.bx.implatform.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* @author Blue
* @version 1.0
* @date 2025-02-23
*/
@Data
@Schema(description = "群聊免打扰")
public class GroupDndDTO {
@NotNull(message = "群id不可为空")
@Schema(description = "群组id")
private Long groupId;
@NotNull(message = "免打扰状态不可为空")
@Schema(description = "免打扰状态")
private Boolean isDnd;
}

5
im-platform/src/main/java/com/bx/implatform/entity/Friend.java

@ -45,6 +45,11 @@ public class Friend{
*/
private String friendHeadImage;
/**
* 是否开启免打扰
*/
private Boolean isDnd;
/**
* 是否已删除
*/

6
im-platform/src/main/java/com/bx/implatform/entity/GroupMember.java

@ -58,6 +58,12 @@ public class GroupMember extends Model<GroupMember> {
*/
private String remarkGroupName;
/**
* 是否免打扰
*/
private Boolean isDnd;
/**
* 是否已退出
*/

2
im-platform/src/main/java/com/bx/implatform/enums/MessageType.java

@ -37,8 +37,10 @@ public enum MessageType {
GROUP_UNBAN(52,"群聊解封"),
FRIEND_NEW(80, "新增好友"),
FRIEND_DEL(81, "删除好友"),
FRIEND_DND(82, "好友免打扰"),
GROUP_NEW(90, "新增群聊"),
GROUP_DEL(91, "删除群聊"),
GROUP_DND(92, "群聊免打扰"),
RTC_CALL_VOICE(100, "语音呼叫"),
RTC_CALL_VIDEO(101, "视频呼叫"),
RTC_ACCEPT(102, "接受"),

7
im-platform/src/main/java/com/bx/implatform/service/FriendService.java

@ -1,6 +1,7 @@
package com.bx.implatform.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.bx.implatform.dto.FriendDndDTO;
import com.bx.implatform.entity.Friend;
import com.bx.implatform.vo.FriendVO;
@ -70,4 +71,10 @@ public interface FriendService extends IService<Friend> {
*/
void bindFriend(Long userId, Long friendId);
/**
* 设置好友免打扰状态
* @param dto
*/
void setDnd(FriendDndDTO dto);
}

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

@ -1,6 +1,7 @@
package com.bx.implatform.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.bx.implatform.dto.GroupDndDTO;
import com.bx.implatform.entity.GroupMember;
import java.util.List;
@ -90,4 +91,12 @@ public interface GroupMemberService extends IService<GroupMember> {
* @param userIds 用户id
*/
Boolean isInGroup(Long groupId,List<Long> userIds);
/**
* 设置免打扰状态
* @param groupId 群id
* @param userId 用户id
* @param isDnd 是否开启免打扰
*/
void setDnd(Long groupId, Long userId, Boolean isDnd);
}

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

@ -1,6 +1,7 @@
package com.bx.implatform.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.bx.implatform.dto.GroupDndDTO;
import com.bx.implatform.dto.GroupInviteDTO;
import com.bx.implatform.dto.GroupMemberRemoveDTO;
import com.bx.implatform.entity.Group;
@ -92,4 +93,10 @@ public interface GroupService extends IService<Group> {
* @return List<GroupMemberVO>
**/
List<GroupMemberVO> findGroupMembers(Long groupId);
/**
* 开启/关闭免打扰
* @param dto
*/
void setDnd(GroupDndDTO dto);
}

31
im-platform/src/main/java/com/bx/implatform/service/impl/FriendServiceImpl.java

@ -11,6 +11,7 @@ import com.bx.imcommon.enums.IMTerminalType;
import com.bx.imcommon.model.IMPrivateMessage;
import com.bx.imcommon.model.IMUserInfo;
import com.bx.implatform.contant.RedisKey;
import com.bx.implatform.dto.FriendDndDTO;
import com.bx.implatform.entity.Friend;
import com.bx.implatform.entity.PrivateMessage;
import com.bx.implatform.entity.User;
@ -138,6 +139,18 @@ public class FriendServiceImpl extends ServiceImpl<FriendMapper, Friend> impleme
sendAddFriendMessage(userId, friendId, friend);
}
@Override
public void setDnd(FriendDndDTO dto) {
UserSession session = SessionContext.getSession();
LambdaUpdateWrapper<Friend> wrapper = Wrappers.lambdaUpdate();
wrapper.eq(Friend::getUserId, session.getUserId());
wrapper.eq(Friend::getFriendId, dto.getFriendId());
wrapper.set(Friend::getIsDnd, dto.getIsDnd());
this.update(wrapper);
// 推送同步消息
sendSyncDndMessage(dto.getFriendId(), dto.getIsDnd());
}
/**
* 单向解除好友关系
*
@ -175,6 +188,7 @@ public class FriendServiceImpl extends ServiceImpl<FriendMapper, Friend> impleme
vo.setHeadImage(f.getFriendHeadImage());
vo.setNickName(f.getFriendNickName());
vo.setDeleted(f.getDeleted());
vo.setIsDnd(f.getIsDnd());
return vo;
}
@ -254,4 +268,21 @@ public class FriendServiceImpl extends ServiceImpl<FriendMapper, Friend> impleme
sendMessage.setData(messageInfo);
imClient.sendPrivateMessage(sendMessage);
}
void sendSyncDndMessage(Long friendId, Boolean isDnd) {
// 同步免打扰状态到其他终端
UserSession session = SessionContext.getSession();
PrivateMessageVO msgInfo = new PrivateMessageVO();
msgInfo.setSendId(session.getUserId());
msgInfo.setRecvId(friendId);
msgInfo.setSendTime(new Date());
msgInfo.setType(MessageType.FRIEND_DND.code());
msgInfo.setContent(isDnd.toString());
IMPrivateMessage<PrivateMessageVO> sendMessage = new IMPrivateMessage<>();
sendMessage.setSender(new IMUserInfo(session.getUserId(), session.getTerminal()));
sendMessage.setData(msgInfo);
sendMessage.setSendToSelf(true);
imClient.sendPrivateMessage(sendMessage);
}
}

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

@ -120,4 +120,12 @@ public class GroupMemberServiceImpl extends ServiceImpl<GroupMemberMapper, Group
return userIds.size() == this.count(wrapper);
}
@Override
public void setDnd(Long groupId, Long userId, Boolean isDnd) {
LambdaUpdateWrapper<GroupMember> wrapper = Wrappers.lambdaUpdate();
wrapper.eq(GroupMember::getGroupId, groupId);
wrapper.eq(GroupMember::getUserId, userId);
wrapper.set(GroupMember::getIsDnd, isDnd);
this.update(wrapper);
}
}

27
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.implatform.contant.Constant;
import com.bx.implatform.contant.RedisKey;
import com.bx.implatform.dto.GroupDndDTO;
import com.bx.implatform.dto.GroupInviteDTO;
import com.bx.implatform.dto.GroupMemberRemoveDTO;
import com.bx.implatform.entity.*;
@ -314,6 +315,15 @@ public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements
}).sorted((m1, m2) -> m2.getOnline().compareTo(m1.getOnline())).collect(Collectors.toList());
}
@Override
public void setDnd(GroupDndDTO dto) {
UserSession session = SessionContext.getSession();
groupMemberService.setDnd(dto.getGroupId(), session.getUserId(), dto.getIsDnd());
// 推送同步消息
sendSyncDndMessage(dto.getGroupId(), dto.getIsDnd());
}
private void sendTipMessage(Long groupId, List<Long> recvIds, String content, Boolean sendToAll) {
UserSession session = SessionContext.getSession();
// 消息入库
@ -351,6 +361,7 @@ public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements
vo.setShowNickName(member.getShowNickName());
vo.setShowGroupName(StrUtil.blankToDefault(member.getRemarkGroupName(), group.getName()));
vo.setQuit(member.getQuit());
vo.setIsDnd(member.getIsDnd());
return vo;
}
@ -386,4 +397,20 @@ public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements
sendMessage.setSendToSelf(false);
imClient.sendGroupMessage(sendMessage);
}
private void sendSyncDndMessage(Long groupId, Boolean isDnd) {
UserSession session = SessionContext.getSession();
GroupMessageVO msgInfo = new GroupMessageVO();
msgInfo.setType(MessageType.GROUP_DND.code());
msgInfo.setSendTime(new Date());
msgInfo.setGroupId(groupId);
msgInfo.setSendId(session.getUserId());
msgInfo.setContent(isDnd.toString());
IMGroupMessage<GroupMessageVO> sendMessage = new IMGroupMessage<>();
sendMessage.setSender(new IMUserInfo(session.getUserId(), session.getTerminal()));
sendMessage.setData(msgInfo);
sendMessage.setSendResult(false);
imClient.sendGroupMessage(sendMessage);
}
}

4
im-platform/src/main/java/com/bx/implatform/vo/FriendVO.java

@ -20,6 +20,10 @@ public class FriendVO {
@Schema(description = "好友头像")
private String headImage;
@Schema(description = "是否开启免打扰")
private Boolean isDnd;
@Schema(description = "是否已删除")
private Boolean deleted;
}

3
im-platform/src/main/java/com/bx/implatform/vo/GroupVO.java

@ -56,5 +56,8 @@ public class GroupVO {
@Schema(description = "被封禁原因")
private String reason;
@Schema(description = "是否开启免打扰")
private Boolean isDnd;
}

31
im-uniapp/App.vue

@ -146,6 +146,12 @@ export default {
this.friendStore.removeFriend(friendId);
return;
}
//
if (msg.type == enums.MESSAGE_TYPE.FRIEND_DND) {
this.friendStore.setDnd(friendId, JSON.parse(msg.content));
this.chatStore.setDnd(chatInfo, JSON.parse(msg.content));
return;
}
//
let friend = this.loadFriendInfo(friendId);
this.insertPrivateMessage(friend, msg);
@ -183,14 +189,20 @@ export default {
type: 'PRIVATE',
targetId: friend.id,
showName: friend.nickName,
headImage: friend.headImage
headImage: friend.headImage,
isDnd: friend.isDnd
};
//
this.chatStore.openChat(chatInfo);
//
this.chatStore.insertMessage(msg, chatInfo);
//
this.playAudioTip();
this.chatStore.insertMessage(msg, chatInfo);
if (!friend.isDnd && !this.chatStore.isLoading() &&
!msg.selfSend && msgType.isNormal(msg.type) &&
msg.status != enums.MESSAGE_STATUS.READED) {
this.playAudioTip();
}
}
@ -240,6 +252,12 @@ export default {
this.groupStore.removeGroup(msg.groupId);
return;
}
//
if (msg.type == enums.MESSAGE_TYPE.GROUP_DND) {
this.groupStore.setDnd(msg.groupId, JSON.parse(msg.content));
this.chatStore.setDnd(chatInfo, JSON.parse(msg.content));
return;
}
//
let group = this.loadGroupInfo(msg.groupId);
this.insertGroupMessage(group, msg);
@ -291,14 +309,19 @@ export default {
type: 'GROUP',
targetId: group.id,
showName: group.showGroupName,
headImage: group.headImageThumb
headImage: group.headImageThumb,
isDnd: group.isDnd
};
//
this.chatStore.openChat(chatInfo);
//
this.chatStore.insertMessage(msg, chatInfo);
//
this.playAudioTip();
if (!group.isDnd && !this.chatStore.isLoading() &&
!msg.selfSend && msgType.isNormal(msg.type) &&
msg.status != enums.MESSAGE_STATUS.READED) {
this.playAudioTip();
}
}
},

4
im-uniapp/common/enums.js

@ -16,8 +16,10 @@ const MESSAGE_TYPE = {
USER_BANNED: 50,
FRIEND_NEW: 80,
FRIEND_DEL: 81,
FRIEND_DND: 82,
GROUP_NEW: 90,
GROUP_DEL: 91,
GROUP_DEL: 91,
GROUP_DND: 92,
RTC_CALL_VOICE: 100,
RTC_CALL_VIDEO: 101,
RTC_ACCEPT: 102,

10
im-uniapp/components/chat-item/chat-item.vue

@ -15,8 +15,10 @@
<view class="chat-content">
<view class="chat-at-text">{{ atText }}</view>
<view class="chat-send-name" v-if="isShowSendName">{{ chat.sendNickName + ':&nbsp;' }}</view>
<rich-text class="chat-content-text" :nodes="$emo.transform(chat.lastContent,'emoji-small')"></rich-text>
<rich-text class="chat-content-text"
:nodes="$emo.transform(chat.lastContent,'emoji-small')"></rich-text>
<uni-badge v-if="chat.unreadCount > 0" :max-num="99" :text="chat.unreadCount" />
<view v-if="chat.isDnd" class="icon iconfont icon-dnd"></view>
</view>
</view>
</view>
@ -43,7 +45,7 @@ export default {
methods: {
showChatBox() {
//
if(!getApp().$vm.isInit || this.chatStore.isLoading()){
if (!getApp().$vm.isInit || this.chatStore.isLoading()) {
uni.showToast({
title: "正在初始化页面,请稍后...",
icon: 'none'
@ -153,8 +155,8 @@ export default {
font-size: $im-font-size-smaller;
color: $im-text-color-lighter;
padding-top: 8rpx;
align-items: center;
align-items: center;
.chat-at-text {
color: $im-color-danger;
}

38
im-uniapp/im.scss

@ -102,7 +102,6 @@ button[size='mini'] {
}
}
.uni-radio-input svg{
border-color: white !important;
background-color: $im-color-primary !important;
@ -171,6 +170,26 @@ button[size='mini'] {
font-size: 10px !important;
font-weight: bolder !important;
}
.uni-switch-input-checked {
background-color: $im-color-primary-light-1 !important;
border-color: $im-color-primary-light-1 !important;
}
.uni-modal__title {
font-size: $im-font-size-larger !important;
}
.uni-modal__bd {
font-size: $im-font-size;
}
.uni-modal__ft {
font-size: $im-font-size;
line-height: 90rpx !important;
.uni-modal__btn_primary {
color: $im-color-primary !important;
}
}
.nav-bar {
height: 100rpx;
@ -214,4 +233,19 @@ button[size='mini'] {
width: 36rpx !important;
height: 36rpx !important;
vertical-align: bottom !important;
}
}
.none-pointer-events {
uni-image img {
// 阻止微信默认长按菜单
pointer-events: none;
-webkit-pointer-events: none;
-ms-pointer-events: none;
-moz-pointer-events: none;
}
}
p {
margin-block-start: 1em;
margin-block-end: 1em;
}

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

@ -133,8 +133,7 @@ export default {
keyboardHeight: 290, //
windowHeight: 1000, //
initHeight: 1000, // h5
atUserIds: [],
needScrollToBottom: false, //
atUserIds: [],
showMinIdx: 0, // showMinIdx
reqQueue: [], //
isSending: false, //
@ -576,7 +575,6 @@ export default {
}, 100)
},
onScrollToTop() {
console.log("onScrollToTop")
if (this.showMinIdx > 0) {
// #ifndef H5
//
@ -924,14 +922,17 @@ export default {
},
watch: {
messageSize: function(newSize, oldSize) {
//
if (newSize > oldSize) {
if (this.isInBottom) {
// ,
this.scrollToBottom();
} else {
//
this.newMessageSize++;
//
if (newSize > oldSize && oldSize > 0) {
let lastMessage = this.chat.messages[newSize - 1];
if (this.$msgType.isNormal(lastMessage.type)) {
if (this.isInBottom) {
// ,
this.scrollToBottom();
} else {
//
this.newMessageSize++;
}
}
}
},
@ -963,13 +964,16 @@ export default {
this.chatStore.activeChat(options.chatIdx);
//
this.isReceipt = false;
//
this.isInBottom = true;
this.newMessageSize = 0;
//
this.listenKeyBoard();
//
this.$nextTick(() => {
this.windowHeight = uni.getSystemInfoSync().windowHeight;
this.reCalChatMainHeight();
this.scrollToBottom();
// #ifdef H5
this.initHeight = window.innerHeight;
// iosh5:
@ -982,13 +986,6 @@ export default {
},
onUnload() {
this.unListenKeyboard();
},
onShow() {
if (this.needScrollToBottom) {
//
this.scrollToBottom();
this.needScrollToBottom = false;
}
}
}
</script>

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

@ -6,7 +6,7 @@
<view>消息接收中...</view>
</loading>
</view>
<view v-if="initializing" class="chat-loading">
<view v-else-if="initializing" class="chat-loading">
<loading :size="50" :mask="false">
<view>正在初始化...</view>
</loading>
@ -17,7 +17,7 @@
placeholder="搜索"></uni-search-bar>
</view>
</view>
<view class="chat-tip" v-if="!loading && chatStore.chats.length == 0">
<view class="chat-tip" v-if="!initializing && !loading && chatStore.chats.length == 0">
温馨提示您现在还没有任何聊天消息快跟您的好友发起聊天吧~
</view>
<scroll-view class="scroll-bar" v-else scroll-with-animation="true" scroll-y="true">

55
im-uniapp/pages/common/user-info.vue

@ -14,17 +14,23 @@
</view>
<view class="info-text">
<text class="label-text">用户名:</text>
<text class="content-text"> {{ userInfo.userName }}</text>
<text class="content-text"> {{ userInfo.userName }}</text>
</view>
<view class="info-text">
<view>
<text class="label-text">签名:</text>
<text class="content-text"> {{ userInfo.signature }} </text>
<text class="content-text"> {{ userInfo.signature }} </text>
</view>
</view>
</view>
</view>
</uni-card>
<bar-group v-if="isFriend">
<switch-bar title="消息免打扰" :checked="friendInfo.isDnd" @change="onDndChange"></switch-bar>
</bar-group>
<bar-group v-if="chatIdx>=0">
<arrow-bar title="清空聊天记录" @tap="onCleanMessage()"></arrow-bar>
</bar-group>
<bar-group>
<btn-bar v-show="isFriend" type="primary" title="发送消息" @tap="onSendMessage()">
</btn-bar>
@ -57,6 +63,9 @@ export default {
showName: this.userInfo.nickName,
headImage: this.userInfo.headImage,
};
if (this.isFriend) {
chat.isDnd = this.friendInfo.isDnd;
}
this.chatStore.openChat(chat);
let chatIdx = this.chatStore.findChatIdx(chat);
uni.navigateTo({
@ -103,6 +112,41 @@ export default {
}
})
},
onCleanMessage() {
uni.showModal({
title: '清空聊天记录',
content: `确认删除与'${this.userInfo.nickName}'的聊天记录吗?`,
confirmText: '确认',
success: (res) => {
if (res.cancel)
return;
this.chatStore.cleanMessage(this.chatIdx);
uni.showToast({
title: `您清空了'${this.userInfo.nickName}'的聊天记录`,
icon: 'none'
})
}
})
},
onDndChange(e) {
let isDnd = e.detail.value;
let friendId = this.userInfo.id;
let formData = {
friendId: friendId,
isDnd: isDnd
}
this.$http({
url: '/friend/dnd',
method: 'PUT',
data: formData
}).then(() => {
this.friendStore.setDnd(friendId, isDnd)
let chat = this.chatStore.findChatByFriend(friendId)
if (chat) {
this.chatStore.setDnd(chat, isDnd)
}
})
},
updateFriendInfo() {
if (this.isFriend) {
// storestore
@ -134,6 +178,13 @@ export default {
},
friendInfo() {
return this.friendStore.findFriend(this.userInfo.id);
},
chatIdx() {
let chat = this.chatStore.findChatByFriend(this.userInfo.id);
if (chat) {
return this.chatStore.findChatIdx(chat);
}
return -1;
}
},
onLoad(options) {

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

@ -52,6 +52,12 @@
</view>
<view v-if="!group.quit" class="group-edit" @click="onEditGroup()">修改群聊资料 > </view>
</view>
<bar-group v-if="!group.quit">
<switch-bar title="消息免打扰" :checked="group.isDnd" @change="onDndChange"></switch-bar>
</bar-group>
<bar-group v-if="!group.quit && chatIdx>=0">
<arrow-bar title="清空聊天记录" @tap="onCleanMessage()"></arrow-bar>
</bar-group>
<bar-group v-if="!group.quit">
<btn-bar type="primary" title="发送消息" @tap="onSendMessage()"></btn-bar>
<btn-bar v-if="!isOwner" type="danger" title="退出群聊" @tap="onQuitGroup()"></btn-bar>
@ -117,6 +123,7 @@ export default {
targetId: this.group.id,
showName: this.group.showGroupName,
headImage: this.group.headImage,
isDnd: this.group.isDnd
};
this.chatStore.openChat(chat);
let chatIdx = this.chatStore.findChatIdx(chat);
@ -178,6 +185,42 @@ export default {
}
});
},
onDndChange(e) {
let isDnd = e.detail.value;
let groupId = this.group.id;
let formData = {
groupId: groupId,
isDnd: isDnd
}
this.$http({
url: '/group/dnd',
method: 'PUT',
data: formData
}).then(() => {
this.groupStore.setDnd(groupId, isDnd);
let chat = this.chatStore.findChatByGroup(groupId);
if (chat) {
this.chatStore.setDnd(chat, isDnd)
}
})
},
onCleanMessage() {
uni.showModal({
title: '清空聊天记录',
content: `确认删除群聊'${this.group.name}'的聊天记录吗?`,
confirmText: '确认',
success: (res) => {
if (res.cancel) {
return;
}
this.chatStore.cleanMessage(this.chatIdx);
uni.showToast({
title: `您清空了'${this.group.name}'的聊天记录`,
icon: 'none'
})
}
})
},
loadGroupInfo() {
this.$http({
url: `/group/find/${this.groupId}`,
@ -210,6 +253,13 @@ export default {
},
showMaxIdx() {
return this.isOwner ? 8 : 9;
},
chatIdx() {
let chat = this.chatStore.findChatByGroup(this.groupId);
if (chat) {
return this.chatStore.findChatIdx(chat);
}
return -1;
}
},
onLoad(options) {

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

@ -1,6 +1,6 @@
@font-face {
font-family: "iconfont"; /* Project id 4272106 */
src: url('iconfont.ttf?t=1746119818070') format('truetype');
src: url('iconfont.ttf?t=1750317465456') format('truetype');
}
.iconfont {
@ -11,6 +11,34 @@
-moz-osx-font-smoothing: grayscale;
}
.icon-dnd:before {
content: "\e693";
}
.icon-privacy-protocol:before {
content: "\e761";
}
.icon-create-group-2:before {
content: "\e616";
}
.icon-create-group:before {
content: "\e650";
}
.icon-qrcode:before {
content: "\e642";
}
.icon-add-friend:before {
content: "\e64f";
}
.icon-scan:before {
content: "\e8b5";
}
.icon-remove:before {
content: "\e603";
}
@ -51,10 +79,6 @@
content: "\ec44";
}
.icon-privacy-protocol:before {
content: "\e70a";
}
.icon-un-register:before {
content: "\e656";
}

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

Binary file not shown.

46
im-uniapp/store/chatStore.js

@ -1,5 +1,7 @@
import { defineStore } from 'pinia';
import { MESSAGE_TYPE, MESSAGE_STATUS } from '@/common/enums.js';
import useFriendStore from './friendStore.js';
import useGroupStore from './groupStore.js';
import useUserStore from './userStore';
let cacheChats = [];
@ -56,6 +58,7 @@ export default defineStore('chatStore', {
type: chatInfo.type,
showName: chatInfo.showName,
headImage: chatInfo.headImage,
isDnd: chatInfo.isDnd,
lastContent: "",
lastSendTime: new Date().getTime(),
unreadCount: 0,
@ -105,6 +108,17 @@ export default defineStore('chatStore', {
this.saveToStorage();
}
},
cleanMessage(idx) {
let chat = this.curChats[idx];
chat.lastContent = '';
chat.hotMinIdx = 0;
chat.unreadCount = 0;
chat.atMe = false;
chat.atAll = false;
chat.stored = false
chat.messages = [];
this.saveToStorage(true);
},
removeChat(idx) {
let chats = this.curChats;
chats[idx].delete = true;
@ -181,7 +195,7 @@ export default defineStore('chatStore', {
chat.lastSendTime = msgInfo.sendTime;
chat.sendNickName = msgInfo.sendNickName;
// 未读加1
if (!msgInfo.selfSend && msgInfo.status != MESSAGE_STATUS.READED &&
if (!chat.isDnd && !msgInfo.selfSend && msgInfo.status != MESSAGE_STATUS.READED &&
msgInfo.status != MESSAGE_STATUS.RECALL && msgInfo.type != MESSAGE_TYPE.TIP_TEXT) {
chat.unreadCount++;
}
@ -336,8 +350,34 @@ export default defineStore('chatStore', {
this.refreshChats()
}
},
setDnd(chatInfo, isDnd) {
let chat = this.findChat(chatInfo);
if (chat) {
chat.isDnd = isDnd;
chat.unreadCount = 0;
}
},
refreshChats() {
if (!cacheChats) return;
// 更新会话免打扰状态
const friendStore = useFriendStore();
const groupStore = useGroupStore();
cacheChats.forEach(chat => {
if (chat.type == 'PRIVATE') {
let friend = friendStore.findFriend(chat.targetId);
if (friend) {
chat.isDnd = friend.isDnd
}
} else if (chat.type == 'GROUP') {
let group = groupStore.findGroup(chat.targetId);
if (group) {
chat.isDnd = group.isDnd
}
}
if (chat.isDnd) {
chat.unreadCount = 0;
}
})
// 排序
cacheChats.sort((chat1, chat2) => chat2.lastSendTime - chat1.lastSendTime);
// #ifndef APP-PLUS
@ -345,8 +385,8 @@ export default defineStore('chatStore', {
* 由于h5和小程序的stroge只有5m,大约只能存储2w条消息
* 所以这里每个会话只保留1000条消息防止溢出
*/
cacheChats.forEach(chat =>{
if(chat.messages.length > 1000){
cacheChats.forEach(chat => {
if (chat.messages.length > 1000) {
let idx = chat.messages.length - 1000;
chat.messages = chat.messages.slice(idx);
}

4
im-uniapp/store/friendStore.js

@ -67,6 +67,10 @@ export default defineStore('friendStore', {
this.refreshOnlineStatus();
}, 30000)
},
setDnd(id, isDnd) {
let friend = this.findFriend(id);
friend.isDnd = isDnd;
},
clear() {
clearTimeout(this.timer);
this.friends = [];

4
im-uniapp/store/groupStore.js

@ -25,6 +25,10 @@ export default defineStore('groupStore', {
let g = this.findGroup(group.id);
Object.assign(g, group);
},
setDnd(id, isDnd) {
let group = this.findGroup(id);
group.isDnd = isDnd;
},
clear() {
this.groups = [];
},

2
im-web/src/api/enums.js

@ -15,8 +15,10 @@ const MESSAGE_TYPE = {
USER_BANNED: 50,
FRIEND_NEW: 80,
FRIEND_DEL: 81,
FRIEND_DND: 82,
GROUP_NEW: 90,
GROUP_DEL: 91,
GROUP_DND: 92,
RTC_CALL_VOICE: 100,
RTC_CALL_VIDEO: 101,
RTC_ACCEPT: 102,

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

@ -1,6 +1,6 @@
@font-face {
font-family: "iconfont"; /* Project id 3791506 */
src: url('iconfont.ttf?t=1745933248800') format('truetype');
src: url('iconfont.ttf?t=1750245745055') format('truetype');
}
.iconfont {
@ -11,6 +11,30 @@
-moz-osx-font-smoothing: grayscale;
}
.icon-dnd:before {
content: "\e691";
}
.icon-screenshot:before {
content: "\e61c";
}
.icon-close:before {
content: "\e609";
}
.icon-minimize:before {
content: "\e650";
}
.icon-maximize:before {
content: "\e651";
}
.icon-unmaximize:before {
content: "\e611";
}
.icon-man:before {
content: "\e615";
}

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

Binary file not shown.

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

@ -9,14 +9,15 @@
<div class="chat-name">
<div class="chat-name-text">
<div>{{ chat.showName }}</div>
<el-tag v-if="chat.type == 'GROUP'" size="mini" ></el-tag>
<el-tag v-if="chat.type == 'GROUP'" size="mini"></el-tag>
</div>
<div class="chat-time-text">{{ showTime }}</div>
</div>
<div class="chat-content">
<div class="chat-at-text">{{ atText }}</div>
<div class="chat-send-name" v-show="isShowSendName">{{ chat.sendNickName + ':&nbsp;' }}</div>
<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 class="icon iconfont icon-dnd" v-if="chat.isDnd"></div>
</div>
</div>
<right-menu ref="rightMenu" @select="onSelectMenu"></right-menu>
@ -36,15 +37,6 @@ export default {
},
data() {
return {
menuItems: [{
key: 'TOP',
name: '置顶',
icon: 'el-icon-top'
}, {
key: 'DELETE',
name: '删除',
icon: 'el-icon-delete'
}]
}
},
props: {
@ -86,6 +78,30 @@ export default {
return "[@全体成员]"
}
return "";
},
menuItems() {
let items = [];
items.push({
key: 'TOP',
name: '置顶'
});
if (this.chat.isDnd) {
items.push({
key: 'DND',
name: '新消息提醒'
})
} else {
items.push({
key: 'DND',
name: '消息免打扰'
})
}
items.push({
key: 'DELETE',
name: '删除聊天',
color: '#F56C6C'
})
return items;
}
}
}
@ -185,7 +201,7 @@ export default {
font-size: var(--im-font-size-small);
color: var(--im-text-color-light);
}
.chat-content-text {
flex: 1;
white-space: nowrap;
@ -195,6 +211,9 @@ export default {
color: var(--im-text-color-light);
}
.icon {
color: var(--im-text-color-light);
}
}
}
}

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

@ -65,8 +65,11 @@ export default {
type: 'PRIVATE',
targetId: user.id,
showName: user.nickName,
headImage: user.headImage,
headImage: user.headImage
};
if (this.isFriend) {
chat.isDnd = this.friendInfo.isDnd;
}
this.chatStore.openChat(chat);
this.chatStore.setActiveChat(0);
if (this.$route.path != "/home/chat") {
@ -102,6 +105,9 @@ export default {
computed: {
isFriend() {
return this.friendStore.isFriend(this.user.id);
},
friendInfo() {
return this.friendStore.findFriend(this.user.id);
}
}
}

31
im-web/src/store/chatStore.js

@ -1,5 +1,7 @@
import { defineStore } from 'pinia';
import { MESSAGE_TYPE, MESSAGE_STATUS } from "../api/enums.js"
import useFriendStore from './friendStore.js';
import useGroupStore from './groupStore.js';
import useUserStore from './userStore.js';
import localForage from 'localforage';
@ -66,6 +68,7 @@ export default defineStore('chatStore', {
type: chatInfo.type,
showName: chatInfo.showName,
headImage: chatInfo.headImage,
isDnd: chatInfo.isDnd,
lastContent: "",
lastSendTime: new Date().getTime(),
unreadCount: 0,
@ -193,7 +196,7 @@ export default defineStore('chatStore', {
chat.lastSendTime = msgInfo.sendTime;
chat.sendNickName = msgInfo.sendNickName;
// 未读加1
if (!msgInfo.selfSend && msgInfo.status != MESSAGE_STATUS.READED &&
if (!chat.isDnd && !msgInfo.selfSend && msgInfo.status != MESSAGE_STATUS.READED &&
msgInfo.status != MESSAGE_STATUS.RECALL && msgInfo.type != MESSAGE_TYPE.TIP_TEXT) {
chat.unreadCount++;
}
@ -343,8 +346,34 @@ export default defineStore('chatStore', {
this.refreshChats();
}
},
setDnd(chatInfo, isDnd) {
let chat = this.findChat(chatInfo);
if (chat) {
chat.isDnd = isDnd;
chat.unreadCount = 0;
}
},
refreshChats() {
if (!cacheChats) return;
// 刷新免打扰状态
const friendStore = useFriendStore();
const groupStore = useGroupStore();
cacheChats.forEach(chat => {
if (chat.type == 'PRIVATE') {
let friend = friendStore.findFriend(chat.targetId);
if (friend) {
chat.isDnd = friend.isDnd
}
} else if (chat.type == 'GROUP') {
let group = groupStore.findGroup(chat.targetId);
if (group) {
chat.isDnd = group.isDnd
}
}
if (chat.isDnd) {
chat.unreadCount = 0;
}
})
// 排序
cacheChats.sort((chat1, chat2) => chat2.lastSendTime - chat1.lastSendTime);
// 记录热数据索引位置

4
im-web/src/store/friendStore.js

@ -42,6 +42,10 @@ export default defineStore('friendStore', {
}
friend.online = friend.onlineWeb || friend.onlineApp;
},
setDnd(id, isDnd) {
let friend = this.findFriend(id);
friend.isDnd = isDnd;
},
clear() {
this.timer && clearTimeout(this.timer);
this.friends = [];

4
im-web/src/store/groupStore.js

@ -35,6 +35,10 @@ export default defineStore('groupStore', {
group.topMessage = topMessage;
}
},
setDnd(id, isDnd) {
let group = this.findGroup(id);
group.isDnd = isDnd;
},
clear() {
this.groups = [];
},

37
im-web/src/view/Chat.vue

@ -13,7 +13,7 @@
<div v-for="(chat, index) in chatStore.chats" :key="index">
<chat-item v-show="!chat.delete && chat.showName && chat.showName.includes(searchText)" :chat="chat"
:index="index" @click.native="onActiveItem(index)" @delete="onDelItem(index)" @top="onTop(index)"
:active="chat === chatStore.activeChat"></chat-item>
@dnd="onDnd(chat)" :active="chat === chatStore.activeChat"></chat-item>
</div>
</el-scrollbar>
</el-aside>
@ -51,6 +51,41 @@ export default {
onTop(chatIdx) {
this.chatStore.moveTop(chatIdx);
},
onDnd(chat) {
if (chat.type == 'PRIVATE') {
this.setFriendDnd(chat, chat.targetId, !chat.isDnd)
} else {
this.setGroupDnd(chat, chat.targetId, !chat.isDnd)
}
},
setFriendDnd(chat, friendId, isDnd) {
let formData = {
friendId: friendId,
isDnd: isDnd
}
this.$http({
url: '/friend/dnd',
method: 'put',
data: formData
}).then(() => {
this.friendStore.setDnd(friendId, isDnd)
this.chatStore.setDnd(chat, isDnd)
})
},
setGroupDnd(chat, groupId, isDnd) {
let formData = {
groupId: groupId,
isDnd: isDnd
}
this.$http({
url: '/group/dnd',
method: 'put',
data: formData
}).then(() => {
this.groupStore.setDnd(groupId, isDnd)
this.chatStore.setDnd(chat, isDnd)
})
}
},
computed: {
loading() {

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

@ -44,7 +44,7 @@
</div>
<div class="btn-group">
<el-button v-show="isFriend" icon="el-icon-position" type="primary"
@click="onSendMessage(userInfo)">发消息</el-button>
@click="onSendMessage(activeFriend)">发消息</el-button>
<el-button v-show="!isFriend" icon="el-icon-plus" type="primary"
@click="onAddFriend(userInfo)">加为好友</el-button>
<el-button v-show="isFriend" icon="el-icon-delete" type="danger"
@ -124,12 +124,13 @@ export default {
this.friendStore.addFriend(friend);
})
},
onSendMessage(user) {
onSendMessage(friend) {
let chat = {
type: 'PRIVATE',
targetId: user.id,
showName: user.nickName,
headImage: user.headImageThumb,
targetId: friend.id,
showName: friend.nickName,
headImage: friend.headImage,
isDnd: friend.isDnd
};
this.chatStore.openChat(chat);
this.chatStore.setActiveChat(0);

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

@ -241,6 +241,7 @@ export default {
targetId: this.activeGroup.id,
showName: this.activeGroup.showGroupName,
headImage: this.activeGroup.headImage,
isDnd: this.activeGroup.isDnd
};
this.chatStore.openChat(chat);
this.chatStore.setActiveChat(0);

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

@ -251,6 +251,12 @@ export default {
this.friendStore.removeFriend(friendId);
return;
}
//
if (msg.type == this.$enums.MESSAGE_TYPE.FRIEND_DND) {
this.friendStore.setDnd(friendId, JSON.parse(msg.content));
this.chatStore.setDnd(chatInfo, JSON.parse(msg.content));
return;
}
// webrtc
if (this.$msgType.isRtcPrivate(msg.type)) {
this.$refs.rtcPrivateVideo.onRTCMessage(msg)
@ -267,14 +273,15 @@ export default {
type: 'PRIVATE',
targetId: friend.id,
showName: friend.nickName,
headImage: friend.headImage
headImage: friend.headImage,
isDnd: friend.isDnd
};
//
this.chatStore.openChat(chatInfo);
//
this.chatStore.insertMessage(msg, chatInfo);
//
if (!msg.selfSend && this.$msgType.isNormal(msg.type) &&
if (!friend.isDnd && !this.chatStore.isLoading() && !msg.selfSend && this.$msgType.isNormal(msg.type) &&
msg.status != this.$enums.MESSAGE_STATUS.READED) {
this.playAudioTip();
}
@ -324,6 +331,12 @@ export default {
this.groupStore.removeGroup(msg.groupId);
return;
}
//
if (msg.type == this.$enums.MESSAGE_TYPE.GROUP_DND) {
this.groupStore.setDnd(msg.groupId, JSON.parse(msg.content));
this.chatStore.setDnd(chatInfo, JSON.parse(msg.content));
return;
}
//
if (this.$msgType.isRtcGroup(msg.type)) {
this.$nextTick(() => {
@ -342,15 +355,17 @@ export default {
type: 'GROUP',
targetId: group.id,
showName: group.showGroupName,
headImage: group.headImageThumb
headImage: group.headImageThumb,
isDnd: group.isDnd
};
//
this.chatStore.openChat(chatInfo);
//
this.chatStore.insertMessage(msg, chatInfo);
//
if (!msg.selfSend && msg.type <= this.$enums.MESSAGE_TYPE.VIDEO &&
msg.status != this.$enums.MESSAGE_STATUS.READED) {
if (!group.isDnd && !this.chatStore.isLoading() &&
!msg.selfSend && this.$msgType.isNormal(msg.type)
&& msg.status != this.$enums.MESSAGE_STATUS.READED) {
this.playAudioTip();
}
},

Loading…
Cancel
Save