From 2eed546b74ff659b4440439d26508ee1d4cb3fe5 Mon Sep 17 00:00:00 2001 From: Blue <825657193@qq.com> Date: Wed, 24 Jan 2024 00:43:25 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=AF=E6=8C=81=E7=BE=A4=E8=81=8A=E5=9B=9E?= =?UTF-8?q?=E6=89=A7=E6=B6=88=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/GroupMessageController.java | 5 + .../bx/implatform/dto/GroupMessageDTO.java | 3 + .../bx/implatform/entity/GroupMessage.java | 10 +- .../com/bx/implatform/enums/MessageType.java | 4 + .../service/IGroupMessageService.java | 7 + .../service/impl/GroupMemberServiceImpl.java | 3 +- .../service/impl/GroupMessageServiceImpl.java | 136 +- .../service/impl/GroupServiceImpl.java | 13 +- .../com/bx/implatform/vo/GroupMessageVO.java | 7 + im-platform/src/main/resources/db/db.sql | 5 +- im-ui/src/api/enums.js | 1 + im-ui/src/api/wssocket.js | 1 - im-ui/src/assets/iconfont/iconfont.css | 12 +- im-ui/src/assets/iconfont/iconfont.ttf | Bin 3928 -> 4364 bytes im-ui/src/assets/iconfont/iconfont.woff | Bin 2572 -> 0 bytes im-ui/src/assets/iconfont/iconfont.woff2 | Bin 2096 -> 0 bytes im-ui/src/components/chat/ChatAtBox.vue | 55 +- im-ui/src/components/chat/ChatBox.vue | 1457 +++++++++-------- im-ui/src/components/chat/ChatGroupMember.vue | 67 + im-ui/src/components/chat/ChatGroupReaded.vue | 186 +++ im-ui/src/components/chat/ChatMessageItem.vue | 659 ++++---- .../src/components/chat/ChatPrivateVideo.vue | 1 - im-ui/src/components/chat/ChatVoice.vue | 3 - im-ui/src/components/group/AddGroupMember.vue | 1 - im-ui/src/store/chatStore.js | 115 +- im-ui/src/store/friendStore.js | 1 - im-ui/src/store/groupStore.js | 1 - im-ui/src/store/index.js | 1 - im-ui/src/view/Home.vue | 634 +++---- im-uniapp/App.vue | 12 +- im-uniapp/common/enums.js | 1 + .../chat-group-readed/chat-group-readed.vue | 131 ++ .../chat-message-item/chat-message-item.vue | 24 +- im-uniapp/pages/chat/chat-box.vue | 36 +- im-uniapp/static/icon/iconfont.css | 10 +- im-uniapp/static/icon/iconfont.ttf | Bin 5644 -> 6048 bytes im-uniapp/store/chatStore.js | 106 +- 37 files changed, 2153 insertions(+), 1555 deletions(-) delete mode 100644 im-ui/src/assets/iconfont/iconfont.woff delete mode 100644 im-ui/src/assets/iconfont/iconfont.woff2 create mode 100644 im-ui/src/components/chat/ChatGroupMember.vue create mode 100644 im-ui/src/components/chat/ChatGroupReaded.vue create mode 100644 im-uniapp/components/chat-group-readed/chat-group-readed.vue diff --git a/im-platform/src/main/java/com/bx/implatform/controller/GroupMessageController.java b/im-platform/src/main/java/com/bx/implatform/controller/GroupMessageController.java index 250c249..147e2e9 100644 --- a/im-platform/src/main/java/com/bx/implatform/controller/GroupMessageController.java +++ b/im-platform/src/main/java/com/bx/implatform/controller/GroupMessageController.java @@ -50,6 +50,11 @@ public class GroupMessageController { return ResultUtils.success(); } + @GetMapping("/findReadedUsers") + @ApiOperation(value = "获取已读用户id", notes = "获取消息已读用户列表") + public Result> findReadedUsers(@RequestParam Long groupId,@RequestParam Long messageId) { + return ResultUtils.success(groupMessageService.findReadedUsers(groupId,messageId)); + } @GetMapping("/history") @ApiOperation(value = "查询聊天记录", notes = "查询聊天记录") diff --git a/im-platform/src/main/java/com/bx/implatform/dto/GroupMessageDTO.java b/im-platform/src/main/java/com/bx/implatform/dto/GroupMessageDTO.java index 556e665..6af8f38 100644 --- a/im-platform/src/main/java/com/bx/implatform/dto/GroupMessageDTO.java +++ b/im-platform/src/main/java/com/bx/implatform/dto/GroupMessageDTO.java @@ -27,6 +27,9 @@ public class GroupMessageDTO { @ApiModelProperty(value = "消息类型") private Integer type; + @ApiModelProperty(value = "是否回执消息") + private Boolean receipt = false; + @Size(max = 20, message = "一次最多只能@20个小伙伴哦") @ApiModelProperty(value = "被@用户列表") private List atUserIds; diff --git a/im-platform/src/main/java/com/bx/implatform/entity/GroupMessage.java b/im-platform/src/main/java/com/bx/implatform/entity/GroupMessage.java index fa5fb28..870f1a2 100644 --- a/im-platform/src/main/java/com/bx/implatform/entity/GroupMessage.java +++ b/im-platform/src/main/java/com/bx/implatform/entity/GroupMessage.java @@ -62,13 +62,19 @@ public class GroupMessage extends Model { private String content; /** - * 消息类型 0:文字 1:图片 2:文件 + * 消息类型 MessageType */ @TableField("type") private Integer type; /** - * 状态 + * 是否回执消息 + */ + @TableField("receipt") + private Boolean receipt; + + /** + * 状态 MessageStatus */ @TableField("status") private Integer status; diff --git a/im-platform/src/main/java/com/bx/implatform/enums/MessageType.java b/im-platform/src/main/java/com/bx/implatform/enums/MessageType.java index 38bb729..69c4e06 100644 --- a/im-platform/src/main/java/com/bx/implatform/enums/MessageType.java +++ b/im-platform/src/main/java/com/bx/implatform/enums/MessageType.java @@ -34,6 +34,10 @@ public enum MessageType { */ READED(11, "已读"), + /** + * 消息已读回执(更新已读数量) + */ + RECEIPT(12, "消息已读回执"), /** * 呼叫 */ diff --git a/im-platform/src/main/java/com/bx/implatform/service/IGroupMessageService.java b/im-platform/src/main/java/com/bx/implatform/service/IGroupMessageService.java index 51f0075..39494d4 100644 --- a/im-platform/src/main/java/com/bx/implatform/service/IGroupMessageService.java +++ b/im-platform/src/main/java/com/bx/implatform/service/IGroupMessageService.java @@ -39,6 +39,13 @@ public interface IGroupMessageService extends IService { */ void readedMessage(Long groupId); + /** + * 查询群里消息已读用户id列表 + * @param groupId 群里id + * @param messageId 消息id + * @return 已读用户id集合 + */ + List findReadedUsers(Long groupId,Long messageId); /** * 拉取历史聊天记录 * diff --git a/im-platform/src/main/java/com/bx/implatform/service/impl/GroupMemberServiceImpl.java b/im-platform/src/main/java/com/bx/implatform/service/impl/GroupMemberServiceImpl.java index 4710b7f..da0eedf 100644 --- a/im-platform/src/main/java/com/bx/implatform/service/impl/GroupMemberServiceImpl.java +++ b/im-platform/src/main/java/com/bx/implatform/service/impl/GroupMemberServiceImpl.java @@ -61,7 +61,8 @@ public class GroupMemberServiceImpl extends ServiceImpl findUserIdsByGroupId(Long groupId) { LambdaQueryWrapper memberWrapper = Wrappers.lambdaQuery(); memberWrapper.eq(GroupMember::getGroupId, groupId) - .eq(GroupMember::getQuit, false); + .eq(GroupMember::getQuit, false) + .select(GroupMember::getUserId); List members = this.list(memberWrapper); return members.stream().map(GroupMember::getUserId).collect(Collectors.toList()); } diff --git a/im-platform/src/main/java/com/bx/implatform/service/impl/GroupMessageServiceImpl.java b/im-platform/src/main/java/com/bx/implatform/service/impl/GroupMessageServiceImpl.java index 0a2edfd..d893c1e 100644 --- a/im-platform/src/main/java/com/bx/implatform/service/impl/GroupMessageServiceImpl.java +++ b/im-platform/src/main/java/com/bx/implatform/service/impl/GroupMessageServiceImpl.java @@ -152,49 +152,48 @@ public class GroupMessageServiceImpl extends ServiceImpl(); } Map groupMemberMap = CollStreamUtil.toIdentityMap(members, GroupMember::getGroupId); - Set ids = groupMemberMap.keySet(); + Set groupIds = groupMemberMap.keySet(); // 只能拉取最近1个月的 Date minDate = DateUtils.addMonths(new Date(), -1); LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); - wrapper.gt(GroupMessage::getId, minId).gt(GroupMessage::getSendTime, minDate).in(GroupMessage::getGroupId, ids) + wrapper.gt(GroupMessage::getId, minId).gt(GroupMessage::getSendTime, minDate).in(GroupMessage::getGroupId, groupIds) .ne(GroupMessage::getStatus, MessageStatus.RECALL.code()).orderByAsc(GroupMessage::getId).last("limit 100"); List messages = this.list(wrapper); // 转成vo List vos = messages.stream() - .filter(m -> { - //排除加群之前的消息 - GroupMember member = groupMemberMap.get(m.getGroupId()); - return Objects.nonNull(member) && DateUtil.compare(member.getCreatedTime(), m.getSendTime()) <= 0; - }) - .map(m -> { - GroupMessageVO vo = BeanUtils.copyProperties(m, GroupMessageVO.class); - // 被@用户列表 - if (StringUtils.isNotBlank(m.getAtUserIds()) && Objects.nonNull(vo)) { - List atIds = Splitter.on(",").trimResults().splitToList(m.getAtUserIds()); - vo.setAtUserIds(atIds.stream().map(Long::parseLong).collect(Collectors.toList())); - } - return vo; - }).collect(Collectors.toList()); - // 消息状态,数据库没有存群聊的消息状态,需要从redis取 - List keys = ids.stream().map(id -> String.join(":", RedisKey.IM_GROUP_READED_POSITION, id.toString(), session.getUserId().toString())) - .collect(Collectors.toList()); - List sendPos = redisTemplate.opsForValue().multiGet(keys); - int idx = 0; - for (Long id : ids) { - Object o = sendPos.get(idx); - Integer sendMaxId = Objects.isNull(o) ? -1 : (Integer) o; - vos.stream().filter(vo -> vo.getGroupId().equals(id)).forEach(vo -> { - if (vo.getId() <= sendMaxId) { - // 已读 - vo.setStatus(MessageStatus.READED.code()); - } else { - // 未推送 - vo.setStatus(MessageStatus.UNSEND.code()); - } - }); - idx++; - } + .filter(m -> { + //排除加群之前的消息 + GroupMember member = groupMemberMap.get(m.getGroupId()); + return Objects.nonNull(member) && DateUtil.compare(member.getCreatedTime(), m.getSendTime()) <= 0; + }) + .map(m -> { + GroupMessageVO vo = BeanUtils.copyProperties(m, GroupMessageVO.class); + // 被@用户列表 + if (StringUtils.isNotBlank(m.getAtUserIds()) && Objects.nonNull(vo)) { + List atIds = Splitter.on(",").trimResults().splitToList(m.getAtUserIds()); + vo.setAtUserIds(atIds.stream().map(Long::parseLong).collect(Collectors.toList())); + } + return vo; + }).collect(Collectors.toList()); + // 通过群聊对消息进行分组 + Map> messageGroupMap = vos.stream().collect(Collectors.groupingBy(GroupMessageVO::getGroupId)); + messageGroupMap.forEach((groupId, messageVos) -> { + // 填充消息状态 + String key = StrUtil.join(":", RedisKey.IM_GROUP_READED_POSITION, groupId); + Object o = redisTemplate.opsForHash().get(key, session.getUserId().toString()); + long readedMaxId = Objects.isNull(o) ? -1 : Long.parseLong(o.toString()); + messageVos.forEach(messageVo -> messageVo.setStatus(readedMaxId >= messageVo.getId() ? MessageStatus.READED.code() : MessageStatus.UNSEND.code())); + // 针对回执消息填充已读人数 + List receiptMessageVos = messageVos.stream().filter(GroupMessageVO::getReceipt).collect(Collectors.toList()); + if (!receiptMessageVos.isEmpty()) { + Map maxIdMap = redisTemplate.opsForHash().entries(key); + receiptMessageVos.forEach(receiptMessageVo -> { + int count = getReadedUserIds(maxIdMap, receiptMessageVo.getId(),receiptMessageVo.getSendId()).size(); + receiptMessageVo.setReadedCount(count); + }); + } + }); return vos; } @@ -203,12 +202,15 @@ public class GroupMessageServiceImpl extends ServiceImpl wrapper = Wrappers.lambdaQuery(); - wrapper.eq(GroupMessage::getGroupId, groupId).orderByDesc(GroupMessage::getId).last("limit 1").select(GroupMessage::getId); + wrapper.eq(GroupMessage::getGroupId, groupId) + .orderByDesc(GroupMessage::getId) + .last("limit 1") + .select(GroupMessage::getId); GroupMessage message = this.getOne(wrapper); if (Objects.isNull(message)) { return; } - // 推送消息给自己的其他终端 + // 推送消息给自己的其他终端,同步清空会话列表中的未读数量 GroupMessageVO msgInfo = new GroupMessageVO(); msgInfo.setType(MessageType.READED.code()); msgInfo.setSendTime(new Date()); @@ -220,10 +222,52 @@ public class GroupMessageServiceImpl extends ServiceImpl receiptMessages = this.list(wrapper); + if (CollectionUtil.isNotEmpty(receiptMessages)) { + List userIds = groupMemberService.findUserIdsByGroupId(groupId); + Map maxIdMap = redisTemplate.opsForHash().entries(key); + for (GroupMessage receiptMessage : receiptMessages) { + Integer readedCount = getReadedUserIds(maxIdMap, receiptMessage.getId(),receiptMessage.getSendId()).size(); + msgInfo = new GroupMessageVO(); + msgInfo.setId(receiptMessage.getId()); + msgInfo.setGroupId(groupId); + msgInfo.setReadedCount(readedCount); + msgInfo.setType(MessageType.RECEIPT.code());; + sendMessage = new IMGroupMessage<>(); + sendMessage.setSender(new IMUserInfo(session.getUserId(), session.getTerminal())); + sendMessage.setRecvIds(userIds); + sendMessage.setData(msgInfo); + sendMessage.setSendToSelf(false); + sendMessage.setSendResult(false); + imClient.sendGroupMessage(sendMessage); + } + } + } + @Override + public List findReadedUsers(Long groupId, Long messageId) { + GroupMessage message = this.getById(messageId); + if (Objects.isNull(message)) { + throw new GlobalException(ResultCode.PROGRAM_ERROR, "消息不存在"); + } + // 已读位置key + String key = StrUtil.join(":", RedisKey.IM_GROUP_READED_POSITION, groupId); + // 一次获取所有用户的已读位置 + Map maxIdMap = redisTemplate.opsForHash().entries(key); + // 返回已读用户的id集合 + return getReadedUserIds(maxIdMap, message.getId(),message.getSendId()); } @Override @@ -249,4 +293,18 @@ public class GroupMessageServiceImpl extends ServiceImpl getReadedUserIds(Map maxIdMap, Long messageId, Long sendId) { + List userIds = new LinkedList<>(); + maxIdMap.forEach((k, v) -> { + Long userId = Long.valueOf(k.toString()); + Long maxId = Long.valueOf(v.toString()); + // 发送者不计入已读人数 + if (!sendId.equals(userId) && maxId >= messageId) { + userIds.add(userId); + } + }); + return userIds; + } + + } diff --git a/im-platform/src/main/java/com/bx/implatform/service/impl/GroupServiceImpl.java b/im-platform/src/main/java/com/bx/implatform/service/impl/GroupServiceImpl.java index 1639dec..58de782 100644 --- a/im-platform/src/main/java/com/bx/implatform/service/impl/GroupServiceImpl.java +++ b/im-platform/src/main/java/com/bx/implatform/service/impl/GroupServiceImpl.java @@ -1,5 +1,6 @@ package com.bx.implatform.service.impl; +import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; @@ -29,6 +30,7 @@ import org.apache.commons.lang3.StringUtils; import org.springframework.cache.annotation.CacheConfig; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; +import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -44,7 +46,7 @@ public class GroupServiceImpl extends ServiceImpl implements private final IGroupMemberService groupMemberService; private final IFriendService friendsService; private final IMClient imClient; - + private final RedisTemplate redisTemplate; @Override public GroupVO createGroup(GroupVO vo) { UserSession session = SessionContext.getSession(); @@ -107,6 +109,9 @@ public class GroupServiceImpl extends ServiceImpl implements this.updateById(group); // 删除成员数据 groupMemberService.removeByGroupId(groupId); + // 清理已读缓存 + String key = StrUtil.join(":", RedisKey.IM_GROUP_READED_POSITION, groupId); + redisTemplate.delete(key); log.info("删除群聊,群聊id:{},群聊名称:{}", group.getId(), group.getName()); } @@ -119,6 +124,9 @@ public class GroupServiceImpl extends ServiceImpl implements } // 删除群聊成员 groupMemberService.removeByGroupAndUserId(groupId, userId); + // 清理已读缓存 + String key = StrUtil.join(":", RedisKey.IM_GROUP_READED_POSITION, groupId); + redisTemplate.opsForHash().delete(key,userId.toString()); log.info("退出群聊,群聊id:{},群聊名称:{},用户id:{}", group.getId(), group.getName(), userId); } @@ -134,6 +142,9 @@ public class GroupServiceImpl extends ServiceImpl implements } // 删除群聊成员 groupMemberService.removeByGroupAndUserId(groupId, userId); + // 清理已读缓存 + String key = StrUtil.join(":", RedisKey.IM_GROUP_READED_POSITION, groupId); + redisTemplate.opsForHash().delete(key,userId.toString()); log.info("踢出群聊,群聊id:{},群聊名称:{},用户id:{}", group.getId(), group.getName(), userId); } diff --git a/im-platform/src/main/java/com/bx/implatform/vo/GroupMessageVO.java b/im-platform/src/main/java/com/bx/implatform/vo/GroupMessageVO.java index 1f5beae..f7deb0b 100644 --- a/im-platform/src/main/java/com/bx/implatform/vo/GroupMessageVO.java +++ b/im-platform/src/main/java/com/bx/implatform/vo/GroupMessageVO.java @@ -3,6 +3,7 @@ package com.bx.implatform.vo; import com.bx.imcommon.serializer.DateToLongSerializer; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import io.swagger.annotations.ApiModelProperty; +import io.swagger.models.auth.In; import lombok.Data; import java.util.Date; @@ -29,6 +30,12 @@ public class GroupMessageVO { @ApiModelProperty(value = "消息内容类型 具体枚举值由应用层定义") private Integer type; + @ApiModelProperty(value = "是否回执消息") + private Boolean receipt; + + @ApiModelProperty(value = "已读消息数量") + private Integer readedCount = 0; + @ApiModelProperty(value = "@用户列表") private List atUserIds; diff --git a/im-platform/src/main/resources/db/db.sql b/im-platform/src/main/resources/db/db.sql index ce67f13..a724696 100644 --- a/im-platform/src/main/resources/db/db.sql +++ b/im-platform/src/main/resources/db/db.sql @@ -70,8 +70,9 @@ create table `im_group_message`( `send_nick_name` varchar(255) DEFAULT '' comment '发送用户昵称', `content` text comment '发送内容', `at_user_ids` varchar(1024) comment '被@的用户id列表,逗号分隔', - `type` tinyint(1) NOT NULL comment '消息类型 0:文字 1:图片 2:文件 3:语音 10:系统提示' , - `status` tinyint(1) DEFAULT 0 comment '状态 0:正常 2:撤回', + `receipt` tinyint DEFAULT 0 comment '是否回执消息', + `type` tinyint(1) NOT NULL comment '消息类型 0:文字 1:图片 2:文件 3:语音 4:视频 10:系统提示' , + `status` tinyint(1) DEFAULT 0 comment '状态 0:未发出 1:已送达 2:撤回 3:已读', `send_time` datetime DEFAULT CURRENT_TIMESTAMP comment '发送时间', key `idx_group_id` (group_id) )ENGINE=InnoDB CHARSET=utf8mb3 comment '群消息'; diff --git a/im-ui/src/api/enums.js b/im-ui/src/api/enums.js index d5f2390..42bcbf0 100644 --- a/im-ui/src/api/enums.js +++ b/im-ui/src/api/enums.js @@ -7,6 +7,7 @@ const MESSAGE_TYPE = { VIDEO:4, RECALL:10, READED:11, + RECEIPT:12, TIP_TIME:20, RTC_CALL: 101, RTC_ACCEPT: 102, diff --git a/im-ui/src/api/wssocket.js b/im-ui/src/api/wssocket.js index 2cb4198..9842697 100644 --- a/im-ui/src/api/wssocket.js +++ b/im-ui/src/api/wssocket.js @@ -105,7 +105,6 @@ let heartCheck = { // 实际调用的方法 let sendMessage = (agentData) => { - // console.log(globalCallback) if (websock.readyState === websock.OPEN) { // 若是ws开启状态 websock.send(JSON.stringify(agentData)) diff --git a/im-ui/src/assets/iconfont/iconfont.css b/im-ui/src/assets/iconfont/iconfont.css index 0b6840e..7092657 100644 --- a/im-ui/src/assets/iconfont/iconfont.css +++ b/im-ui/src/assets/iconfont/iconfont.css @@ -1,8 +1,6 @@ @font-face { font-family: "iconfont"; /* Project id 3791506 */ - src: url('iconfont.woff2?t=1669336625993') format('woff2'), - url('iconfont.woff?t=1669336625993') format('woff'), - url('iconfont.ttf?t=1669336625993') format('truetype'); + src: url('iconfont.ttf?t=1706022894868') format('truetype'); } .iconfont { @@ -13,6 +11,14 @@ -moz-osx-font-smoothing: grayscale; } +.icon-ok:before { + content: "\e6ac"; +} + +.icon-receipt:before { + content: "\e61a"; +} + .icon-biaoqing:before { content: "\e60c"; } diff --git a/im-ui/src/assets/iconfont/iconfont.ttf b/im-ui/src/assets/iconfont/iconfont.ttf index 94501384b881a8ef97415ac0675238b5cecd9c0f..79f6e9c8f2b217df04a92e4733cde861bfafd3ee 100644 GIT binary patch delta 964 zcmZutTSydP6h7ylab|bi*$ZyF+ic9{>a5T%t9hAGvI|ixY6W2=J;h>Ps#VtuVOVT| zNQgzYz4TIPFFp7W1>u9(g`V;yf-Wyby^IPG5hfwH+ZpGf9{Pvx|Ihi(^_!3CRP<&| z?#H9Ic>uP9aHJ=5vAflq{Q|&`130hrcBRk6UY}S30OCOJB|`knT_<~iP}$ozGFn3h z0DhyZCNt2P_AU+so`qHCWtbjWd50SsKKi${mebD>}z|GUVp6tM|Jo2gI_6GoO zh7#^hFI4Pz0g_1=HuoMl?P>l6rIbwP>$_|Hf8XL14_r6-4qz5b$EtJXRb0Qt9C<%0 zJ37Bb0Xzn7%SzBIQQkc8FhR*7@Dk%NOH7xkm@Vd{IcKgfudTq!;%a-b2=H$tpJ9>w zbL8ug>i+iu_2y_99z>ev00By%6r40q$iHB}^KV#b&SmWhx3_MfC{3_o?PXHb0(AjX zv0;fbTWnBgFeh!$3^3I@bVxjqaR!R%*}F92t$%|`TRmf zc{JAGtJn8nLwQu#=JN;Y%4_uopSC5|&`E8QiI#dU9aA<+Svl4aiwpo;ig+*3|5u#!#mkOwU zwW&(4Uquuz-s0sl@T=xljm9=%?ftrk@EPJcU~$C~K>F5-y)MabPJ_z(J3q z`W^J(YSV#6P$dUi;`Pgc<|xum!!{hyLHobO9pKTAxJOBbg7R)`m{sP0d1l!te7N@P XT3ZY;#S)je#Uozvi7kHQPD8^#-)3+7 diff --git a/im-ui/src/assets/iconfont/iconfont.woff b/im-ui/src/assets/iconfont/iconfont.woff deleted file mode 100644 index 70cb16825918795a8dedc583de3d37bbab0b0d4a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2572 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 -
-
-
- -
-
-
{{member.aliasName}}
-
-
+
+
\ No newline at end of file + + .chat-group-side-box { + border: #dddddd solid 1px; + animation: rtl-drawer-in .3s 1ms; + } +} \ No newline at end of file diff --git a/im-ui/src/components/chat/ChatGroupMember.vue b/im-ui/src/components/chat/ChatGroupMember.vue new file mode 100644 index 0000000..589d346 --- /dev/null +++ b/im-ui/src/components/chat/ChatGroupMember.vue @@ -0,0 +1,67 @@ + + + + + diff --git a/im-ui/src/components/chat/ChatGroupReaded.vue b/im-ui/src/components/chat/ChatGroupReaded.vue new file mode 100644 index 0000000..5b5e54b --- /dev/null +++ b/im-ui/src/components/chat/ChatGroupReaded.vue @@ -0,0 +1,186 @@ + + + + + + \ No newline at end of file diff --git a/im-ui/src/components/chat/ChatMessageItem.vue b/im-ui/src/components/chat/ChatMessageItem.vue index c152261..c0205e8 100644 --- a/im-ui/src/components/chat/ChatMessageItem.vue +++ b/im-ui/src/components/chat/ChatMessageItem.vue @@ -1,408 +1,433 @@ - \ No newline at end of file diff --git a/im-ui/src/components/chat/ChatPrivateVideo.vue b/im-ui/src/components/chat/ChatPrivateVideo.vue index 19dc951..687bafd 100644 --- a/im-ui/src/components/chat/ChatPrivateVideo.vue +++ b/im-ui/src/components/chat/ChatPrivateVideo.vue @@ -93,7 +93,6 @@ }, (stream) => { this.stream = stream; - console.log(this.stream) this.$refs.mineVideo.srcObject = stream; this.$refs.mineVideo.muted = true; callback(stream) diff --git a/im-ui/src/components/chat/ChatVoice.vue b/im-ui/src/components/chat/ChatVoice.vue index 879f57c..de9f116 100644 --- a/im-ui/src/components/chat/ChatVoice.vue +++ b/im-ui/src/components/chat/ChatVoice.vue @@ -60,9 +60,7 @@ this.state = 'RUNNING'; this.stateTip = "正在录音..."; }).catch(error => { - console.log(error); this.$message.error(error); - console.log(error); }); @@ -90,7 +88,6 @@ this.mode = 'PLAY'; }, onStopAudio() { - console.log(this.$refs.audio); this.$refs.audio.pause(); this.mode = 'RECORD'; }, diff --git a/im-ui/src/components/group/AddGroupMember.vue b/im-ui/src/components/group/AddGroupMember.vue index 5518181..6f57bcb 100644 --- a/im-ui/src/components/group/AddGroupMember.vue +++ b/im-ui/src/components/group/AddGroupMember.vue @@ -110,7 +110,6 @@ let friend = JSON.parse(JSON.stringify(f)) let m = this.members.filter((m) => !m.quit) .find((m) => m.userId == f.id); - console.log(m); if (m) { // 好友已经在群里 friend.disabled = true; diff --git a/im-ui/src/store/chatStore.js b/im-ui/src/store/chatStore.js index 26cf9df..ede92f0 100644 --- a/im-ui/src/store/chatStore.js +++ b/im-ui/src/store/chatStore.js @@ -77,10 +77,10 @@ export default { state.chats[idx].messages.forEach((m) => { if (m.selfSend && m.status != MESSAGE_STATUS.RECALL) { // pos.maxId为空表示整个会话已读 - if(!pos.maxId || m.id <= pos.maxId){ + if (!pos.maxId || m.id <= pos.maxId) { m.status = MESSAGE_STATUS.READED } - + } }) } @@ -96,8 +96,8 @@ export default { }, moveTop(state, idx) { // 加载中不移动,很耗性能 - if(state.loadingPrivateMsg || state.loadingGroupMsg){ - return ; + if (state.loadingPrivateMsg || state.loadingGroupMsg) { + return; } if (idx > 0) { let chat = state.chats[idx]; @@ -123,17 +123,21 @@ export default { } }, insertMessage(state, msgInfo) { - // 获取对方id或群id let type = msgInfo.groupId ? 'GROUP' : 'PRIVATE'; - let targetId = msgInfo.groupId ? msgInfo.groupId : msgInfo.selfSend ? msgInfo.recvId : msgInfo.sendId; - let chat = null; - for (let idx in state.chats) { - if (state.chats[idx].type == type && - state.chats[idx].targetId === targetId) { - chat = state.chats[idx]; - this.commit("moveTop", idx) - break; - } + // 记录消息的最大id + if (msgInfo.id && type == "PRIVATE" && msgInfo.id > state.privateMsgMaxId) { + state.privateMsgMaxId = msgInfo.id; + } + if (msgInfo.id && type == "GROUP" && msgInfo.id > state.groupMsgMaxId) { + state.groupMsgMaxId = msgInfo.id; + } + // 如果是已存在消息,则覆盖旧的消息数据 + let chat = this.getters.findChat(msgInfo); + let message = this.getters.findMessage(chat, msgInfo); + if(message){ + Object.assign(message, msgInfo); + this.commit("saveToStorage"); + return; } // 插入新的数据 if (msgInfo.type == MESSAGE_TYPE.IMAGE) { @@ -162,28 +166,6 @@ export default { chat.atAll = true; } } - // 记录消息的最大id - if (msgInfo.id && type == "PRIVATE" && msgInfo.id > state.privateMsgMaxId) { - state.privateMsgMaxId = msgInfo.id; - } - if (msgInfo.id && type == "GROUP" && msgInfo.id > state.groupMsgMaxId) { - state.groupMsgMaxId = msgInfo.id; - } - // 如果是已存在消息,则覆盖旧的消息数据 - for (let idx in chat.messages) { - if (msgInfo.id && chat.messages[idx].id == msgInfo.id) { - Object.assign(chat.messages[idx], msgInfo); - this.commit("saveToStorage"); - return; - } - // 正在发送中的消息可能没有id,通过发送时间判断 - if (msgInfo.selfSend && chat.messages[idx].selfSend && - chat.messages[idx].sendTime == msgInfo.sendTime) { - Object.assign(chat.messages[idx], msgInfo); - this.commit("saveToStorage"); - return; - } - } // 间隔大于10分钟插入时间显示 if (!chat.lastTimeTip || (chat.lastTimeTip < msgInfo.sendTime - 600 * 1000)) { chat.messages.push({ @@ -196,19 +178,18 @@ export default { chat.messages.push(msgInfo); this.commit("saveToStorage"); }, - deleteMessage(state, msgInfo) { + updateMessage(state, msgInfo) { // 获取对方id或群id - let type = msgInfo.groupId ? 'GROUP' : 'PRIVATE'; - let targetId = msgInfo.groupId ? msgInfo.groupId : msgInfo.selfSend ? msgInfo.recvId : msgInfo.sendId; - let chat = null; - for (let idx in state.chats) { - if (state.chats[idx].type == type && - state.chats[idx].targetId === targetId) { - chat = state.chats[idx]; - break; - } + let chat = this.getters.findChat(msgInfo); + let message = this.getters.findMessage(chat, msgInfo); + if(message){ + // 属性拷贝 + Object.assign(message, msgInfo); + this.commit("saveToStorage"); } - + }, + deleteMessage(state, msgInfo) { + let chat = this.getters.findChat(msgInfo); for (let idx in chat.messages) { // 已经发送成功的,根据id删除 if (chat.messages[idx].id && chat.messages[idx].id == msgInfo.id) { @@ -249,18 +230,18 @@ export default { loadingPrivateMsg(state, loadding) { state.loadingPrivateMsg = loadding; - if(!state.loadingPrivateMsg && !state.loadingGroupMsg){ + if (!state.loadingPrivateMsg && !state.loadingGroupMsg) { this.commit("sort") } }, loadingGroupMsg(state, loadding) { state.loadingGroupMsg = loadding; - if(!state.loadingPrivateMsg && !state.loadingGroupMsg){ + if (!state.loadingPrivateMsg && !state.loadingGroupMsg) { this.commit("sort") } }, - sort(state){ - state.chats.sort((c1,c2)=>c2.lastSendTime-c1.lastSendTime); + sort(state) { + state.chats.sort((c1, c2) => c2.lastSendTime - c1.lastSendTime); }, saveToStorage(state) { let userId = userStore.state.userInfo.id; @@ -290,5 +271,37 @@ export default { resolve(); }) } + }, + getters: { + findChat: (state) => (msgInfo) => { + // 获取对方id或群id + let type = msgInfo.groupId ? 'GROUP' : 'PRIVATE'; + let targetId = msgInfo.groupId ? msgInfo.groupId : msgInfo.selfSend ? msgInfo.recvId : msgInfo.sendId; + let chat = null; + for (let idx in state.chats) { + if (state.chats[idx].type == type && + state.chats[idx].targetId === targetId) { + chat = state.chats[idx]; + break; + } + } + return chat; + }, + findMessage: (state) => (chat, msgInfo) => { + if (!chat) { + return null; + } + for (let idx in chat.messages) { + // 通过id判断 + if (msgInfo.id && chat.messages[idx].id == msgInfo.id) { + return chat.messages[idx]; + } + // 正在发送中的消息可能没有id,通过发送时间判断 + if (msgInfo.selfSend && chat.messages[idx].selfSend && + chat.messages[idx].sendTime == msgInfo.sendTime) { + return chat.messages[idx]; + } + } + } } } \ No newline at end of file diff --git a/im-ui/src/store/friendStore.js b/im-ui/src/store/friendStore.js index e4ad788..c3e4f86 100644 --- a/im-ui/src/store/friendStore.js +++ b/im-ui/src/store/friendStore.js @@ -96,7 +96,6 @@ export default { }).then((friends) => { context.commit("setFriends", friends); context.commit("refreshOnlineStatus"); - console.log("loadFriend") resolve() }).catch((res) => { reject(); diff --git a/im-ui/src/store/groupStore.js b/im-ui/src/store/groupStore.js index 6b198f2..5b26af2 100644 --- a/im-ui/src/store/groupStore.js +++ b/im-ui/src/store/groupStore.js @@ -47,7 +47,6 @@ export default { method: 'GET' }).then((groups) => { context.commit("setGroups", groups); - console.log("loadGroup") resolve(); }).catch((res) => { reject(res); diff --git a/im-ui/src/store/index.js b/im-ui/src/store/index.js index 9887d4d..6b136bf 100644 --- a/im-ui/src/store/index.js +++ b/im-ui/src/store/index.js @@ -15,7 +15,6 @@ export default new Vuex.Store({ }, actions: { load(context) { - console.log("load") return this.dispatch("loadUser").then(() => { const promises = []; promises.push(this.dispatch("loadFriend")); diff --git a/im-ui/src/view/Home.vue b/im-ui/src/view/Home.vue index 7ed9b81..613dfe6 100644 --- a/im-ui/src/view/Home.vue +++ b/im-ui/src/view/Home.vue @@ -3,8 +3,7 @@
+ :url="$store.state.userStore.userInfo.headImageThumb" :size="60" @click.native="showSettingDialog = true">
@@ -12,7 +11,7 @@ -
{{unreadCount}}
+
{{ unreadCount }}
@@ -47,367 +46,376 @@ :friend="uiStore.chatPrivateVideo.friend" :master="uiStore.chatPrivateVideo.master" :offer="uiStore.chatPrivateVideo.offer" @close="$store.commit('closeChatPrivateVideoBox')"> - + \ No newline at end of file diff --git a/im-uniapp/App.vue b/im-uniapp/App.vue index 4f2de1e..72eeeed 100644 --- a/im-uniapp/App.vue +++ b/im-uniapp/App.vue @@ -166,6 +166,17 @@ store.commit("resetUnreadCount", chatInfo) return; } + // 消息回执处理 + if (msg.type == this.$enums.MESSAGE_TYPE.RECEIPT) { + // 更新消息已读人数 + let msgInfo = { + id: msg.id, + groupId: msg.groupId, + readedCount: msg.readedCount + }; + this.$store.commit("updateMessage", msgInfo) + return; + } this.loadGroupInfo(groupId).then((group) => { // 插入群聊消息 this.insertGroupMessage(group, msg); @@ -234,7 +245,6 @@ // this.audioTip.play(); }, initAudit() { - console.log("initAudit") if (store.state.userStore.userInfo.type == 1) { // 显示群组功能 uni.setTabBarItem({ diff --git a/im-uniapp/common/enums.js b/im-uniapp/common/enums.js index 6beeb24..bcc5db9 100644 --- a/im-uniapp/common/enums.js +++ b/im-uniapp/common/enums.js @@ -7,6 +7,7 @@ const MESSAGE_TYPE = { VIDEO:4, RECALL:10, READED:11, + RECEIPT:12, TIP_TIME:20, RTC_CALL: 101, RTC_ACCEPT: 102, diff --git a/im-uniapp/components/chat-group-readed/chat-group-readed.vue b/im-uniapp/components/chat-group-readed/chat-group-readed.vue new file mode 100644 index 0000000..cca2587 --- /dev/null +++ b/im-uniapp/components/chat-group-readed/chat-group-readed.vue @@ -0,0 +1,131 @@ + + + + + \ No newline at end of file diff --git a/im-uniapp/components/chat-message-item/chat-message-item.vue b/im-uniapp/components/chat-message-item/chat-message-item.vue index 8cc3a20..e32be6c 100644 --- a/im-uniapp/components/chat-message-item/chat-message-item.vue +++ b/im-uniapp/components/chat-message-item/chat-message-item.vue @@ -44,17 +44,19 @@ && msgInfo.status==$enums.MESSAGE_STATUS.READED">已读 未读 - + + {{msgInfo.readedCount}}人已读 + + - - + @@ -75,6 +77,9 @@ msgInfo: { type: Object, required: true + }, + groupMembers: { + type: Array } }, data() { @@ -135,6 +140,9 @@ uni.previewImage({ urls: [imageUrl] }) + }, + onShowReadedBox() { + this.$refs.chatGroupReaded.open(); } }, computed: { @@ -345,6 +353,16 @@ color: #ccc; font-weight: 600; } + .chat-receipt { + font-size: 13px; + color: darkblue; + font-weight: 600; + + .icon-ok { + font-size: 20px; + color: green; + } + } } } diff --git a/im-uniapp/pages/chat/chat-box.vue b/im-uniapp/pages/chat/chat-box.vue index 8342242..7a0c21a 100644 --- a/im-uniapp/pages/chat/chat-box.vue +++ b/im-uniapp/pages/chat/chat-box.vue @@ -12,7 +12,7 @@ + :msgInfo="msgInfo" :groupMembers="groupMembers"> @@ -30,6 +30,7 @@ @@ -68,6 +69,10 @@ 语音输入 + + + 回执消息 + 呼叫 @@ -96,6 +101,7 @@ group: {}, groupMembers: [], sendText: "", + isReceipt: false, // 是否回执消息 showVoice: false, // 是否显示语音录制弹窗 scrollMsgIdx: 0, // 滚动条定位为到哪条消息 chatTabBox: 'none', @@ -108,10 +114,13 @@ methods: { showTip() { uni.showToast({ - title: "加班开发中...", + title: "暂未支持...", icon: "none" }) }, + switchReceipt(){ + this.isReceipt = !this.isReceipt; + }, openAtBox() { this.$refs.atBox.init(this.atUserIds); this.$refs.atBox.open(); @@ -146,12 +155,13 @@ title: "不能发送空白信息", icon: "none" }); - } - let atText = this.createAtText() + let receiptText = this.isReceipt? "【回执消息】":""; + let atText = this.createAtText(); let msgInfo = { - content: this.sendText + atText, + content: receiptText + this.sendText + atText, atUserIds: this.atUserIds, + receipt : this.isReceipt, type: 0 } // 填充对方id @@ -166,6 +176,7 @@ msgInfo.sendTime = new Date().getTime(); msgInfo.sendId = this.$store.state.userStore.userInfo.id; msgInfo.selfSend = true; + msgInfo.readedCount = 0, msgInfo.status = this.$enums.MESSAGE_STATUS.UNSEND; this.$store.commit("insertMessage", msgInfo); this.sendText = ""; @@ -174,6 +185,7 @@ this.scrollToBottom(); // 清空@用户列表 this.atUserIds = []; + this.isReceipt = false; }); }, createAtText() { @@ -215,7 +227,6 @@ return; } this.$nextTick(() => { - console.log("scrollToMsgIdx", this.scrollMsgIdx) this.scrollMsgIdx = idx; }); @@ -256,6 +267,7 @@ sendTime: new Date().getTime(), selfSend: true, type: this.$enums.MESSAGE_TYPE.IMAGE, + readedCount: 0, loadStatus: "loading", status: this.$enums.MESSAGE_STATUS.UNSEND } @@ -272,6 +284,7 @@ onUploadImageSuccess(file, res) { let msgInfo = JSON.parse(JSON.stringify(file.msgInfo)); msgInfo.content = JSON.stringify(res.data); + msgInfo.receipt = this.isReceipt this.$http({ url: this.messageAction, method: 'POST', @@ -279,6 +292,7 @@ }).then((id) => { msgInfo.loadStatus = 'ok'; msgInfo.id = id; + this.isReceipt = false; this.$store.commit("insertMessage", msgInfo); }) }, @@ -300,6 +314,7 @@ sendTime: new Date().getTime(), selfSend: true, type: this.$enums.MESSAGE_TYPE.FILE, + readedCount: 0, loadStatus: "loading", status: this.$enums.MESSAGE_STATUS.UNSEND } @@ -321,6 +336,7 @@ } let msgInfo = JSON.parse(JSON.stringify(file.msgInfo)); msgInfo.content = JSON.stringify(data); + msgInfo.receipt = this.isReceipt this.$http({ url: this.messageAction, method: 'POST', @@ -328,6 +344,7 @@ }).then((id) => { msgInfo.loadStatus = 'ok'; msgInfo.id = id; + this.isReceipt = false; this.$store.commit("insertMessage", msgInfo); }) }, @@ -399,7 +416,6 @@ // 防止滚动条定格在顶部,不能一直往上滚 this.scrollToMsgIdx(this.showMinIdx); // #endif - // 多展示10条信息 this.showMinIdx = this.showMinIdx > 10 ? this.showMinIdx - 10 : 0; }, @@ -565,6 +581,8 @@ this.loadFriend(this.chat.targetId); this.loadReaded(this.chat.targetId) } + // 复位回执消息 + this.isReceipt = false; }, onUnload() { this.$store.commit("activeChat", -1); @@ -709,6 +727,10 @@ font-size: 80rpx; background-color: white; border-radius: 20%; + + &.active{ + background-color: #ddd; + } } .tool-name { diff --git a/im-uniapp/static/icon/iconfont.css b/im-uniapp/static/icon/iconfont.css index b437e6f..911f651 100644 --- a/im-uniapp/static/icon/iconfont.css +++ b/im-uniapp/static/icon/iconfont.css @@ -1,6 +1,6 @@ @font-face { font-family: "iconfont"; /* Project id 4272106 */ - src: url('iconfont.ttf?t=1699795609670') format('truetype'); + src: url('iconfont.ttf?t=1706027587101') format('truetype'); } .iconfont { @@ -11,6 +11,14 @@ -moz-osx-font-smoothing: grayscale; } +.icon-receipt:before { + content: "\e61a"; +} + +.icon-ok:before { + content: "\e65a"; +} + .icon-at:before { content: "\e7de"; } diff --git a/im-uniapp/static/icon/iconfont.ttf b/im-uniapp/static/icon/iconfont.ttf index de77a9226db541c032ffe72cd3c437bce9f11dce..45f0474220a486625606e4ad3b58327343863b4c 100644 GIT binary patch delta 1010 zcmZvZT}TvB6vxk<*`1kPwViE0re!o}O!k(lrV+rb|2O|okpl${}M4*)EZ0GJ^PtEX~d+we8Ip=?7ZdJ@3 znyShyFPzf>xO!x@cVME&5;$`kz}y62E9pxo&Ihv-Hv!-i67ItT@dH1D@e)$jH#j!l zD&;l+$U5#@2ZmFL;$&$R=I_IO`Cwvvgp@-T-?M$mP+~AG-)ndX;OxeJy(7b;MPvH= zFUtV#S14Gyvs8N20R%&2Q9r)eWoy`g0uvqpd|sJBp|R#Rb5~6Q&zwoXu~WQQofq6n`xYOYL~eH>C|Sq zn$xUq*}ppo|Gxq2nqd!CD?)uC>;*ge5p8Y?FFb*r`&R3`O}3cH36XOslKy(q zs**YVX-o|URqmK4&=m0OG%`84cGjX==l2LaiY3Ki67_eJ_%Cp>+*^K@_kt4+LKJOj zr)=I#BT?Ln+bIg7;9%|ZenM!cHs~k6Mw4Cj(P({FlI{khsgAblP^h}CBikNlLAACTOk3=Vv>>rm#SV{T fbGu9&T93<*3vq&YDV<7R7#ZV+`?VXc>jeG+NBG~e delta 644 zcmaKpKWI}?6vn@M@5^hnjeQYP4MZui4Gx7?!FEVRkWfPh5!=N{g0{_&*oZ~iK{Hew z+76YwIEWPL)}ew+kO~e`96E@be<%ua)Zoyij;7zurK|Vx^x@w6+j7 zCU6W#s2HMyYN0)6{@J%GW(R|?{=wPg;@1^!4)&k}54%C!9bsQc%Ht*!&hDTVB=2$a zNYT^oIl-9F38dnoC&*n-o%SFL9vX$zJ+us2^Uyqg@{c&&hxVj@m9mUF-2U%4ZK~0K=h$ A;Q#;t diff --git a/im-uniapp/store/chatStore.js b/im-uniapp/store/chatStore.js index 87761e2..0155b3d 100644 --- a/im-uniapp/store/chatStore.js +++ b/im-uniapp/store/chatStore.js @@ -5,7 +5,6 @@ import { import userStore from './userStore'; export default { - state: { activeIndex: -1, chats: [], @@ -127,18 +126,23 @@ export default { insertMessage(state, msgInfo) { // 获取对方id或群id let type = msgInfo.groupId ? 'GROUP' : 'PRIVATE'; - let targetId = msgInfo.groupId ? msgInfo.groupId : msgInfo.selfSend ? msgInfo.recvId : msgInfo.sendId; - let chat = null; - for (let idx in state.chats) { - if (state.chats[idx].type == type && - state.chats[idx].targetId === targetId) { - chat = state.chats[idx]; - this.commit("moveTop", idx) - break; - } + // 记录消息的最大id + if (msgInfo.id && type == "PRIVATE" && msgInfo.id > state.privateMsgMaxId) { + state.privateMsgMaxId = msgInfo.id; + } + if (msgInfo.id && type == "GROUP" && msgInfo.id > state.groupMsgMaxId) { + state.groupMsgMaxId = msgInfo.id; + } + // 如果是已存在消息,则覆盖旧的消息数据 + let chat = this.getters.findChat(msgInfo); + let message = this.getters.findMessage(chat, msgInfo); + if(message){ + Object.assign(message, msgInfo); + this.commit("saveToStorage"); + return; } // 会话列表内容 - if(!state.loadingPrivateMsg && !state.loadingPrivateMsg){ + if(!state.loadingPrivateMsg && !state.loadingGroupMsg){ if (msgInfo.type == MESSAGE_TYPE.IMAGE) { chat.lastContent = "[图片]"; } else if (msgInfo.type == MESSAGE_TYPE.FILE) { @@ -166,28 +170,8 @@ export default { chat.atAll = true; } } - // 记录消息的最大id - if (msgInfo.id && type == "PRIVATE" && msgInfo.id > state.privateMsgMaxId) { - state.privateMsgMaxId = msgInfo.id; - } - if (msgInfo.id && type == "GROUP" && msgInfo.id > state.groupMsgMaxId) { - state.groupMsgMaxId = msgInfo.id; - } - // 如果是已存在消息,则覆盖旧的消息数据 - for (let idx in chat.messages) { - if (msgInfo.id && chat.messages[idx].id == msgInfo.id) { - Object.assign(chat.messages[idx], msgInfo); - this.commit("saveToStorage"); - return; - } - // 正在发送中的消息可能没有id,通过发送时间判断 - if (msgInfo.selfSend && chat.messages[idx].selfSend && - chat.messages[idx].sendTime == msgInfo.sendTime) { - Object.assign(chat.messages[idx], msgInfo); - this.commit("saveToStorage"); - return; - } - } + + // 间隔大于10分钟插入时间显示 if (!chat.lastTimeTip || (chat.lastTimeTip < msgInfo.sendTime - 600 * 1000)) { chat.messages.push({ @@ -199,21 +183,20 @@ export default { // 新的消息 chat.messages.push(msgInfo); this.commit("saveToStorage"); - }, - deleteMessage(state, msgInfo) { + updateMessage(state, msgInfo) { // 获取对方id或群id - let type = msgInfo.groupId ? 'GROUP' : 'PRIVATE'; - let targetId = msgInfo.groupId ? msgInfo.groupId : msgInfo.selfSend ? msgInfo.recvId : msgInfo.sendId; - let chat = null; - for (let idx in state.chats) { - if (state.chats[idx].type == type && - state.chats[idx].targetId === targetId) { - chat = state.chats[idx]; - break; - } + let chat = this.getters.findChat(msgInfo); + let message = this.getters.findMessage(chat, msgInfo); + if(message){ + // 属性拷贝 + Object.assign(message, msgInfo); + this.commit("saveToStorage"); } - + }, + deleteMessage(state, msgInfo) { + // 获取对方id或群id + let chat = this.getters.findChat(msgInfo); for (let idx in chat.messages) { // 已经发送成功的,根据id删除 if (chat.messages[idx].id && chat.messages[idx].id == msgInfo.id) { @@ -324,6 +307,37 @@ export default { }); }) } - + }, + getters: { + findChat: (state) => (msgInfo) => { + // 获取对方id或群id + let type = msgInfo.groupId ? 'GROUP' : 'PRIVATE'; + let targetId = msgInfo.groupId ? msgInfo.groupId : msgInfo.selfSend ? msgInfo.recvId : msgInfo.sendId; + let chat = null; + for (let idx in state.chats) { + if (state.chats[idx].type == type && + state.chats[idx].targetId === targetId) { + chat = state.chats[idx]; + break; + } + } + return chat; + }, + findMessage: (state) => (chat, msgInfo) => { + if (!chat) { + return null; + } + for (let idx in chat.messages) { + // 通过id判断 + if (msgInfo.id && chat.messages[idx].id == msgInfo.id) { + return chat.messages[idx]; + } + // 正在发送中的消息可能没有id,通过发送时间判断 + if (msgInfo.selfSend && chat.messages[idx].selfSend && + chat.messages[idx].sendTime == msgInfo.sendTime) { + return chat.messages[idx]; + } + } + } } } \ No newline at end of file