Browse Source

feat: 消息免打扰

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

+ 2 - 0
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 - 0
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 - 0
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 - 0
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 - 0
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 - 0
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 - 0
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 - 0
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 - 0
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 - 0
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 - 0
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 - 0
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 - 0
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 - 0
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 - 0
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 - 0
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;
+
 
 }

+ 27 - 4
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();
+				}
 			}
 
 		},

+ 3 - 1
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,

+ 6 - 4
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;
 			}

+ 36 - 2
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;
+}

+ 16 - 19
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;
 			// 兼容ios的h5:禁止页面滚动
@@ -982,13 +986,6 @@ export default {
 	},
 	onUnload() {
 		this.unListenKeyboard();
-	},
-	onShow() {
-		if (this.needScrollToBottom) {
-			// 页面滚到底部
-			this.scrollToBottom();
-			this.needScrollToBottom = false;
-		}
 	}
 }
 </script>

+ 2 - 2
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">

+ 53 - 2
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) {
 				// store的数据不能直接修改,深拷贝一份store的数据
@@ -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 - 0
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) {

+ 29 - 5
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


+ 43 - 3
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 - 0
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 - 0
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 - 0
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,

+ 25 - 1
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


+ 31 - 12
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);
+			}
 		}
 	}
 }

+ 7 - 1
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);
 		}
 	}
 }

+ 30 - 1
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 - 0
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 - 0
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 = [];
 		},

+ 36 - 1
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() {

+ 6 - 5
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 - 0
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);

+ 20 - 5
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();
 			}
 		},