Browse Source

!97 前端性能优化

Merge pull request !97 from blue/v_3.0.0
master
blue 2 years ago
committed by Gitee
parent
commit
30fbd5a931
No known key found for this signature in database GPG Key ID: 173E9B9CA92EEF8F
  1. 3
      db/im-platform.sql
  2. 2
      im-platform/src/main/java/com/bx/implatform/entity/GroupMember.java
  3. 20
      im-platform/src/main/java/com/bx/implatform/service/impl/GroupMessageServiceImpl.java
  4. 30
      im-platform/src/main/java/com/bx/implatform/service/impl/GroupServiceImpl.java
  5. 72
      im-platform/src/main/java/com/bx/implatform/service/impl/PrivateMessageServiceImpl.java
  6. 128
      im-uniapp/App.vue
  7. 4
      im-uniapp/common/request.js
  8. 4
      im-uniapp/components/chat-at-box/chat-at-box.vue
  9. 2
      im-uniapp/components/chat-group-readed/chat-group-readed.vue
  10. 15
      im-uniapp/components/chat-item/chat-item.vue
  11. 2
      im-uniapp/components/group-rtc-join/group-rtc-join.vue
  12. 22
      im-uniapp/main.js
  13. 63
      im-uniapp/pages/chat/chat-box.vue
  14. 4
      im-uniapp/pages/chat/chat-group-video.vue
  15. 4
      im-uniapp/pages/chat/chat-private-video.vue
  16. 35
      im-uniapp/pages/chat/chat.vue
  17. 22
      im-uniapp/pages/common/user-info.vue
  18. 8
      im-uniapp/pages/friend/friend-add.vue
  19. 5
      im-uniapp/pages/friend/friend.vue
  20. 26
      im-uniapp/pages/group/group-edit.vue
  21. 23
      im-uniapp/pages/group/group-info.vue
  22. 9
      im-uniapp/pages/group/group-invite.vue
  23. 6
      im-uniapp/pages/group/group-member.vue
  24. 4
      im-uniapp/pages/group/group.vue
  25. 4
      im-uniapp/pages/login/login.vue
  26. 4
      im-uniapp/pages/mine/mine-edit.vue
  27. 5
      im-uniapp/pages/mine/mine.vue
  28. 352
      im-uniapp/store/chatStore.js
  29. 34
      im-uniapp/store/configStore.js
  30. 112
      im-uniapp/store/friendStore.js
  31. 67
      im-uniapp/store/groupStore.js
  32. 35
      im-uniapp/store/index.js
  33. 42
      im-uniapp/store/userStore.js
  34. 171
      im-web/src/store/chatStore.js
  35. 2
      im-web/src/view/Chat.vue
  36. 2
      im-web/src/view/Friend.vue
  37. 6
      im-web/src/view/Group.vue
  38. 4
      im-web/src/view/Home.vue

3
db/im-platform.sql

@ -35,7 +35,8 @@ create table `im_private_message`(
`type` tinyint(1) NOT NULL comment '消息类型 0:文字 1:图片 2:文件 3:语音 4:视频 21:提示',
`status` tinyint(1) NOT NULL comment '状态 0:未读 1:已读 2:撤回 3:已读',
`send_time` datetime DEFAULT CURRENT_TIMESTAMP comment '发送时间',
key `idx_send_recv_id` (`send_id`,`recv_id`)
key `idx_send_id` (`send_id`),
key `idx_recv_id` (`recv_id`)
)ENGINE=InnoDB CHARSET=utf8mb4 comment '私聊消息';

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

@ -74,7 +74,7 @@ public class GroupMember extends Model<GroupMember> {
private Date quitTime;
public String getShowNickName() {
return StrUtil.isEmpty(remarkNickName) ? userNickName : remarkNickName;
return StrUtil.blankToDefault(remarkNickName, userNickName);
}
}

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

@ -72,9 +72,7 @@ public class GroupMessageServiceImpl extends ServiceImpl<GroupMessageMapper, Gro
msg.setSendId(session.getUserId());
msg.setSendTime(new Date());
msg.setSendNickName(member.getShowNickName());
if (CollectionUtil.isNotEmpty(dto.getAtUserIds())) {
msg.setAtUserIds(StrUtil.join(",", dto.getAtUserIds()));
}
msg.setAtUserIds(CommaTextUtils.asText(dto.getAtUserIds()));
this.save(msg);
// 过滤内容中的敏感词
if(MessageType.TEXT.code().equals(dto.getType())){
@ -160,13 +158,14 @@ public class GroupMessageServiceImpl extends ServiceImpl<GroupMessageMapper, Gro
// 开启加载中标志
this.sendLoadingMessage(true);
// 只能拉取最近3个月的,最多拉取3000条
Date minDate = DateUtils.addMonths(new Date(), -3);
int months = session.getTerminal().equals(IMTerminalType.APP.code()) ? 1 : 3;
Date minDate = DateUtils.addMonths(new Date(), -months);
LambdaQueryWrapper<GroupMessage> wrapper = Wrappers.lambdaQuery();
wrapper.gt(GroupMessage::getId, minId)
.gt(GroupMessage::getSendTime, minDate)
.in(GroupMessage::getGroupId, groupIds)
.ne(GroupMessage::getStatus, MessageStatus.RECALL.code())
.orderByDesc(GroupMessage::getId).last("limit 3000");
.orderByAsc(GroupMessage::getId);
List<GroupMessage> messages = this.list(wrapper);
// 通过群聊对消息进行分组
Map<Long, List<GroupMessage>> messageGroupMap = messages.stream().collect(Collectors.groupingBy(GroupMessage::getGroupId));
@ -178,8 +177,7 @@ public class GroupMessageServiceImpl extends ServiceImpl<GroupMessageMapper, Gro
.between(GroupMessage::getSendTime, minDate,quitMember.getQuitTime())
.eq(GroupMessage::getGroupId, quitMember.getGroupId())
.ne(GroupMessage::getStatus, MessageStatus.RECALL.code())
.orderByDesc(GroupMessage::getId)
.last("limit 100");
.orderByAsc(GroupMessage::getId);
List<GroupMessage> groupMessages = this.list(wrapper);
messageGroupMap.put(quitMember.getGroupId(),groupMessages);
groupMemberMap.put(quitMember.getGroupId(),quitMember);
@ -187,8 +185,6 @@ public class GroupMessageServiceImpl extends ServiceImpl<GroupMessageMapper, Gro
// 推送消息
AtomicInteger sendCount = new AtomicInteger();
messageGroupMap.forEach((groupId, groupMessages) -> {
// id从小到大排序
CollectionUtil.reverse(groupMessages);
// 填充消息状态
String key = StrUtil.join(":", RedisKey.IM_GROUP_READED_POSITION, groupId);
Object o = redisTemplate.opsForHash().get(key, session.getUserId().toString());
@ -208,10 +204,8 @@ public class GroupMessageServiceImpl extends ServiceImpl<GroupMessageMapper, Gro
// 组装vo
GroupMessageVO vo = BeanUtils.copyProperties(m, GroupMessageVO.class);
// 被@用户列表
if (StringUtils.isNotBlank(m.getAtUserIds()) && Objects.nonNull(vo)) {
List<String> atIds = Splitter.on(",").trimResults().splitToList(m.getAtUserIds());
vo.setAtUserIds(atIds.stream().map(Long::parseLong).collect(Collectors.toList()));
}
List<String> atIds = CommaTextUtils.asList(m.getAtUserIds());
vo.setAtUserIds(atIds.stream().map(Long::parseLong).collect(Collectors.toList()));
// 填充状态
vo.setStatus(readedMaxId >= m.getId() ? MessageStatus.READED.code() : MessageStatus.UNSEND.code());
// 针对回执消息填充已读人数

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

@ -70,8 +70,8 @@ public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements
groupMemberService.save(member);
// 返回
vo.setId(group.getId());
vo.setShowNickName(StrUtil.isEmpty(vo.getRemarkNickName()) ? member.getUserNickName() : vo.getRemarkNickName());
vo.setShowGroupName(StrUtil.isEmpty(vo.getRemarkGroupName()) ? group.getName() : vo.getRemarkGroupName());
vo.setShowNickName(member.getShowNickName());
vo.setShowGroupName(StrUtil.blankToDefault(member.getRemarkGroupName(), group.getName()));
log.info("创建群聊,群聊id:{},群聊名称:{}", group.getId(), group.getName());
return vo;
}
@ -96,8 +96,8 @@ public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements
group = BeanUtils.copyProperties(vo, Group.class);
this.updateById(group);
}
vo.setShowNickName(StrUtil.isEmpty(vo.getRemarkNickName()) ? member.getUserNickName() : vo.getRemarkNickName());
vo.setShowGroupName(StrUtil.isEmpty(vo.getRemarkGroupName()) ? group.getName() : vo.getRemarkGroupName());
vo.setShowNickName(member.getShowNickName());
vo.setShowGroupName(StrUtil.blankToDefault(member.getRemarkGroupName(), group.getName()));
log.info("修改群聊,群聊id:{},群聊名称:{}", group.getId(), group.getName());
return vo;
}
@ -139,7 +139,7 @@ public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements
String key = StrUtil.join(":", RedisKey.IM_GROUP_READED_POSITION, groupId);
redisTemplate.opsForHash().delete(key, userId.toString());
// 推送退出群聊提示
this.sendTipMessage(groupId, Arrays.asList(userId), "您已退出群聊");
this.sendTipMessage(groupId, List.of(userId), "您已退出群聊");
log.info("退出群聊,群聊id:{},群聊名称:{},用户id:{}", group.getId(), group.getName(), userId);
}
@ -159,7 +159,7 @@ public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements
String key = StrUtil.join(":", RedisKey.IM_GROUP_READED_POSITION, groupId);
redisTemplate.opsForHash().delete(key, userId.toString());
// 推送踢出群聊提示
this.sendTipMessage(groupId, Arrays.asList(userId), "您已被移出群聊");
this.sendTipMessage(groupId, List.of(userId), "您已被移出群聊");
log.info("踢出群聊,群聊id:{},群聊名称:{},用户id:{}", group.getId(), group.getName(), userId);
}
@ -178,7 +178,7 @@ public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements
vo.setRemarkGroupName(member.getRemarkGroupName());
vo.setRemarkNickName(member.getRemarkNickName());
vo.setShowNickName(member.getShowNickName());
vo.setShowGroupName(StrUtil.isEmpty(vo.getRemarkGroupName()) ? group.getName() : vo.getRemarkGroupName());
vo.setShowGroupName(StrUtil.blankToDefault(member.getRemarkGroupName(), group.getName()));
vo.setQuit(member.getQuit());
return vo;
}
@ -215,12 +215,12 @@ public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements
groupWrapper.in(Group::getId, ids);
List<Group> groups = this.list(groupWrapper);
// 转vo
return groups.stream().map(g -> {
GroupVO vo = BeanUtils.copyProperties(g, GroupVO.class);
GroupMember member = groupMembers.stream().filter(m -> g.getId().equals(m.getGroupId())).findFirst().get();
vo.setShowNickName(
StrUtil.isEmpty(vo.getRemarkNickName()) ? session.getNickName() : vo.getRemarkNickName());
vo.setShowGroupName(StrUtil.isEmpty(vo.getRemarkGroupName()) ? g.getName() : vo.getRemarkGroupName());
return groups.stream().map(group -> {
GroupVO vo = BeanUtils.copyProperties(group, GroupVO.class);
GroupMember member =
groupMembers.stream().filter(m -> group.getId().equals(m.getGroupId())).findFirst().get();
vo.setShowNickName(StrUtil.blankToDefault(member.getRemarkNickName(), session.getNickName()));
vo.setShowGroupName(StrUtil.blankToDefault(member.getRemarkGroupName(), group.getName()));
vo.setQuit(member.getQuit());
return vo;
}).collect(Collectors.toList());
@ -244,7 +244,7 @@ public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements
List<Friend> friends = friendsService.findFriendByUserId(session.getUserId());
List<Friend> friendsList = vo.getFriendIds().stream()
.map(id -> friends.stream().filter(f -> f.getFriendId().equals(id)).findFirst().get())
.collect(Collectors.toList());
.toList();
if (friendsList.size() != vo.getFriendIds().size()) {
throw new GlobalException("部分用户不是您的好友,邀请失败");
}
@ -282,7 +282,7 @@ public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements
return members.stream().map(m -> {
GroupMemberVO vo = BeanUtils.copyProperties(m, GroupMemberVO.class);
vo.setShowNickName(m.getShowNickName());
vo.setShowGroupName(StrUtil.isEmpty(m.getRemarkGroupName()) ? group.getName() : m.getRemarkGroupName());
vo.setShowGroupName(StrUtil.blankToDefault(m.getRemarkGroupName(), group.getName()));
vo.setOnline(onlineUserIds.contains(m.getUserId()));
return vo;
}).sorted((m1, m2) -> m2.getOnline().compareTo(m1.getOnline())).collect(Collectors.toList());

72
im-platform/src/main/java/com/bx/implatform/service/impl/PrivateMessageServiceImpl.java

@ -37,8 +37,8 @@ import java.util.stream.Collectors;
@Slf4j
@Service
@RequiredArgsConstructor
public class PrivateMessageServiceImpl extends ServiceImpl<PrivateMessageMapper, PrivateMessage> implements
PrivateMessageService {
public class PrivateMessageServiceImpl extends ServiceImpl<PrivateMessageMapper, PrivateMessage>
implements PrivateMessageService {
private final FriendService friendService;
private final IMClient imClient;
@ -58,7 +58,7 @@ public class PrivateMessageServiceImpl extends ServiceImpl<PrivateMessageMapper,
msg.setSendTime(new Date());
this.save(msg);
// 过滤内容中的敏感词
if(MessageType.TEXT.code().equals(dto.getType())){
if (MessageType.TEXT.code().equals(dto.getType())) {
msg.setContent(sensitiveFilterUtil.filter(dto.getContent()));
}
// 推送消息
@ -112,7 +112,6 @@ public class PrivateMessageServiceImpl extends ServiceImpl<PrivateMessageMapper,
log.info("撤回私聊消息,发送id:{},接收id:{},内容:{}", msg.getSendId(), msg.getRecvId(), msg.getContent());
}
@Override
public List<PrivateMessageVO> findHistoryMessage(Long friendId, Long page, Long size) {
page = page > 0 ? page : 1;
@ -120,17 +119,16 @@ public class PrivateMessageServiceImpl extends ServiceImpl<PrivateMessageMapper,
Long userId = SessionContext.getSession().getUserId();
long stIdx = (page - 1) * size;
QueryWrapper<PrivateMessage> wrapper = new QueryWrapper<>();
wrapper.lambda().and(wrap -> wrap.and(
wp -> wp.eq(PrivateMessage::getSendId, userId)
.eq(PrivateMessage::getRecvId, friendId))
.or(wp -> wp.eq(PrivateMessage::getRecvId, userId)
.eq(PrivateMessage::getSendId, friendId)))
.ne(PrivateMessage::getStatus, MessageStatus.RECALL.code())
.orderByDesc(PrivateMessage::getId)
wrapper.lambda().and(
wrap -> wrap.and(wp -> wp.eq(PrivateMessage::getSendId, userId).eq(PrivateMessage::getRecvId, friendId))
.or(wp -> wp.eq(PrivateMessage::getRecvId, userId).eq(PrivateMessage::getSendId, friendId)))
.ne(PrivateMessage::getStatus, MessageStatus.RECALL.code()).orderByDesc(PrivateMessage::getId)
.last("limit " + stIdx + "," + size);
List<PrivateMessage> messages = this.list(wrapper);
List<PrivateMessageVO> messageInfos = messages.stream().map(m -> BeanUtils.copyProperties(m, PrivateMessageVO.class)).collect(Collectors.toList());
List<PrivateMessageVO> messageInfos =
messages.stream().map(m -> BeanUtils.copyProperties(m, PrivateMessageVO.class))
.collect(Collectors.toList());
log.info("拉取聊天记录,用户id:{},好友id:{},数量:{}", userId, friendId, messageInfos.size());
return messageInfos;
}
@ -138,7 +136,7 @@ public class PrivateMessageServiceImpl extends ServiceImpl<PrivateMessageMapper,
@Override
public void pullOfflineMessage(Long minId) {
UserSession session = SessionContext.getSession();
if(!imClient.isOnline(session.getUserId())){
if (!imClient.isOnline(session.getUserId())) {
throw new GlobalException("网络连接失败,无法拉取离线消息");
}
// 查询用户好友列表
@ -153,28 +151,22 @@ public class PrivateMessageServiceImpl extends ServiceImpl<PrivateMessageMapper,
List<Long> friendIds = friends.stream().map(Friend::getFriendId).collect(Collectors.toList());
// 获取当前用户的消息
LambdaQueryWrapper<PrivateMessage> queryWrapper = Wrappers.lambdaQuery();
// 只能拉取最近3个月的3000条消息
Date minDate = DateUtils.addMonths(new Date(), -3);
queryWrapper.gt(PrivateMessage::getId, minId)
.ge(PrivateMessage::getSendTime, minDate)
.ne(PrivateMessage::getStatus, MessageStatus.RECALL.code())
.and(wrap -> wrap.and(
wp -> wp.eq(PrivateMessage::getSendId, session.getUserId())
.in(PrivateMessage::getRecvId, friendIds))
.or(wp -> wp.eq(PrivateMessage::getRecvId, session.getUserId())
.in(PrivateMessage::getSendId, friendIds)))
.orderByDesc(PrivateMessage::getId)
.last("limit 3000");
// 只能拉取最近3个月的消息,移动端只拉取一个月消息
int months = session.getTerminal().equals(IMTerminalType.APP.code()) ? 1 : 3;
Date minDate = DateUtils.addMonths(new Date(), -months);
queryWrapper.gt(PrivateMessage::getId, minId).ge(PrivateMessage::getSendTime, minDate)
.ne(PrivateMessage::getStatus, MessageStatus.RECALL.code()).and(wrap -> wrap.and(
wp -> wp.eq(PrivateMessage::getSendId, session.getUserId()).in(PrivateMessage::getRecvId, friendIds))
.or(wp -> wp.eq(PrivateMessage::getRecvId, session.getUserId()).in(PrivateMessage::getSendId, friendIds)))
.orderByAsc(PrivateMessage::getId);
List<PrivateMessage> messages = this.list(queryWrapper);
// 消息顺序从小到大
CollectionUtil.reverse(messages);
// 推送消息
for(PrivateMessage m:messages ){
for (PrivateMessage m : messages) {
PrivateMessageVO vo = BeanUtils.copyProperties(m, PrivateMessageVO.class);
IMPrivateMessage<PrivateMessageVO> sendMessage = new IMPrivateMessage<>();
sendMessage.setSender(new IMUserInfo(m.getSendId(), IMTerminalType.WEB.code()));
sendMessage.setRecvId(session.getUserId());
sendMessage.setRecvTerminals(Arrays.asList(session.getTerminal()));
sendMessage.setRecvTerminals(List.of(session.getTerminal()));
sendMessage.setSendToSelf(false);
sendMessage.setData(vo);
sendMessage.setSendResult(true);
@ -214,42 +206,36 @@ public class PrivateMessageServiceImpl extends ServiceImpl<PrivateMessageMapper,
imClient.sendPrivateMessage(sendMessage);
// 修改消息状态为已读
LambdaUpdateWrapper<PrivateMessage> updateWrapper = Wrappers.lambdaUpdate();
updateWrapper.eq(PrivateMessage::getSendId, friendId)
.eq(PrivateMessage::getRecvId, session.getUserId())
updateWrapper.eq(PrivateMessage::getSendId, friendId).eq(PrivateMessage::getRecvId, session.getUserId())
.eq(PrivateMessage::getStatus, MessageStatus.SENDED.code())
.set(PrivateMessage::getStatus, MessageStatus.READED.code());
this.update(updateWrapper);
log.info("消息已读,接收方id:{},发送方id:{}", session.getUserId(), friendId);
}
@Override
public Long getMaxReadedId(Long friendId) {
UserSession session = SessionContext.getSession();
LambdaQueryWrapper<PrivateMessage> wrapper = Wrappers.lambdaQuery();
wrapper.eq(PrivateMessage::getSendId, session.getUserId())
.eq(PrivateMessage::getRecvId, friendId)
.eq(PrivateMessage::getStatus, MessageStatus.READED.code())
.orderByDesc(PrivateMessage::getId)
.select(PrivateMessage::getId)
.last("limit 1");
wrapper.eq(PrivateMessage::getSendId, session.getUserId()).eq(PrivateMessage::getRecvId, friendId)
.eq(PrivateMessage::getStatus, MessageStatus.READED.code()).orderByDesc(PrivateMessage::getId)
.select(PrivateMessage::getId).last("limit 1");
PrivateMessage message = this.getOne(wrapper);
if(Objects.isNull(message)){
if (Objects.isNull(message)) {
return -1L;
}
return message.getId();
}
private void sendLoadingMessage(Boolean isLoadding){
private void sendLoadingMessage(Boolean isLoadding) {
UserSession session = SessionContext.getSession();
PrivateMessageVO msgInfo = new PrivateMessageVO();
msgInfo.setType(MessageType.LOADING.code());
msgInfo.setContent(isLoadding.toString());
IMPrivateMessage sendMessage = new IMPrivateMessage<>();
IMPrivateMessage<PrivateMessageVO> sendMessage = new IMPrivateMessage<>();
sendMessage.setSender(new IMUserInfo(session.getUserId(), session.getTerminal()));
sendMessage.setRecvId(session.getUserId());
sendMessage.setRecvTerminals(Arrays.asList(session.getTerminal()));
sendMessage.setRecvTerminals(List.of(session.getTerminal()));
sendMessage.setData(msgInfo);
sendMessage.setSendToSelf(false);
sendMessage.setSendResult(false);

128
im-uniapp/App.vue

@ -1,5 +1,5 @@
<script>
import store from './store';
import App from './App'
import http from './common/request';
import * as msgType from './common/messageType';
import * as enums from './common/enums';
@ -18,7 +18,7 @@
init() {
this.isExit = false;
//
store.dispatch("load").then(() => {
this.loadStore().then(() => {
// websocket
this.initWebSocket();
}).catch((e) => {
@ -40,8 +40,8 @@
})
}
// 线
this.pullPrivateOfflineMessage(store.state.chatStore.privateMsgMaxId);
this.pullGroupOfflineMessage(store.state.chatStore.groupMsgMaxId);
this.pullPrivateOfflineMessage(this.chatStore.privateMsgMaxId);
this.pullGroupOfflineMessage(this.chatStore.groupMsgMaxId);
});
wsApi.onMessage((cmd, msgInfo) => {
if (cmd == 2) {
@ -66,36 +66,53 @@
console.log("ws断开", res);
//
this.reconnectWs();
})
},
loadStore() {
return this.userStore.loadUser().then(() => {
const promises = [];
promises.push(this.friendStore.loadFriend());
promises.push(this.groupStore.loadGroup());
promises.push(this.chatStore.loadChat());
promises.push(this.configStore.loadConfig());
return Promise.all(promises);
})
},
unloadStore(){
this.friendStore.clear();
this.groupStore.clear();
this.chatStore.clear();
this.configStore.clear();
this.userStore.clear();
},
pullPrivateOfflineMessage(minId) {
store.commit("loadingPrivateMsg", true)
this.chatStore.setLoadingPrivateMsg(true)
http({
url: "/message/private/pullOfflineMessage?minId=" + minId,
method: 'GET'
}).catch(() => {
store.commit("loadingPrivateMsg", false)
this.chatStore.setLoadingPrivateMsg(false)
})
},
pullGroupOfflineMessage(minId) {
store.commit("loadingGroupMsg", true)
this.chatStore.setLoadingGroupMsg(true)
http({
url: "/message/group/pullOfflineMessage?minId=" + minId,
method: 'GET'
}).catch(() => {
store.commit("loadingGroupMsg", false)
this.chatStore.setLoadingGroupMsg(false)
})
},
handlePrivateMessage(msg) {
//
if (msg.type == enums.MESSAGE_TYPE.LOADING) {
store.commit("loadingPrivateMsg", JSON.parse(msg.content))
this.chatStore.setLoadingPrivateMsg(JSON.parse(msg.content))
return;
}
//
if (msg.type == enums.MESSAGE_TYPE.READED) {
store.commit("resetUnreadCount", {
this.chatStore.resetUnreadCount({
type: 'PRIVATE',
targetId: msg.recvId
})
@ -103,19 +120,18 @@
}
// ,
if (msg.type == enums.MESSAGE_TYPE.RECEIPT) {
store.commit("readedMessage", {
this.chatStore.readedMessage({
friendId: msg.sendId
})
return;
}
//
msg.selfSend = msg.sendId == store.state.userStore.userInfo.id;
msg.selfSend = msg.sendId == this.userStore.userInfo.id;
// id
let friendId = msg.selfSend ? msg.recvId : msg.sendId;
this.loadFriendInfo(friendId).then((friend) => {
this.loadFriendInfo(friendId, (friend) => {
this.insertPrivateMessage(friend, msg);
})
},
insertPrivateMessage(friend, msg) {
//
@ -151,9 +167,9 @@
headImage: friend.headImage
};
//
store.commit("openChat", chatInfo);
this.chatStore.openChat(chatInfo);
//
store.commit("insertMessage", msg);
this.chatStore.insertMessage(msg);
//
this.playAudioTip();
@ -161,7 +177,7 @@
handleGroupMessage(msg) {
//
if (msg.type == enums.MESSAGE_TYPE.LOADING) {
store.commit("loadingGroupMsg", JSON.parse(msg.content))
this.chatStore.setLoadingGroupMsg(JSON.parse(msg.content))
return;
}
//
@ -171,7 +187,7 @@
type: 'GROUP',
targetId: msg.groupId
}
store.commit("resetUnreadCount", chatInfo)
this.chatStore.resetUnreadCount(chatInfo)
return;
}
//
@ -183,12 +199,12 @@
readedCount: msg.readedCount,
receiptOk: msg.receiptOk
};
store.commit("updateMessage", msgInfo)
this.chatStore.updateMessage(msgInfo)
return;
}
//
msg.selfSend = msg.sendId == store.state.userStore.userInfo.id;
this.loadGroupInfo(msg.groupId).then((group) => {
msg.selfSend = msg.sendId == this.userStore.userInfo.id;
this.loadGroupInfo(msg.groupId, (group) => {
//
this.insertGroupMessage(group, msg);
})
@ -241,43 +257,39 @@
headImage: group.headImageThumb
};
//
store.commit("openChat", chatInfo);
this.chatStore.openChat(chatInfo);
//
store.commit("insertMessage", msg);
this.chatStore.insertMessage(msg);
//
this.playAudioTip();
},
loadFriendInfo(id) {
return new Promise((resolve, reject) => {
let friend = store.getters.findFriend(id);
if (friend) {
resolve(friend);
} else {
http({
url: `/friend/find/${id}`,
method: 'GET'
}).then((friend) => {
store.commit("addFriend", friend);
resolve(friend)
})
}
});
loadFriendInfo(id, callback) {
let friend = this.friendStore.findFriend(id);
if (friend) {
callback(friend);
} else {
http({
url: `/friend/find/${id}`,
method: 'GET'
}).then((friend) => {
this.friendStore.addFriend(friend);
callback(friend)
})
}
},
loadGroupInfo(id) {
return new Promise((resolve, reject) => {
let group = store.state.groupStore.groups.find((g) => g.id == id);
if (group) {
resolve(group);
} else {
http({
url: `/group/find/${id}`,
method: 'GET'
}).then((group) => {
resolve(group)
store.commit("addGroup", group);
})
}
});
loadGroupInfo(id, callback) {
let group = this.groupStore.findGroup(id);
if (group) {
callback(group);
} else {
http({
url: `/group/find/${id}`,
method: 'GET'
}).then((group) => {
this.groupStore.addGroup(group);
callback(group)
})
}
},
exit() {
console.log("exit");
@ -287,7 +299,7 @@
uni.reLaunch({
url: "/pages/login/login"
})
store.dispatch("unload");
this.unloadStore();
},
playAudioTip() {
//
@ -314,7 +326,7 @@
title: '连接已断开,尝试重新连接...',
icon: 'none',
})
store.commit("setUserInfo", userInfo);
this.userStore.setUserInfo(userInfo);
//
let loginInfo = uni.getStorageSync("loginInfo")
wsApi.reconnect(UNI_APP.WS_URL, loginInfo.accessToken);
@ -333,10 +345,10 @@
}
},
onLaunch() {
this.$mountStore();
//
let loginInfo = uni.getStorageSync("loginInfo")
if (!this.isExpired(loginInfo)) {
console.log("初始化")
//
this.init();
//
@ -367,7 +379,7 @@
.tab-page {
// #ifdef H5
height: calc(100vh - 50px); // h5100vh
height: calc(100vh - 50px); // h5100vh
// #endif
// #ifndef H5
height: calc(100vh);

4
im-uniapp/common/request.js

@ -21,7 +21,7 @@ const request = (options) => {
if (res.data.code == 200) {
return resolve(res.data.data)
} else if (res.data.code == 400) {
getApp().exit();
getApp().$vm.exit();
} else if (res.data.code == 401) {
console.log("token失效,尝试重新获取")
if (isRefreshToken) {
@ -38,7 +38,7 @@ const request = (options) => {
requestList = [];
isRefreshToken = false;
console.log("刷新token失败")
getApp().exit();
getApp().$vm.exit();
return;
}
let newInfo = res.data.data;

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

@ -56,7 +56,7 @@
methods: {
init(atUserIds) {
this.showMembers = [];
let userId = this.$store.state.userStore.userInfo.id;
let userId = this.userStore.userInfo.id;
if(this.ownerId == userId){
this.showMembers.push({
userId:-1,
@ -64,7 +64,7 @@
})
}
this.members.forEach((m) => {
if(m.userId != userId){
if(!m.quit && m.userId != userId){
m.checked = atUserIds.indexOf(m.userId) >= 0;
this.showMembers.push(m);
}

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

@ -79,7 +79,7 @@
this.items[0] = `已读(${this.readedMembers.length})`;
this.items[1] = `未读(${this.unreadMembers.length})`;
//
this.$store.commit("updateMessage", {
this.chatStore.updateMessage({
id: this.msgInfo.id,
groupId: this.msgInfo.groupId,
readedCount: this.readedMembers.length

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

@ -17,7 +17,7 @@
</view>
<view class="chat-content">
<view class="chat-at-text">{{atText}}</view>
<view class="chat-send-name" v-show="chat.sendNickName">{{chat.sendNickName+':&nbsp;'}}</view>
<view class="chat-send-name" v-if="isShowSendName">{{chat.sendNickName+':&nbsp;'}}</view>
<rich-text class="chat-content-text" :nodes="$emo.transform(chat.lastContent)"></rich-text>
<uni-badge v-if="chat.unreadCount>0" size="small" :max-num="99" :text="chat.unreadCount" />
</view>
@ -44,6 +44,7 @@
}
},
methods: {
showChatBox() {
uni.navigateTo({
url: "/pages/chat/chat-box?chatIdx=" + this.index
@ -51,6 +52,18 @@
}
},
computed: {
isShowSendName() {
if (!this.chat.sendNickName) {
return false;
}
let size = this.chat.messages.length;
if (size == 0) {
return false;
}
//
let lastMsg = this.chat.messages[size - 1];
return this.$msgType.isNormal(lastMsg.type)
},
atText() {
if (this.chat.atMe) {
return "[有人@我]"

2
im-uniapp/components/group-rtc-join/group-rtc-join.vue

@ -41,7 +41,7 @@
},
onOk() {
let users = this.rtcInfo.userInfos;
let mine = this.$store.state.userStore.userInfo;
let mine = this.userStore.userInfo;
//
if(!users.find((user)=>user.id==mine.id)){
users.push({

22
im-uniapp/main.js

@ -5,9 +5,15 @@ import * as enums from './common/enums.js';
import * as date from './common/date';
import * as socketApi from './common/wssocket';
import * as messageType from './common/messageType';
import store from './store';
import { createSSRApp } from 'vue'
import uviewPlus from '@/uni_modules/uview-plus'
import * as pinia from 'pinia';
import useChatStore from '@/store/chatStore.js'
import useFriendStore from '@/store/friendStore.js'
import useGroupStore from '@/store/groupStore.js'
import useConfigStore from '@/store/configStore.js'
import useUserStore from '@/store/userStore.js'
// #ifdef H5
import * as recorder from './common/recorder-h5';
// #endif
@ -16,10 +22,11 @@ import * as recorder from './common/recorder-app';
// #endif
export function createApp() {
const app = createSSRApp(App)
app.use(store);
app.use(uviewPlus);
app.use(pinia.createPinia());
app.config.globalProperties.$http = request;
app.config.globalProperties.$wsApi = socketApi;
app.config.globalProperties.$msgType = messageType;
@ -27,7 +34,16 @@ export function createApp() {
app.config.globalProperties.$enums = enums;
app.config.globalProperties.$date = date;
app.config.globalProperties.$rc = recorder;
// 初始化时再挂载store对象
app.config.globalProperties.$mountStore = ()=>{
app.config.globalProperties.chatStore = useChatStore();
app.config.globalProperties.friendStore = useFriendStore();
app.config.globalProperties.groupStore = useGroupStore();
app.config.globalProperties.configStore = useConfigStore();
app.config.globalProperties.userStore = useUserStore();
}
return {
app
app,
pinia
}
}

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

@ -9,7 +9,7 @@
<scroll-view class="scroll-box" scroll-y="true" upper-threshold="200" @scrolltoupper="onScrollToTop"
:scroll-into-view="'chat-item-'+scrollMsgIdx">
<view v-if="chat" v-for="(msgInfo,idx) in chat.messages" :key="idx">
<chat-message-item v-if="idx>=showMinIdx&&!msgInfo.delete" :headImage="headImage(msgInfo)"
<chat-message-item v-if="idx>=showMinIdx" :headImage="headImage(msgInfo)"
@call="onRtCall(msgInfo)" :showName="showName(msgInfo)" @recall="onRecallMessage"
@delete="onDeleteMessage" @longPressHead="onLongPressHead(msgInfo)" @download="onDownloadFile"
:id="'chat-item-'+idx" :msgInfo="msgInfo" :groupMembers="groupMembers">
@ -108,15 +108,18 @@
<chat-at-box ref="atBox" :ownerId="group.ownerId" :members="groupMembers"
@complete="onAtComplete"></chat-at-box>
<!-- 群语音通话时选择成员 -->
<!-- #ifndef MP-WEIXIN -->
<group-member-selector ref="selBox" :members="groupMembers"
:maxSize="$store.state.configStore.webrtc.maxChannel"
:maxSize="configStore.webrtc.maxChannel"
@complete="onInviteOk"></group-member-selector>
<group-rtc-join ref="rtcJoin" :groupId="group.id"></group-rtc-join>
<!-- #endif -->
</view>
</template>
<script>
import UNI_APP from '@/.env.js';
export default {
data() {
return {
@ -157,7 +160,7 @@
this.fillTargetId(msgInfo, this.chat.targetId);
this.sendMessageRequest(msgInfo).then((m) => {
m.selfSend = true;
this.$store.commit("insertMessage", m);
this.chatStore.insertMessage(m);
//
this.moveChatToTop();
//
@ -226,8 +229,8 @@
})
},
moveChatToTop() {
let chatIdx = this.$store.getters.findChatIdx(this.chat);
this.$store.commit("moveTop", chatIdx);
let chatIdx = this.chatStore.findChatIdx(this.chat);
this.chatStore.moveTop(chatIdx);
},
switchReceipt() {
this.isReceipt = !this.isReceipt;
@ -261,6 +264,7 @@
}
},
sendTextMessage() {
const timeStamp = new Date().getTime();
if (!this.sendText.trim() && this.atUserIds.length == 0) {
return uni.showToast({
title: "不能发送空白信息",
@ -279,8 +283,10 @@
// id
this.fillTargetId(msgInfo, this.chat.targetId);
this.sendMessageRequest(msgInfo).then((m) => {
console.log("请求耗时:",new Date().getTime()-timeStamp)
m.selfSend = true;
this.$store.commit("insertMessage", m);
this.chatStore.insertMessage(m);
console.log("insertMessage耗时:",new Date().getTime()-timeStamp)
//
this.moveChatToTop();
}).finally(() => {
@ -387,7 +393,7 @@
// id
this.fillTargetId(msgInfo, this.chat.targetId);
//
this.$store.commit("insertMessage", msgInfo);
this.chatStore.insertMessage(msgInfo);
//
this.moveChatToTop();
// file
@ -404,13 +410,13 @@
msgInfo.loadStatus = 'ok';
msgInfo.id = m.id;
this.isReceipt = false;
this.$store.commit("insertMessage", msgInfo);
this.chatStore.insertMessage(msgInfo);
})
},
onUploadImageFail(file, err) {
let msgInfo = JSON.parse(JSON.stringify(file.msgInfo));
msgInfo.loadStatus = 'fail';
this.$store.commit("insertMessage", msgInfo);
this.chatStore.insertMessage(msgInfo);
},
onUploadFileBefore(file) {
let data = {
@ -433,7 +439,7 @@
// id
this.fillTargetId(msgInfo, this.chat.targetId);
//
this.$store.commit("insertMessage", msgInfo);
this.chatStore.insertMessage(msgInfo);
//
this.moveChatToTop();
// file
@ -455,13 +461,13 @@
msgInfo.loadStatus = 'ok';
msgInfo.id = m.id;
this.isReceipt = false;
this.$store.commit("insertMessage", msgInfo);
this.chatStore.insertMessage(msgInfo);
})
},
onUploadFileFail(file, res) {
let msgInfo = JSON.parse(JSON.stringify(file.msgInfo));
msgInfo.loadStatus = 'fail';
this.$store.commit("insertMessage", msgInfo);
this.chatStore.insertMessage(msgInfo);
},
onDeleteMessage(msgInfo) {
uni.showModal({
@ -469,7 +475,7 @@
content: '确认删除消息?',
success: (res) => {
if (!res.cancel) {
this.$store.commit("deleteMessage", msgInfo);
this.chatStore.deleteMessage(msgInfo);
uni.showToast({
title: "删除成功",
icon: "none"
@ -493,7 +499,7 @@
msgInfo.type = this.$enums.MESSAGE_TYPE.RECALL;
msgInfo.content = '你撤回了一条消息';
msgInfo.status = this.$enums.MESSAGE_STATUS.RECALL;
this.$store.commit("insertMessage", msgInfo);
this.chatStore.insertMessage(msgInfo);
})
}
}
@ -553,13 +559,13 @@
})
}
},
loadReaded(fId) {
loadReaded(fid) {
this.$http({
url: `/message/private/maxReadedId?friendId=${fId}`,
url: `/message/private/maxReadedId?friendId=${fid}`,
method: 'get'
}).then((id) => {
this.$store.commit("readedMessage", {
friendId: fId,
this.chatStore.readedMessage({
friendId: fid,
maxId: id
});
});
@ -578,7 +584,7 @@
url: url,
method: 'PUT'
}).then(() => {
this.$store.commit("resetUnreadCount", this.chat)
this.chatStore.resetUnreadCount(this.chat)
this.scrollToBottom();
})
},
@ -588,8 +594,8 @@
method: 'GET'
}).then((group) => {
this.group = group;
this.$store.commit("updateChatFromGroup", group);
this.$store.commit("updateGroup", group);
this.chatStore.updateChatFromGroup(group);
this.groupStore.updateGroup(group);
});
this.$http({
@ -606,8 +612,8 @@
method: 'GET'
}).then((friend) => {
this.friend = friend;
this.$store.commit("updateChatFromFriend", friend);
this.$store.commit("updateFriend", friend);
this.chatStore.updateChatFromFriend(friend);
this.friendStore.updateFriend(friend);
})
},
rpxTopx(rpx) {
@ -649,7 +655,7 @@
},
computed: {
mine() {
return this.$store.state.userStore.userInfo;
return this.userStore.userInfo;
},
title() {
if (!this.chat) {
@ -719,7 +725,7 @@
},
onLoad(options) {
//
this.chat = this.$store.state.chatStore.chats[options.chatIdx];
this.chat = this.chatStore.chats[options.chatIdx];
// 20
let size = this.messageSize;
this.showMinIdx = size > 20 ? size - 20 : 0;
@ -733,13 +739,10 @@
this.loadReaded(this.chat.targetId)
}
//
this.$store.commit("activeChat", options.chatIdx);
this.chatStore.activeChat(options.chatIdx);
//
this.isReceipt = false;
},
onUnload() {
this.$store.commit("activeChat", -1);
},
onShow(){
if(this.needScrollToBottom){
//
@ -835,7 +838,7 @@
margin-bottom: 10rpx;
border: #dddddd solid 1px;
background-color: #f7f8fd;
height: 80rpx;
.iconfont {
font-size: 68rpx;
margin: 6rpx;

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

@ -67,12 +67,12 @@
this.url = "/hybrid/html/rtc-group/index.html?";
this.url += "baseUrl=" + UNI_APP.BASE_URL;
this.url += "&groupId=" + this.groupId;
this.url += "&userId=" + this.$store.state.userStore.userInfo.id;
this.url += "&userId=" + this.userStore.userInfo.id;
this.url += "&inviterId=" + this.inviterId;
this.url += "&isHost=" + this.isHost;
this.url += "&loginInfo=" + JSON.stringify(uni.getStorageSync("loginInfo"));
this.url += "&userInfos=" + JSON.stringify(this.userInfos);
this.url += "&config=" + JSON.stringify(this.$store.state.configStore.webrtc);
this.url += "&config=" + JSON.stringify(this.configStore.webrtc);
},
},
onBackPress() {

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

@ -62,9 +62,9 @@
this.url += "&isHost="+this.isHost;
this.url += "&baseUrl="+UNI_APP.BASE_URL;
this.url += "&loginInfo="+JSON.stringify(uni.getStorageSync("loginInfo"));
this.url += "&userInfo="+JSON.stringify(this.$store.state.userStore.userInfo);
this.url += "&userInfo="+JSON.stringify(this.userStore.userInfo);
this.url += "&friend="+JSON.stringify(this.friend);
this.url += "&config=" + JSON.stringify(this.$store.state.configStore.webrtc);
this.url += "&config=" + JSON.stringify(this.configStore.webrtc);
},
},
onBackPress() {

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

@ -14,11 +14,11 @@
温馨提示您现在还没有任何聊天消息快跟您的好友发起聊天吧~
</view>
<scroll-view class="scroll-bar" v-else scroll-with-animation="true" scroll-y="true">
<view v-for="(chatPos,i) in chatsPos" :key="i">
<pop-menu v-if="isShowChat(chatStore.chats[chatPos.idx])" :items="menu.items"
@select="onSelectMenu($event,chatPos.idx)">
<chat-item :chat="chatStore.chats[chatPos.idx]"
:active="menu.chatIdx==chatPos.idx" :index="chatPos.idx"></chat-item>
<view v-for="(chat,index) in chatStore.chats" :key="index">
<pop-menu v-if="isShowChat(chat)" :items="menu.items"
@select="onSelectMenu($event,index)">
<chat-item :chat="chat" :index="index"
:active="menu.chatIdx==index"></chat-item>
</pop-menu>
</view>
</scroll-view>
@ -26,6 +26,8 @@
</template>
<script>
import useChatStore from '@/store/chatStore.js'
export default {
data() {
return {
@ -65,10 +67,10 @@
this.menu.show = false;
},
removeChat(chatIdx) {
this.$store.commit("removeChat", chatIdx);
this.chatStore.removeChat(chatIdx);
},
moveToTop(chatIdx) {
this.$store.commit("moveTop", chatIdx);
this.chatStore.moveTop(chatIdx);
},
isShowChat(chat){
if(chat.delete){
@ -87,29 +89,10 @@
index: 0,
complete: () => {}
})
}
}
},
computed: {
chatsPos() {
//
let chatsPos = [];
let chats = this.chatStore.chats;
chats.forEach((chat, idx) => {
chatsPos.push({
idx: idx,
sendTime: chat.lastSendTime
})
})
chatsPos.sort((chatPos1, chatPos2) => {
return chatPos2.sendTime - chatPos1.sendTime;
});
return chatsPos;
},
chatStore() {
return this.$store.state.chatStore;
},
unreadCount() {
let count = 0;
this.chatStore.chats.forEach(chat => {

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

@ -1,7 +1,7 @@
<template>
<view class="page user-info">
<view class="content">
<head-image :name="userInfo.nickName" :url="userInfo.headImage"
<head-image :name="userInfo.nickName" :url="userInfo.headImageThumb"
:size="160" @click="onShowFullImage()"></head-image>
<view class="info-item">
@ -54,8 +54,8 @@
showName: this.userInfo.nickName,
headImage: this.userInfo.headImage,
};
this.$store.commit("openChat", chat);
let chatIdx = this.$store.getters.findChatIdx(chat);
this.chatStore.openChat(chat);
let chatIdx = this.chatStore.findChatIdx(chat);
uni.navigateTo({
url:"/pages/chat/chat-box?chatIdx=" + chatIdx
})
@ -71,7 +71,7 @@
headImage: this.userInfo.headImageThumb,
online: this.userInfo.online
}
this.$store.commit("addFriend", friend);
this.friendStore.addFriend(friend);
uni.showToast({
title: '对方已成为您的好友',
icon: 'none'
@ -81,7 +81,7 @@
onDelFriend(){
uni.showModal({
title: "确认删除",
content: `确认删除 '${this.userInfo.nickName}'的好友关系吗?`,
content: `确认删除 '${this.userInfo.nickName}',并删除聊天记录吗?`,
success: (res)=> {
if(res.cancel)
return;
@ -89,8 +89,8 @@
url: `/friend/delete/${this.userInfo.id}`,
method: 'delete'
}).then((data) => {
this.$store.commit("removeFriend", this.userInfo.id);
this.$store.commit("removePrivateChat", this.userInfo.id);
this.friendStore.removeFriend(this.userInfo.id);
this.chatStore.removePrivateChat(this.userInfo.id);
uni.showToast({
title: `与 '${this.userInfo.nickName}'的好友关系已解除`,
icon: 'none'
@ -110,9 +110,9 @@
data: friend
}).then(() => {
//
this.$store.commit("updateFriend", friend);
this.friendStore.updateFriend(friend);
//
this.$store.commit("updateChatFromFriend", this.userInfo);
this.chatStore.updateChatFromFriend(this.userInfo);
})
},
loadUserInfo(id){
@ -131,10 +131,10 @@
},
computed: {
isFriend() {
return this.friendInfo&&!this.friendInfo.delete;
return !!this.friendInfo;
},
friendInfo(){
let friends = this.$store.state.friendStore.friends;
let friends = this.friendStore.friends;
let friend = friends.find((f) => f.id == this.userInfo.id);
return friend;
}

8
im-uniapp/pages/friend/friend-add.vue

@ -6,7 +6,7 @@
</view>
<view class="user-items">
<scroll-view class="scroll-bar" scroll-with-animation="true" scroll-y="true">
<view v-for="(user) in users" :key="user.id" v-show="user.id != $store.state.userStore.userInfo.id">
<view v-for="(user) in users" :key="user.id" v-show="user.id != userStore.userInfo.id">
<view class="user-item">
<head-image :id="user.id" :name="user.nickName"
:online="user.online" :url="user.headImage"
@ -55,7 +55,7 @@
headImage: user.headImage,
online: user.online
}
this.$store.commit("addFriend", friend);
this.friendStore.addFriend(friend);
uni.showToast({
title: "添加成功,对方已成为您的好友",
icon: "none"
@ -68,9 +68,9 @@
})
},
isFriend(userId) {
let friends = this.$store.state.friendStore.friends;
let friends = this.friendStore.friends;
let friend = friends.find((f) => f.id == userId);
return friend&&!friend.delete;
return !!friend;
}
}
}

5
im-uniapp/pages/friend/friend.vue

@ -58,15 +58,12 @@
},
computed: {
friends() {
return this.$store.state.friendStore.friends;
return this.friendStore.friends;
},
friendGroupMap(){
//
let groupMap = new Map();
this.friends.forEach((f) => {
if (f.delete) {
return;
}
if(this.searchText && !f.nickName.includes(this.searchText)){
return;
}

26
im-uniapp/pages/group/group-edit.vue

@ -1,13 +1,13 @@
<template>
<view v-if="$store.state.userStore.userInfo.type == 1" class="page group-edit">
<view v-if="userStore.userInfo.type == 1" class="page group-edit">
<uni-forms ref="form" :modelValue="group" :rules="rules" validate-trigger="bind" label-position="top"
label-width="100%">
<uni-forms-item label="群聊头像:" name="headImage">
<image-upload v-show="isOwner" :onSuccess="onUnloadImageSuccess">
<image :src="group.headImage" class="group-image"></image>
<image-upload v-if="isOwner" :onSuccess="onUnloadImageSuccess">
<image :src="group.headImageThumb" class="group-image"></image>
</image-upload>
<head-image v-show="!isOwner" :name="group.showGroupName"
:url="group.headImage" :size="200"></head-image>
<head-image v-if="!isOwner" :name="group.showGroupName"
:url="group.headImageThumb" :size="200"></head-image>
</uni-forms-item>
<uni-forms-item label="群聊名称:" name="name" :required="true">
<uni-easyinput type="text" v-model="group.name" :disabled="!isOwner" placeholder="请输入群聊名称" />
@ -16,7 +16,7 @@
<uni-easyinput v-model="group.remarkGroupName" type="text" :placeholder="group.name" />
</uni-forms-item>
<uni-forms-item label="我在本群的昵称:" name="remarkNickName">
<uni-easyinput v-model="group.remarkNickName" type="text" :placeholder="$store.state.userStore.userInfo.nickName" />
<uni-easyinput v-model="group.remarkNickName" type="text" :placeholder="userStore.userInfo.nickName" />
</uni-forms-item>
<uni-forms-item label="群公告:" name="notice">
<uni-easyinput type="textarea" v-model="group.notice" :disabled="!isOwner" placeholder="请输入群公告" />
@ -61,7 +61,7 @@
method: "PUT",
data: this.group
}).then((group) => {
this.$store.commit("updateGroup", group);
this.groupStore.updateGroup(group);
uni.showToast({
title: "修改群聊信息成功",
icon: 'none'
@ -81,7 +81,7 @@
method: 'POST',
data: this.group
}).then((group) => {
this.$store.commit("addGroup", group);
this.groupStore.addGroup(group);
uni.showToast({
title: `群聊创建成功,快邀请小伙伴进群吧`,
icon: 'none',
@ -102,25 +102,25 @@
}).then((group) => {
this.group = group;
//
this.$store.commit("updateChatFromGroup", group);
this.chatStore.updateChatFromGroup(group);
//
this.$store.commit("updateGroup", group);
this.groupStore.updateGroup(group);
});
},
initNewGroup() {
let userInfo = this.$store.state.userStore.userInfo;
let userInfo = this.userStore.userInfo;
this.group = {
name: `${userInfo.userName}创建的群聊`,
headImage: userInfo.headImage,
headImageThumb: userInfo.headImageThumb,
ownerId: this.$store.state.userStore.userInfo.id
ownerId: this.userStore.userInfo.id
}
}
},
computed: {
isOwner() {
return this.$store.state.userStore.userInfo.id == this.group.ownerId
return this.userStore.userInfo.id == this.group.ownerId
}
},
onLoad(options) {

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

@ -1,5 +1,5 @@
<template>
<view v-if="$store.state.userStore.userInfo.type == 1" class="page group-info">
<view v-if="userStore.userInfo.type == 1" class="page group-info">
<view v-if="!group.quit" class="group-members">
<view class="member-items">
<view v-for="(member,idx) in groupMembers" :key="idx">
@ -31,7 +31,7 @@
<uni-section title="群名备注:" titleFontSize="14px">
<template v-slot:right>
<text class="detail-text"> {{group.showGroupName}}</text>
<text class="detail-text"> {{group.remarkGroupName}}</text>
</template>
</uni-section>
<uni-section title="我在本群的昵称:" titleFontSize="14px">
@ -85,8 +85,8 @@
showName: this.group.showGroupName,
headImage: this.group.headImage,
};
this.$store.commit("openChat", chat);
let chatIdx = this.$store.getters.findChatIdx(chat);
this.chatStore.openChat(chat);
let chatIdx = this.chatStore.findChatIdx(chat);
uni.navigateTo({
url: "/pages/chat/chat-box?chatIdx=" + chatIdx
})
@ -111,8 +111,8 @@
uni.switchTab({
url:"/pages/group/group"
});
this.$store.commit("removeGroup", this.groupId);
this.$store.commit("removeGroupChat",this.groupId);
this.groupStore.removeGroup(this.groupId);
this.chatStore.removeGroupChat(this.groupId);
},100)
}
})
@ -121,7 +121,6 @@
});
},
onDissolveGroup() {
console.log(this.group.name)
uni.showModal({
title: '确认解散?',
content: `确认要解散群聊'${this.group.name}'吗?`,
@ -141,8 +140,8 @@
uni.switchTab({
url:"/pages/group/group"
});
this.$store.commit("removeGroup", this.groupId);
this.$store.commit("removeGroupChat",this.groupId);
this.groupStore.removeGroup(this.groupId);
this.chatStore.removeGroupChat(this.groupId);
},100)
}
})
@ -158,9 +157,9 @@
}).then((group) => {
this.group = group;
//
this.$store.commit("updateChatFromGroup", group);
this.chatStore.updateChatFromGroup(group);
//
this.$store.commit("updateGroup", group);
this.groupStore.updateGroup(group);
});
},
@ -180,7 +179,7 @@
return member && member.showNickName;
},
isOwner() {
return this.group.ownerId == this.$store.state.userStore.userInfo.id;
return this.group.ownerId == this.userStore.userInfo.id;
}
},
onLoad(options) {

9
im-uniapp/pages/group/group-invite.vue

@ -1,5 +1,5 @@
<template>
<view v-if="$store.state.userStore.userInfo.type == 1" class="page group-invite">
<view class="page group-invite">
<view class="search-bar">
<uni-search-bar v-model="searchText" radius="100" cancelButton="none" placeholder="输入好友昵称搜索"></uni-search-bar>
</view>
@ -17,7 +17,6 @@
<radio :checked="friend.checked" :disabled="friend.disabled" @click.stop="onSwitchChecked(friend)"/>
</view>
</view>
</view>
</scroll-view>
</view>
@ -78,15 +77,11 @@
if (!friend.disabled) {
friend.checked = !friend.checked;
}
console.log(this.inviteSize)
},
initFriendItems() {
this.friendItems = [];
let friends = this.$store.state.friendStore.friends;
let friends = this.friendStore.friends;
friends.forEach((f => {
if(f.delete){
return
}
let item = {
id: f.id,
headImage: f.headImage,

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

@ -1,5 +1,5 @@
<template>
<view v-if="$store.state.userStore.userInfo.type == 1" class="page group-member">
<view class="page group-member">
<view class="search-bar">
<uni-search-bar v-model="searchText" radius="100" cancelButton="none" placeholder="输入成员昵称搜索"></uni-search-bar>
</view>
@ -79,12 +79,12 @@
})
},
isSelf(userId) {
return this.$store.state.userStore.userInfo.id == userId
return this.userStore.userInfo.id == userId
}
},
computed: {
isOwner() {
return this.$store.state.userStore.userInfo.id == this.group.ownerId;
return this.userStore.userInfo.id == this.group.ownerId;
}
},
onLoad(options) {

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

@ -9,12 +9,12 @@
<uni-icons type="personadd" size="35"></uni-icons>
</view>
</view>
<view class="group-tip" v-if="$store.state.groupStore.groups.length==0">
<view class="group-tip" v-if="groupStore.groups.length==0">
温馨提示您现在还没有加入任何群聊点击右上方'+'按钮可以创建群聊哦~
</view>
<view class="group-items" v-else>
<scroll-view class="scroll-bar" scroll-with-animation="true" scroll-y="true">
<view v-for="group in $store.state.groupStore.groups" :key="group.id">
<view v-for="group in groupStore.groups" :key="group.id">
<group-item v-if="!group.quit&&group.showGroupName.includes(searchText)"
:group="group"></group-item>
</view>

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

@ -54,7 +54,7 @@
loginInfo.expireTime = new Date().getTime() + loginInfo.refreshTokenExpiresIn * 1000;
uni.setStorageSync("loginInfo", loginInfo);
// App.vue
getApp().init()
getApp().$vm.init()
//
uni.switchTab({
url: "/pages/chat/chat"
@ -72,8 +72,6 @@
<style lang="scss" scoped>
.login {
.title {
padding-top: 150rpx;
padding-bottom: 50rpx;

4
im-uniapp/pages/mine/mine-edit.vue

@ -48,7 +48,7 @@
method: "PUT",
data: this.userInfo
}).then(()=>{
this.$store.commit("setUserInfo",this.userInfo);
this.userStore.setUserInfo(this.userInfo);
uni.showToast({
title:"修改成功",
icon: 'none'
@ -61,7 +61,7 @@
},
onLoad() {
//
let mine = this.$store.state.userStore.userInfo;
let mine = this.userStore.userInfo;
this.userInfo = JSON.parse(JSON.stringify(mine));
}
}

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

@ -52,7 +52,8 @@
title: '确认退出?',
success: (res) => {
if (res.confirm) {
getApp().exit()
console.log(getApp())
getApp().$vm.exit()
}
}
});
@ -60,7 +61,7 @@
},
computed: {
userInfo() {
return this.$store.state.userStore.userInfo;
return this.userStore.userInfo;
}
}

352
im-uniapp/store/chatStore.js

@ -1,48 +1,34 @@
import {
MESSAGE_TYPE,
MESSAGE_STATUS
} from '@/common/enums.js';
import userStore from './userStore';
/*
uniapp性能优化
1.由于uniapp渲染消息性能非常拉胯,所以先把离线消息存储到cacheChats,
待所有离线消息拉取完成后再统一进行渲染
2.在vuex中对数组进行unshift,splice特别卡所以删除会话会话置顶
除消息等操作进行优化不通过unshift,splice实现改造方案如下
删除会话 通过delete标志判断是否删除
删除消息通过delete标志判断是否删除
会话置顶通过lastSendTime排序确定会话顺序
*/
import { defineStore } from 'pinia';
import { MESSAGE_TYPE, MESSAGE_STATUS } from '@/common/enums.js';
import useUserStore from './userStore';
let cacheChats = [];
export default {
state: {
chats: [],
privateMsgMaxId: 0,
groupMsgMaxId: 0,
loadingPrivateMsg: false,
loadingGroupMsg: false,
export default defineStore('chatStore', {
state: () => {
return {
chats: [],
privateMsgMaxId: 0,
groupMsgMaxId: 0,
loadingPrivateMsg: false,
loadingGroupMsg: false
}
},
mutations: {
initChats(state, chatsData) {
actions: {
initChats(chatsData) {
cacheChats = [];
state.chats = [];
this.chats = [];
for (let chat of chatsData.chats) {
// 已删除的会话直接丢弃
if (chat.delete) {
continue;
}
// 暂存至缓冲区
chat.stored = false;
cacheChats.push(JSON.parse(JSON.stringify(chat)));
// 加载期间显示只前15个会话做做样子,一切都为了加快初始化时间
if (state.chats.length < 15) {
if (this.chats.length < 15) {
chat.messages = [];
state.chats.push(chat);
this.chats.push(chat);
}
}
state.privateMsgMaxId = chatsData.privateMsgMaxId || 0;
state.groupMsgMaxId = chatsData.groupMsgMaxId || 0;
this.privateMsgMaxId = chatsData.privateMsgMaxId || 0;
this.groupMsgMaxId = chatsData.groupMsgMaxId || 0;
// 防止图片一直处在加载中状态
cacheChats.forEach((chat) => {
chat.messages.forEach((msg) => {
@ -52,16 +38,15 @@ export default {
})
})
},
openChat(state, chatInfo) {
let chats = this.getters.findChats();
openChat(chatInfo) {
let chats = this.curChats;
let chat = null;
for (let idx in chats) {
if (chats[idx].type == chatInfo.type &&
chats[idx].targetId === chatInfo.targetId) {
chat = chats[idx];
chat.delete = false;
// 放置头部
this.commit("moveTop", idx)
this.moveTop(idx)
break;
}
}
@ -78,97 +63,106 @@ export default {
messages: [],
atMe: false,
atAll: false,
delete: false
stored: false
};
chats.push(chat);
this.commit("moveTop", chats.length - 1)
chats.unshift(chat);
this.saveToStorage();
}
},
activeChat(state, idx) {
let chats = this.getters.findChats();
activeChat(idx) {
let chats = this.curChats;
if (idx >= 0) {
chats[idx].unreadCount = 0;
}
},
resetUnreadCount(state, chatInfo) {
let chats = this.getters.findChats();
resetUnreadCount(chatInfo) {
let chats = this.curChats;
for (let idx in chats) {
if (chats[idx].type == chatInfo.type &&
chats[idx].targetId == chatInfo.targetId) {
chats[idx].unreadCount = 0;
chats[idx].atMe = false;
chats[idx].atAll = false;
chats[idx].stored = false;
this.saveToStorage();
}
}
this.commit("saveToStorage");
},
readedMessage(state, pos) {
let chats = this.getters.findChats();
for (let idx in chats) {
if (chats[idx].type == 'PRIVATE' &&
chats[idx].targetId == pos.friendId) {
chats[idx].messages.forEach((m) => {
if (m.selfSend && m.status != MESSAGE_STATUS.RECALL) {
// pos.maxId为空表示整个会话已读
if (!pos.maxId || m.id <= pos.maxId) {
m.status = MESSAGE_STATUS.READED
}
}
})
readedMessage(pos) {
let chat = this.findChatByFriend(pos.friendId);
chat.messages.forEach((m) => {
if (m.id && m.selfSend && m.status < MESSAGE_STATUS.RECALL) {
// pos.maxId为空表示整个会话已读
if (!pos.maxId || m.id <= pos.maxId) {
m.status = MESSAGE_STATUS.READED
chat.stored = false;
}
}
})
if(!chat.stored){
this.saveToStorage();
}
this.commit("saveToStorage");
},
removeChat(state, idx) {
let chats = this.getters.findChats();
removeChat(idx) {
let chats = this.curChats;
chats[idx].delete = true;
this.commit("saveToStorage");
chats[idx].stored = false;
this.saveToStorage();
},
removePrivateChat(state, userId) {
let chats = this.getters.findChats();
removePrivateChat(userId) {
let chats = this.curChats;
for (let idx in chats) {
if (chats[idx].type == 'PRIVATE' &&
chats[idx].targetId == userId) {
this.commit("removeChat", idx);
this.removeChat(idx);
}
}
},
removeGroupChat(state, groupId) {
let chats = this.getters.findChats();
removeGroupChat(groupId) {
let chats = this.curChats;
for (let idx in chats) {
if (chats[idx].type == 'GROUP' &&
chats[idx].targetId == groupId) {
this.commit("removeChat", idx);
this.removeChat(idx);
}
}
},
moveTop(state, idx) {
let chats = this.getters.findChats();
let chat = chats[idx];
// 最新的时间会显示在顶部
chat.lastSendTime = new Date().getTime();
this.commit("saveToStorage");
moveTop(idx) {
if (this.isLoading()) {
return;
}
let chats = this.curChats;
if (idx > 0) {
let chat = chats[idx];
chats.splice(idx, 1);
chats.unshift(chat);
chat.lastSendTime = new Date().getTime();
chat.stored = false;
this.saveToStorage();
}
},
insertMessage(state, msgInfo) {
insertMessage(msgInfo) {
// 获取对方id或群id
let type = msgInfo.groupId ? 'GROUP' : 'PRIVATE';
// 记录消息的最大id
if (msgInfo.id && type == "PRIVATE" && msgInfo.id > state.privateMsgMaxId) {
state.privateMsgMaxId = msgInfo.id;
if (msgInfo.id && type == "PRIVATE" && msgInfo.id > this.privateMsgMaxId) {
this.privateMsgMaxId = msgInfo.id;
}
if (msgInfo.id && type == "GROUP" && msgInfo.id > state.groupMsgMaxId) {
state.groupMsgMaxId = msgInfo.id;
if (msgInfo.id && type == "GROUP" && msgInfo.id > this.groupMsgMaxId) {
this.groupMsgMaxId = msgInfo.id;
}
// 如果是已存在消息,则覆盖旧的消息数据
let chat = this.getters.findChat(msgInfo);
let message = this.getters.findMessage(chat, msgInfo);
let chat = this.findChat(msgInfo);
let message = this.findMessage(chat, msgInfo);
if (message) {
Object.assign(message, msgInfo);
// 撤回消息需要显示
if (msgInfo.type == MESSAGE_TYPE.RECALL) {
chat.lastContent = msgInfo.content;
}
this.commit("saveToStorage");
chat.stored = false;
this.saveToStorage();
return;
}
// 会话列表内容
@ -178,16 +172,17 @@ export default {
chat.lastContent = "[文件]";
} else if (msgInfo.type == MESSAGE_TYPE.AUDIO) {
chat.lastContent = "[语音]";
} else if (msgInfo.type == MESSAGE_TYPE.TEXT || msgInfo.type == MESSAGE_TYPE.RECALL) {
chat.lastContent = msgInfo.content;
} else if (msgInfo.type == MESSAGE_TYPE.ACT_RT_VOICE) {
chat.lastContent = "[语音通话]";
} else if (msgInfo.type == MESSAGE_TYPE.ACT_RT_VIDEO) {
chat.lastContent = "[视频通话]";
} else if (msgInfo.type == MESSAGE_TYPE.TEXT ||
msgInfo.type == MESSAGE_TYPE.RECALL ||
msgInfo.type == MESSAGE_TYPE.TIP_TEXT) {
chat.lastContent = msgInfo.content;
}
chat.lastSendTime = msgInfo.sendTime;
chat.sendNickName = msgInfo.sendNickName;
// 未读加1
if (!msgInfo.selfSend && msgInfo.status != MESSAGE_STATUS.READED &&
msgInfo.type != MESSAGE_TYPE.TIP_TEXT) {
@ -196,7 +191,8 @@ export default {
// 是否有人@我
if (!msgInfo.selfSend && chat.type == "GROUP" && msgInfo.atUserIds &&
msgInfo.status != MESSAGE_STATUS.READED) {
let userId = userStore.state.userInfo.id;
const userStore = useUserStore();
let userId = userStore.userInfo.id;
if (msgInfo.atUserIds.indexOf(userId) >= 0) {
chat.atMe = true;
}
@ -230,70 +226,71 @@ export default {
} else {
chat.messages.splice(insertPos, 0, msgInfo);
}
this.commit("saveToStorage");
chat.stored = false;
this.saveToStorage();
},
updateMessage(state, msgInfo) {
updateMessage(msgInfo) {
// 获取对方id或群id
let chat = this.getters.findChat(msgInfo);
let message = this.getters.findMessage(chat, msgInfo);
let chat = this.findChat(msgInfo);
let message = this.findMessage(chat, msgInfo);
if (message) {
// 属性拷贝
Object.assign(message, msgInfo);
this.commit("saveToStorage");
chat.stored = false;
this.saveToStorage();
}
},
deleteMessage(state, msgInfo) {
deleteMessage(msgInfo) {
// 获取对方id或群id
let chat = this.getters.findChat(msgInfo);
let chat = this.findChat(msgInfo);
for (let idx in chat.messages) {
// 已经发送成功的,根据id删除
if (chat.messages[idx].id && chat.messages[idx].id == msgInfo.id) {
chat.messages[idx].delete = true;
chat.messages.splice(idx, 1);
break;
}
// 正在发送中的消息可能没有id,根据发送时间删除
if (msgInfo.selfSend && chat.messages[idx].selfSend &&
chat.messages[idx].sendTime == msgInfo.sendTime) {
chat.messages[idx].delete = true;
chat.messages.splice(idx, 1);
break;
}
}
this.commit("saveToStorage");
chat.stored = false;
this.saveToStorage();
},
updateChatFromFriend(state, friend) {
let chats = this.getters.findChats();
for (let i in chats) {
let chat = chats[i];
if (chat.type == 'PRIVATE' && chat.targetId == friend.id) {
chat.headImage = friend.headImageThumb;
chat.showName = friend.nickName;
break;
}
updateChatFromFriend(friend) {
let chat = this.findChatByFriend(friend.id)
if (chat && (chat.headImage != friend.headImageThumb ||
chat.showName != friend.nickName)) {
// 更新会话中的群名和头像
chat.headImage = friend.headImageThumb;
chat.showName = friend.nickName;
chat.stored = false;
this.saveToStorage();
}
this.commit("saveToStorage");
},
updateChatFromGroup(state, group) {
let chats = this.getters.findChats();
for (let i in chats) {
let chat = chats[i];
if (chat.type == 'GROUP' && chat.targetId == group.id) {
chat.headImage = group.headImageThumb;
chat.showName = group.showGroupName;
break;
}
updateChatFromGroup(group) {
let chat = this.findChatByGroup(group.id);
if (chat && (chat.headImage != group.headImageThumb ||
chat.showName != group.showGroupName)) {
// 更新会话中的群名称和头像
chat.headImage = group.headImageThumb;
chat.showName = group.showGroupName;
chat.stored = false;
this.saveToStorage();
}
this.commit("saveToStorage");
},
loadingPrivateMsg(state, loading) {
state.loadingPrivateMsg = loading;
if (!this.getters.isLoading()) {
this.commit("refreshChats")
setLoadingPrivateMsg(loading) {
this.loadingPrivateMsg = loading;
if (!this.isLoading()) {
this.refreshChats()
}
},
loadingGroupMsg(state, loading) {
state.loadingGroupMsg = loading;
if (!this.getters.isLoading()) {
this.commit("refreshChats")
setLoadingGroupMsg(loading) {
this.loadingGroupMsg = loading;
if (!this.isLoading()) {
this.refreshChats()
}
},
refreshChats(state) {
@ -302,51 +299,70 @@ export default {
return chat2.lastSendTime - chat1.lastSendTime;
});
// 将消息一次性装载回来
state.chats = cacheChats;
this.chats = cacheChats;
// 断线重连后不能使用缓存模式,否则会导致聊天窗口的消息不刷新
cacheChats = state.chats;
this.commit("saveToStorage");
cacheChats = this.chats;
this.saveToStorage();
},
saveToStorage(state) {
// 加载中不保存,防止卡顿
if (this.getters.isLoading()) {
if (this.isLoading()) {
return;
}
let userId = userStore.state.userInfo.id;
const userStore = useUserStore();
let userId = userStore.userInfo.id;
let key = "chats-app-" + userId;
let chatKeys = [];
// 按会话为单位存储,只存储有改动的会话
this.chats.forEach((chat)=>{
let chatKey = `${key}-${chat.type}-${chat.targetId}`
if(!chat.stored){
if(chat.delete){
uni.removeStorageSync(chatKey);
}else{
uni.setStorageSync(chatKey,chat);
}
chat.stored = true;
}
if(!chat.delete){
chatKeys.push(chatKey);
}
})
// 会话核心信息
let chatsData = {
privateMsgMaxId: state.privateMsgMaxId,
groupMsgMaxId: state.groupMsgMaxId,
chats: state.chats
privateMsgMaxId: this.privateMsgMaxId,
groupMsgMaxId: this.groupMsgMaxId,
chatKeys: chatKeys
}
uni.setStorage({
key: key,
data: chatsData ,
})
uni.setStorageSync(key, chatsData)
},
clear(state) {
cacheChats = [];
state.chats = [];
state.privateMsgMaxId = 0;
state.groupMsgMaxId = 0;
state.loadingPrivateMsg = false;
state.loadingGroupMsg = false;
}
},
actions: {
this.chats = [];
this.privateMsgMaxId = 0;
this.groupMsgMaxId = 0;
this.loadingPrivateMsg = false;
this.loadingGroupMsg = false;
},
loadChat(context) {
return new Promise((resolve, reject) => {
let userId = userStore.state.userInfo.id;
uni.getStorage({
key: "chats-app-" + userId,
success(res) {
context.commit("initChats", res.data);
resolve()
},
fail(e) {
resolve()
let userStore = useUserStore();
let userId = userStore.userInfo.id;
let chatsData = uni.getStorageSync("chats-app-" + userId)
if(chatsData){
if(chatsData.chatKeys){
let time = new Date().getTime();
chatsData.chats = [];
chatsData.chatKeys.forEach(key=>{
let chat = uni.getStorageSync(key);
if(chat){
chatsData.chats.push(chat);
}
})
}
});
this.initChats(chatsData);
}
resolve()
})
}
},
@ -354,11 +370,11 @@ export default {
isLoading: (state) => () => {
return state.loadingPrivateMsg || state.loadingGroupMsg
},
findChats: (state, getters) => () => {
return getters.isLoading() ? cacheChats : state.chats;
curChats: (state) => {
return state.isLoading() ? cacheChats : state.chats;
},
findChatIdx: (state, getters) => (chat) => {
let chats = getters.findChats();
findChatIdx: (state) => (chat) => {
let chats = state.curChats;
for (let idx in chats) {
if (chats[idx].type == chat.type &&
chats[idx].targetId === chat.targetId) {
@ -367,11 +383,12 @@ export default {
}
}
},
findChat: (state, getters) => (msgInfo) => {
let chats = getters.findChats();
findChat: (state) => (msgInfo) => {
let chats = state.curChats;
// 获取对方id或群id
let type = msgInfo.groupId ? 'GROUP' : 'PRIVATE';
let targetId = msgInfo.groupId ? msgInfo.groupId : msgInfo.selfSend ? msgInfo.recvId : msgInfo.sendId;
let targetId = msgInfo.groupId ? msgInfo.groupId : msgInfo.selfSend ? msgInfo.recvId : msgInfo
.sendId;
let chat = null;
for (let idx in chats) {
if (chats[idx].type == type &&
@ -382,6 +399,14 @@ export default {
}
return chat;
},
findChatByFriend: (state) => (fid) => {
return state.curChats.find(chat => chat.type == 'PRIVATE' &&
chat.targetId == fid)
},
findChatByGroup: (state) => (gid) => {
return state.curChats.find(chat => chat.type == 'GROUP' &&
chat.targetId == gid)
},
findMessage: (state) => (chat, msgInfo) => {
if (!chat) {
return null;
@ -394,10 +419,9 @@ export default {
// 正在发送中的消息可能没有id,只有tmpId
if (msgInfo.tmpId && chat.messages[idx].tmpId &&
chat.messages[idx].tmpId == msgInfo.tmpId) {
console.log("chat.messages[idx].tmpId == msgInfo.tmpId")
return chat.messages[idx];
}
}
}
}
}
});

34
im-uniapp/store/configStore.js

@ -1,32 +1,32 @@
import { defineStore } from 'pinia';
import http from '../common/request'
export default {
state: {
webrtc: {}
},
mutations: {
setConfig(state, config) {
state.webrtc = config.webrtc;
},
clear(state){
state.webrtc = {};
export default defineStore('configStore', {
state: () => {
return {
webrtc: {}
}
},
actions:{
loadConfig(context){
actions: {
setConfig(config) {
this.webrtc = config.webrtc;
},
clear() {
this.webrtc = {};
},
loadConfig() {
return new Promise((resolve, reject) => {
http({
url: '/system/config',
method: 'GET'
}).then((config) => {
console.log("系统配置",config)
context.commit("setConfig",config);
console.log("系统配置", config)
this.setConfig(config);
resolve();
}).catch((res)=>{
}).catch((res) => {
reject(res);
});
})
}
}
}
})

112
im-uniapp/store/friendStore.js

@ -1,95 +1,85 @@
import { defineStore } from 'pinia';
import http from '../common/request'
import {TERMINAL_TYPE} from '../common/enums.js'
import { TERMINAL_TYPE } from '../common/enums.js'
export default {
state: {
friends: [],
timer: null
export default defineStore('friendStore', {
state: () => {
return {
friends: [],
timer: null
}
},
mutations: {
setFriends(state, friends) {
friends.forEach((f)=>{
actions: {
setFriends(friends) {
friends.forEach((f) => {
f.online = false;
f.onlineWeb = false;
f.onlineApp = false;
})
state.friends = friends;
this.friends = friends;
},
updateFriend(state, friend) {
state.friends.forEach((f, index) => {
if (!f.delete && f.id == friend.id) {
// 拷贝属性
let online = state.friends[index].online;
Object.assign(state.friends[index], friend);
state.friends[index].online = online;
updateFriend(friend) {
let f = this.findFriend(friend.id);
let copy = JSON.parse(JSON.stringify(f));
Object.assign(f, friend);
f.online = copy.online;
f.onlineWeb = copy.onlineWeb;
f.onlineApp = copy.onlineApp;
},
removeFriend(id) {
this.friends.forEach((f, idx) => {
if (f.id == id) {
this.friends.splice(idx, 1)
}
})
},
removeFriend(state, id) {
let friend = this.getters.findFriend(id);
if(friend){
friend.delete = true;
}
addFriend(friend) {
this.friends.push(friend);
},
addFriend(state, friend) {
let f = this.getters.findFriend(friend.id);
if(f){
Object.assign(f, friend);
f.delete = false;
}else{
state.friends.push(friend);
}
},
setOnlineStatus(state, onlineTerminals) {
state.friends.forEach((f)=>{
let userTerminal = onlineTerminals.find((o)=> f.id==o.userId);
if(userTerminal){
setOnlineStatus(onlineTerminals) {
this.friends.forEach((f) => {
let userTerminal = onlineTerminals.find((o) => f.id == o.userId);
if (userTerminal) {
f.online = true;
f.onlineWeb = userTerminal.terminals.indexOf(TERMINAL_TYPE.WEB)>=0
f.onlineApp = userTerminal.terminals.indexOf(TERMINAL_TYPE.APP)>=0
}else{
f.onlineWeb = userTerminal.terminals.indexOf(TERMINAL_TYPE.WEB) >= 0
f.onlineApp = userTerminal.terminals.indexOf(TERMINAL_TYPE.APP) >= 0
} else {
f.online = false;
f.onlineWeb = false;
f.onlineApp = false;
}
});
},
refreshOnlineStatus(state) {
if (state.friends.length > 0) {
refreshOnlineStatus() {
if (this.friends.length > 0) {
let userIds = [];
state.friends.forEach((f) => {
userIds.push(f.id)
});
this.friends.forEach(f => userIds.push(f.id));
http({
url: '/user/terminal/online?userIds=' + userIds.join(','),
method: 'GET'
}).then((onlineTerminals) => {
this.commit("setOnlineStatus", onlineTerminals);
this.setOnlineStatus(onlineTerminals);
})
}
// 30s后重新拉取
clearTimeout(state.timer);
state.timer = setTimeout(() => {
this.commit("refreshOnlineStatus");
clearTimeout(this.timer);
this.timer = setTimeout(() => {
this.refreshOnlineStatus();
}, 30000)
},
clear(state) {
clearTimeout(state.timer);
state.friends = [];
state.timer = null;
}
},
actions: {
loadFriend(context) {
clear() {
clearTimeout(this.timer);
this.friends = [];
this.timer = null;
},
loadFriend() {
return new Promise((resolve, reject) => {
http({
url: '/friend/list',
method: 'GET'
}).then((friends) => {
context.commit("setFriends", friends);
context.commit("refreshOnlineStatus");
this.setFriends(friends);
this.refreshOnlineStatus();
resolve()
}).catch((res) => {
reject();
@ -97,9 +87,9 @@ export default {
});
}
},
getters:{
getters: {
findFriend: (state) => (id) => {
return state.friends.find((f)=>f.id==id);
return state.friends.find((f) => f.id == id);
}
}
}
})

67
im-uniapp/store/groupStore.js

@ -1,57 +1,58 @@
import { defineStore } from 'pinia';
import http from '@/common/request';
export default {
state: {
groups: [],
activeIndex: -1,
export default defineStore('groupStore', {
state: () => {
return {
groups: [],
activeIndex: -1
}
},
mutations: {
setGroups(state, groups) {
state.groups = groups;
actions: {
setGroups(groups) {
this.groups = groups;
},
activeGroup(state, index) {
state.activeIndex = index;
activeGroup(index) {
this.activeIndex = index;
},
addGroup(state, group) {
state.groups.unshift(group);
addGroup(group) {
this.groups.unshift(group);
},
removeGroup(state, groupId) {
state.groups.forEach((g, index) => {
removeGroup(groupId) {
this.groups.forEach((g, index) => {
if (g.id == groupId) {
state.groups.splice(index, 1);
if (state.activeIndex >= state.groups.length) {
state.activeIndex = state.groups.length - 1;
this.groups.splice(index, 1);
if (this.activeIndex >= this.groups.length) {
this.activeIndex = this.groups.length - 1;
}
}
})
},
updateGroup(state, group) {
state.groups.forEach((g, idx) => {
if (g.id == group.id) {
// 拷贝属性
Object.assign(state.groups[idx], group);
}
})
updateGroup(group) {
let g = this.findGroup(group.id);
Object.assign(g, group);
},
clear(state){
state.groups = [];
state.activeGroup = -1;
}
},
actions: {
loadGroup(context) {
clear() {
this.groups = [];
this.activeGroup = -1;
},
loadGroup() {
return new Promise((resolve, reject) => {
http({
url: '/group/list',
method: 'GET'
}).then((groups) => {
context.commit("setGroups", groups);
this.setGroups(groups);
resolve();
}).catch((res) => {
reject(res);
})
});
}
},
getters: {
findGroup: (state) => (id) => {
return state.groups.find((g) => g.id == id);
}
}
}
})

35
im-uniapp/store/index.js

@ -1,35 +0,0 @@
import chatStore from './chatStore.js';
import friendStore from './friendStore.js';
import userStore from './userStore.js';
import groupStore from './groupStore.js';
import configStore from './configStore.js';
import { createStore } from 'vuex';
const store = createStore({
modules: {
chatStore,
friendStore,
userStore,
groupStore,
configStore
},
state: {},
actions: {
load(context) {
return this.dispatch("loadUser").then(() => {
const promises = [];
promises.push(this.dispatch("loadFriend"));
promises.push(this.dispatch("loadGroup"));
promises.push(this.dispatch("loadChat"));
promises.push(this.dispatch("loadConfig"));
return Promise.all(promises);
})
},
unload(context){
context.commit("clear");
}
},
strict: true
})
export default store;

42
im-uniapp/store/userStore.js

@ -1,44 +1,32 @@
import {USER_STATE} from "../common/enums"
import { defineStore } from 'pinia';
import http from '../common/request'
export default {
state: {
userInfo: {},
config:{
webrtc:{}
},
state: USER_STATE.FREE
export default defineStore('userStore', {
state: () => {
return {
userInfo: {}
}
},
mutations: {
setUserInfo(state, userInfo) {
// 使用深拷贝方式,否则小程序页面不刷新
Object.assign(state.userInfo, userInfo);
actions: {
setUserInfo(userInfo) {
this.userInfo = userInfo;
},
setUserState(state, userState) {
state.state = userState;
clear() {
this.userInfo = {};
},
clear(state){
state.userInfo = {};
state.state = USER_STATE.FREE;
}
},
actions:{
loadUser(context){
loadUser(context) {
return new Promise((resolve, reject) => {
http({
url: '/user/self',
method: 'GET'
}).then((userInfo) => {
console.log(userInfo)
context.commit("setUserInfo",userInfo);
this.setUserInfo(userInfo);
resolve();
}).catch((res)=>{
}).catch((res) => {
reject(res);
});
})
}
}
}
})

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

@ -21,7 +21,7 @@ export default {
state.chats = [];
state.privateMsgMaxId = chatsData.privateMsgMaxId || 0;
state.groupMsgMaxId = chatsData.groupMsgMaxId || 0;
cacheChats = chatsData.chats||[];
cacheChats = chatsData.chats || [];
// 防止图片一直处在加载中状态
cacheChats.forEach((chat) => {
chat.messages.forEach((msg) => {
@ -55,7 +55,9 @@ export default {
unreadCount: 0,
messages: [],
atMe: false,
atAll: false
atAll: false,
stored: false,
delete: false
};
chats.unshift(chat);
}
@ -72,26 +74,23 @@ export default {
chats[idx].unreadCount = 0;
chats[idx].atMe = false;
chats[idx].atAll = false;
chats[idx].stored = false;
this.commit("saveToStorage");
break;
}
}
this.commit("saveToStorage");
},
readedMessage(state, pos) {
let chats = this.getters.findChats();
for (let idx in chats) {
if (chats[idx].type == 'PRIVATE' &&
chats[idx].targetId == pos.friendId) {
chats[idx].messages.forEach((m) => {
if (m.selfSend && m.status != MESSAGE_STATUS.RECALL) {
// pos.maxId为空表示整个会话已读
if (!pos.maxId || m.id <= pos.maxId) {
m.status = MESSAGE_STATUS.READED
}
}
})
let chat = this.getters.findChatByFriend(pos.friendId);
chat.messages.forEach((m) => {
if (m.id && m.selfSend && m.status < MESSAGE_STATUS.RECALL) {
// pos.maxId为空表示整个会话已读
if (!pos.maxId || m.id <= pos.maxId) {
m.status = MESSAGE_STATUS.READED
chat.stored = false;
}
}
}
})
this.commit("saveToStorage");
},
removeChat(state, idx) {
@ -99,9 +98,30 @@ export default {
if (chats[idx] == state.activeChat) {
state.activeChat = null;
}
chats.splice(idx, 1);
chats[idx].delete = true;
chats[idx].stored = false;
this.commit("saveToStorage");
},
removePrivateChat(state,friendId){
let chats = this.getters.findChats();
for (let idx in chats) {
if (chats[idx].type == 'PRIVATE' &&
chats[idx].targetId === friendId) {
this.commit("removeChat",idx)
break;
}
}
},
removeGroupChat(state,groupId){
let chats = this.getters.findChats();
for (let idx in chats) {
if (chats[idx].type == 'GROUP' &&
chats[idx].targetId === groupId) {
this.commit("removeChat",idx)
break;
}
}
},
moveTop(state, idx) {
// 加载中不移动,很耗性能
if (this.getters.isLoading()) {
@ -112,18 +132,11 @@ export default {
let chat = chats[idx];
chats.splice(idx, 1);
chats.unshift(chat);
chat.lastSendTime = new Date().getTime();
chat.stored = false;
this.commit("saveToStorage");
}
},
removePrivateChat(state, friendId) {
let chats = this.getters.findChats();
for (let idx in chats) {
if (chats[idx].type == 'PRIVATE' &&
chats[idx].targetId == friendId) {
this.commit("removeChat", idx);
}
}
},
insertMessage(state, msgInfo) {
let type = msgInfo.groupId ? 'GROUP' : 'PRIVATE';
// 记录消息的最大id
@ -142,6 +155,7 @@ export default {
if (msgInfo.type == MESSAGE_TYPE.RECALL) {
chat.lastContent = msgInfo.content;
}
chat.stored = false;
this.commit("saveToStorage");
return;
}
@ -152,14 +166,14 @@ export default {
chat.lastContent = "[文件]";
} else if (msgInfo.type == MESSAGE_TYPE.AUDIO) {
chat.lastContent = "[语音]";
} else if (msgInfo.type == MESSAGE_TYPE.TEXT ||
msgInfo.type == MESSAGE_TYPE.RECALL ||
msgInfo.type == MESSAGE_TYPE.TIP_TEXT) {
chat.lastContent = msgInfo.content;
} else if (msgInfo.type == MESSAGE_TYPE.ACT_RT_VOICE) {
chat.lastContent = "[语音通话]";
} else if (msgInfo.type == MESSAGE_TYPE.ACT_RT_VIDEO) {
chat.lastContent = "[视频通话]";
} else if (msgInfo.type == MESSAGE_TYPE.TEXT ||
msgInfo.type == MESSAGE_TYPE.RECALL ||
msgInfo.type == MESSAGE_TYPE.TIP_TEXT) {
chat.lastContent = msgInfo.content;
}
chat.lastSendTime = msgInfo.sendTime;
chat.sendNickName = msgInfo.sendNickName;
@ -199,6 +213,7 @@ export default {
}
}
chat.messages.splice(insertPos, 0, msgInfo);
chat.stored = false;
this.commit("saveToStorage");
},
updateMessage(state, msgInfo) {
@ -208,6 +223,7 @@ export default {
if (message) {
// 属性拷贝
Object.assign(message, msgInfo);
chat.stored = false;
this.commit("saveToStorage");
}
},
@ -226,31 +242,30 @@ export default {
break;
}
}
chat.stored = false;
this.commit("saveToStorage");
},
updateChatFromFriend(state, friend) {
let chats = this.getters.findChats();
for (let i in chats) {
let chat = chats[i];
if (chat.type == 'PRIVATE' && chat.targetId == friend.id) {
chat.headImage = friend.headImageThumb;
chat.showName = friend.nickName;
break;
}
let chat = this.getters.findChatByFriend(friend.id);
// 更新会话中的群名和头像
if (chat && (chat.headImage != friend.headImageThumb ||
chat.showName != friend.nickName)) {
chat.headImage = friend.headImageThumb;
chat.showName = friend.nickName;
chat.stored = false;
this.commit("saveToStorage")
}
this.commit("saveToStorage");
},
updateChatFromGroup(state, group) {
let chats = this.getters.findChats();
for (let i in chats) {
let chat = chats[i];
if (chat.type == 'GROUP' && chat.targetId == group.id) {
chat.headImage = group.headImageThumb;
chat.showName = group.showGroupName;
break;
}
let chat = this.getters.findChatByGroup(group.id);
if (chat && (chat.headImage != group.headImageThumb ||
chat.showName != group.showGroupName)) {
// 更新会话中的群名称和头像
chat.headImage = group.headImageThumb;
chat.showName = group.showGroupName;
chat.stored = false;
this.commit("saveToStorage")
}
this.commit("saveToStorage");
},
loadingPrivateMsg(state, loading) {
state.loadingPrivateMsg = loading;
@ -282,10 +297,29 @@ export default {
}
let userId = userStore.state.userInfo.id;
let key = "chats-" + userId;
let chatKeys = [];
// 按会话为单位存储,
state.chats.forEach((chat) => {
// 只存储有改动的会话
let chatKey = `${key}-${chat.type}-${chat.targetId}`
if (!chat.stored) {
console.log(chatKey)
if (chat.delete) {
localForage.removeItem(chatKey);
} else {
localForage.setItem(chatKey, chat);
}
chat.stored = true;
}
if (!chat.delete) {
chatKeys.push(chatKey);
}
})
// 会话核心信息
let chatsData = {
privateMsgMaxId: state.privateMsgMaxId,
groupMsgMaxId: state.groupMsgMaxId,
chats: state.chats
chatKeys: chatKeys
}
localForage.setItem(key, chatsData)
},
@ -293,7 +327,6 @@ export default {
cacheChats = []
state.chats = [];
state.activeChat = null;
}
},
actions: {
@ -301,17 +334,27 @@ export default {
return new Promise((resolve, reject) => {
let userId = userStore.state.userInfo.id;
let key = "chats-" + userId;
localForage.getItem(key).then((item)=>{
let chatsData = item;
// 兼容历史数据,以后要删除
if(!chatsData){
chatsData = JSON.parse(localStorage.getItem(key));
localForage.getItem(key).then((chatsData) => {
if (!chatsData) {
resolve();
}
if (chatsData) {
else if(chatsData.chats){
// 兼容旧版本
context.commit("initChats", chatsData);
resolve();
}else if (chatsData.chatKeys) {
const promises = [];
chatsData.chatKeys.forEach(key => {
promises.push(localForage.getItem(key))
})
Promise.all(promises).then(chats => {
chatsData.chats = chats.filter(o => o);
context.commit("initChats", chatsData);
resolve();
})
}
resolve();
}).catch(()=>{
}).catch((e) => {
console.log("加载消息失败")
reject();
})
})
@ -349,6 +392,16 @@ export default {
}
return chat;
},
findChatByFriend: (state, getters) => (fid) => {
let chats = getters.findChats();
return chats.find(chat => chat.type == 'PRIVATE' &&
chat.targetId == fid)
},
findChatByGroup: (state, getters) => (gid) => {
let chats = getters.findChats();
return chats.find(chat => chat.type == 'GROUP' &&
chat.targetId == gid)
},
findMessage: (state) => (chat, msgInfo) => {
if (!chat) {
return null;

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

@ -12,7 +12,7 @@
</div>
<el-scrollbar class="chat-list-items">
<div v-for="(chat,index) in chatStore.chats" :key="index">
<chat-item v-show="chat.showName.includes(searchText)" :chat="chat" :index="index"
<chat-item v-show="!chat.delete&&chat.showName.includes(searchText)" :chat="chat" :index="index"
@click.native="onActiveItem(index)" @delete="onDelItem(index)" @top="onTop(index)"
:active="chat === chatStore.activeChat"></chat-item>
</div>

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

@ -89,7 +89,7 @@
this.loadUserInfo(friend, idx);
},
onDelItem(friend, idx) {
this.$confirm(`确认要解除与 '${friend.nickName}'的好友关系吗?`, '确认解除?', {
this.$confirm(`确认删除'${friend.nickName}',并清空聊天记录吗?`, '确认解除?', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'

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

@ -172,7 +172,7 @@
});
},
onDissolve() {
this.$confirm('确认要解散群聊吗?', '确认解散?', {
this.$confirm(`确认要解散'${this.activeGroup.name}'吗?`, '确认解散?', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
@ -183,6 +183,7 @@
}).then(() => {
this.$message.success(`群聊'${this.activeGroup.name}'已解散`);
this.$store.commit("removeGroup", this.activeGroup.id);
this.$store.commit("removeGroupChat", this.activeGroup.id);
this.reset();
});
})
@ -208,7 +209,7 @@
},
onQuit() {
this.$confirm('退出群聊后将不再接受群里的消息,确认退出吗?', '确认退出?', {
this.$confirm(`确认退出'${this.activeGroup.showGroupName}',并清空聊天记录吗?`, '确认退出?', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
@ -217,6 +218,7 @@
url: `/group/quit/${this.activeGroup.id}`,
method: 'delete'
}).then(() => {
this.$message.success(`您已退出'${this.activeGroup.name}'`);
this.$store.commit("removeGroup", this.activeGroup.id);
this.$store.commit("removeGroupChat", this.activeGroup.id);
this.reset();

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

@ -336,7 +336,9 @@
let unreadCount = 0;
let chats = this.$store.state.chatStore.chats;
chats.forEach((chat) => {
unreadCount += chat.unreadCount
if(!chat.delete){
unreadCount += chat.unreadCount
}
});
return unreadCount;
}

Loading…
Cancel
Save