Browse Source

支持删除和撤回消息

master
xie.bx 3 years ago
parent
commit
53ebbbe483
  1. 3
      commom/src/main/java/com/bx/common/enums/MessageTypeEnum.java
  2. 6
      im-platform/src/main/java/com/bx/implatform/controller/GroupController.java
  3. 18
      im-platform/src/main/java/com/bx/implatform/controller/GroupMessageController.java
  4. 20
      im-platform/src/main/java/com/bx/implatform/controller/PrivateMessageController.java
  5. 2
      im-platform/src/main/java/com/bx/implatform/entity/PrivateMessage.java
  6. 4
      im-platform/src/main/java/com/bx/implatform/service/IGroupMessageService.java
  7. 4
      im-platform/src/main/java/com/bx/implatform/service/IPrivateMessageService.java
  8. 90
      im-platform/src/main/java/com/bx/implatform/service/impl/GroupMessageServiceImpl.java
  9. 40
      im-platform/src/main/java/com/bx/implatform/service/impl/PrivateMessageServiceImpl.java
  10. 2
      im-server/src/main/java/com/bx/imserver/websocket/WebsocketChannelCtxHolder.java
  11. 2
      im-server/src/main/java/com/bx/imserver/websocket/endecode/MessageProtocolDecoder.java
  12. 2
      im-server/src/main/java/com/bx/imserver/websocket/endecode/MessageProtocolEncoder.java
  13. 1
      im-server/src/main/java/com/bx/imserver/websocket/processor/HeartbeatProcessor.java
  14. 2
      im-server/src/main/java/com/bx/imserver/websocket/processor/PrivateMessageProcessor.java
  15. 54
      im-ui/src/components/chat/ChatBox.vue
  16. 64
      im-ui/src/components/chat/MessageItem.vue
  17. 63
      im-ui/src/components/common/RightMenu.vue
  18. 43
      im-ui/src/store/chatStore.js
  19. 2
      im-ui/src/store/friendStore.js
  20. 2
      im-ui/src/store/groupStore.js
  21. 1
      im-ui/src/store/uiStore.js

3
commom/src/main/java/com/bx/common/enums/MessageTypeEnum.java

@ -6,7 +6,8 @@ public enum MessageTypeEnum {
TEXT(0,"文字"),
FILE(1,"文件"),
IMAGE(2,"图片"),
VIDEO(3,"视频");
VIDEO(3,"视频"),
TIP(10,"系统提示");
private Integer code;

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

@ -40,7 +40,7 @@ public class GroupController {
@ApiOperation(value = "解散群聊",notes="解散群聊")
@DeleteMapping("/delete/{groupId}")
public Result<GroupVO> deleteGroup(@NotNull(message = "群聊id不能为空") @PathVariable Long groupId){
public Result deleteGroup(@NotNull(message = "群聊id不能为空") @PathVariable Long groupId){
groupService.deleteGroup(groupId);
return ResultUtils.success();
}
@ -72,14 +72,14 @@ public class GroupController {
@ApiOperation(value = "退出群聊",notes="退出群聊")
@DeleteMapping("/quit/{groupId}")
public Result<GroupVO> quitGroup(@NotNull(message = "群聊id不能为空") @PathVariable Long groupId){
public Result quitGroup(@NotNull(message = "群聊id不能为空") @PathVariable Long groupId){
groupService.quitGroup(groupId);
return ResultUtils.success();
}
@ApiOperation(value = "踢出群聊",notes="将用户踢出群聊")
@DeleteMapping("/kick/{groupId}")
public Result<GroupVO> kickGroup(@NotNull(message = "群聊id不能为空") @PathVariable Long groupId,
public Result kickGroup(@NotNull(message = "群聊id不能为空") @PathVariable Long groupId,
@NotNull(message = "用户id不能为空") @RequestParam Long userId){
groupService.kickGroup(groupId,userId);
return ResultUtils.success();

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

@ -8,13 +8,10 @@ import com.bx.implatform.vo.GroupMessageVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
@Api(tags = "群聊消息")
@ -28,11 +25,16 @@ public class GroupMessageController {
@PostMapping("/send")
@ApiOperation(value = "发送群聊消息",notes="发送群聊消息")
public Result register(@Valid @RequestBody GroupMessageVO vo){
groupMessageService.sendMessage(vo);
return ResultUtils.success();
public Result<Long> sendMessage(@Valid @RequestBody GroupMessageVO vo){
return ResultUtils.success(groupMessageService.sendMessage(vo));
}
@DeleteMapping("/recall/{id}")
@ApiOperation(value = "撤回消息",notes="撤回群聊消息")
public Result<Long> recallMessage(@NotNull(message = "消息id不能为空") @PathVariable Long id){
groupMessageService.recallMessage(id);
return ResultUtils.success();
}
@PostMapping("/pullUnreadMessage")
@ApiOperation(value = "拉取未读消息",notes="拉取未读消息")

20
im-platform/src/main/java/com/bx/implatform/controller/PrivateMessageController.java

@ -8,12 +8,10 @@ import com.bx.implatform.vo.PrivateMessageVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
@Api(tags = "私聊消息")
@RestController
@ -24,12 +22,20 @@ public class PrivateMessageController {
private IPrivateMessageService privateMessageService;
@PostMapping("/send")
@ApiOperation(value = "发送消息",notes="发送单人消息")
public Result register(@Valid @RequestBody PrivateMessageVO vo){
privateMessageService.sendMessage(vo);
@ApiOperation(value = "发送消息",notes="发送私聊消息")
public Result<Long> sendMessage(@Valid @RequestBody PrivateMessageVO vo){
return ResultUtils.success(privateMessageService.sendMessage(vo));
}
@DeleteMapping("/recall/{id}")
@ApiOperation(value = "撤回消息",notes="撤回私聊消息")
public Result<Long> recallMessage(@NotNull(message = "消息id不能为空") @PathVariable Long id){
privateMessageService.recallMessage(id);
return ResultUtils.success();
}
@PostMapping("/pullUnreadMessage")
@ApiOperation(value = "拉取未读消息",notes="拉取未读消息")
public Result pullUnreadMessage(){

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

@ -51,7 +51,7 @@ public class PrivateMessage extends Model<PrivateMessage> {
private String content;
/**
* 消息类型
* 消息类型 0:文字 1:图片 2:文件 3:语音 10:撤回消息
*/
@TableField("type")
private Integer type;

4
im-platform/src/main/java/com/bx/implatform/service/IGroupMessageService.java

@ -8,7 +8,9 @@ import com.bx.implatform.vo.GroupMessageVO;
public interface IGroupMessageService extends IService<GroupMessage> {
void sendMessage(GroupMessageVO vo);
Long sendMessage(GroupMessageVO vo);
void recallMessage(Long id);
void pullUnreadMessage();
}

4
im-platform/src/main/java/com/bx/implatform/service/IPrivateMessageService.java

@ -7,7 +7,9 @@ import com.bx.implatform.vo.PrivateMessageVO;
public interface IPrivateMessageService extends IService<PrivateMessage> {
void sendMessage(PrivateMessageVO vo);
Long sendMessage(PrivateMessageVO vo);
void recallMessage(Long id);
void pullUnreadMessage();

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

@ -3,6 +3,7 @@ package com.bx.implatform.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.bx.common.contant.RedisKey;
import com.bx.common.enums.MessageTypeEnum;
import com.bx.common.enums.ResultCode;
import com.bx.common.model.im.GroupMessageInfo;
import com.bx.common.util.BeanUtils;
@ -46,7 +47,7 @@ public class GroupMessageServiceImpl extends ServiceImpl<GroupMessageMapper, Gro
* @return
*/
@Override
public void sendMessage(GroupMessageVO vo) {
public Long sendMessage(GroupMessageVO vo) {
Long userId = SessionContext.getSession().getId();
Group group = groupService.getById(vo.getGroupId());
if(group == null){
@ -65,31 +66,49 @@ public class GroupMessageServiceImpl extends ServiceImpl<GroupMessageMapper, Gro
msg.setSendId(userId);
msg.setSendTime(new Date());
this.save(msg);
// 根据群聊每个成员所连的IM-server,进行分组
Map<Integer,List<Long>> serverMap = new ConcurrentHashMap<>();
userIds.parallelStream().forEach(id->{
String key = RedisKey.IM_USER_SERVER_ID + id;
Integer serverId = (Integer)redisTemplate.opsForValue().get(key);
if(serverId != null){
if(serverMap.containsKey(serverId)){
serverMap.get(serverId).add(id);
}else {
List<Long> list = Collections.synchronizedList(new LinkedList<Long>());
list.add(id);
serverMap.put(serverId,list);
// 群发
GroupMessageInfo msgInfo = BeanUtils.copyProperties(msg, GroupMessageInfo.class);
this.sendMessage(userIds,msgInfo);
log.info("发送群聊消息,发送id:{},群聊id:{},内容:{}",userId,vo.getGroupId(),vo.getContent());
return msg.getId();
}
/**
* 撤回消息
*
* @param id 消息id
*/
@Override
public void recallMessage(Long id) {
Long userId = SessionContext.getSession().getId();
GroupMessage msg = this.getById(id);
if(msg == null){
throw new GlobalException(ResultCode.PROGRAM_ERROR,"消息不存在");
}
});
// 逐个server发送
for (Map.Entry<Integer,List<Long>> entry : serverMap.entrySet()) {
GroupMessageInfo msgInfo = BeanUtils.copyProperties(msg, GroupMessageInfo.class);
msgInfo.setRecvIds(new LinkedList<>(entry.getValue()));
String key = RedisKey.IM_UNREAD_GROUP_MESSAGE +entry.getKey();
redisTemplate.opsForList().rightPush(key,msgInfo);
if(msg.getSendId() != userId){
throw new GlobalException(ResultCode.PROGRAM_ERROR,"这条消息不是您发送的呢");
}
log.info("发送群聊消息,发送id:{},群聊id:{}",userId,vo.getGroupId());
// 判断是否在群里
GroupMember member = groupMemberService.findByGroupAndUserId(msg.getGroupId(),userId);
if(member == null){
throw new GlobalException(ResultCode.PROGRAM_ERROR,"您已不在群聊里面,无法撤回消息");
}
// 直接物理删除
this.removeById(id);
// 群发
List<Long> userIds = groupMemberService.findUserIdsByGroupId(msg.getGroupId());
GroupMessageInfo msgInfo = BeanUtils.copyProperties(msg, GroupMessageInfo.class);
msgInfo.setType(MessageTypeEnum.TIP.getCode());
String content = String.format("'%s'撤回了一条消息",member.getAliasName());
msgInfo.setContent(content);
this.sendMessage(userIds,msgInfo);
log.info("删除群聊消息,发送id:{},群聊id:{},内容:{}",userId,msg.getGroupId(),msg.getContent());
}
/**
* 异步拉取群聊消息通过websocket异步推送
*
@ -129,6 +148,35 @@ public class GroupMessageServiceImpl extends ServiceImpl<GroupMessageMapper, Gro
}).collect(Collectors.toList());
key = RedisKey.IM_UNREAD_GROUP_MESSAGE + serverId;
redisTemplate.opsForList().rightPushAll(key,messageInfos.toArray());
log.info("拉取未读群聊消息,用户id:{},群聊id:{},数量:{}",userId,member.getGroupId(),messageInfos.size());
}
}
private void sendMessage(List<Long> userIds, GroupMessageInfo msgInfo){
// 根据群聊每个成员所连的IM-server,进行分组
Map<Integer,List<Long>> serverMap = new ConcurrentHashMap<>();
userIds.parallelStream().forEach(id->{
String key = RedisKey.IM_USER_SERVER_ID + id;
Integer serverId = (Integer)redisTemplate.opsForValue().get(key);
if(serverId != null){
if(serverMap.containsKey(serverId)){
serverMap.get(serverId).add(id);
}else {
// 此处需要加锁,否则list可以会被覆盖
synchronized(serverMap){
List<Long> list = Collections.synchronizedList(new LinkedList<Long>());
list.add(id);
serverMap.put(serverId,list);
}
}
}
});
// 逐个server发送
for (Map.Entry<Integer,List<Long>> entry : serverMap.entrySet()) {
msgInfo.setRecvIds(new LinkedList<>(entry.getValue()));
String key = RedisKey.IM_UNREAD_GROUP_MESSAGE +entry.getKey();
redisTemplate.opsForList().rightPush(key,msgInfo);
}
}
}

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

@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.bx.common.contant.RedisKey;
import com.bx.common.enums.MessageStatusEnum;
import com.bx.common.enums.MessageTypeEnum;
import com.bx.common.enums.ResultCode;
import com.bx.common.model.im.PrivateMessageInfo;
import com.bx.common.util.BeanUtils;
@ -36,10 +37,10 @@ public class PrivateMessageServiceImpl extends ServiceImpl<PrivateMessageMapper,
* 发送私聊消息
*
* @param vo 私聊消息vo
* @return
* @return 消息id
*/
@Override
public void sendMessage(PrivateMessageVO vo) {
public Long sendMessage(PrivateMessageVO vo) {
Long userId = SessionContext.getSession().getId();
Boolean isFriends = friendService.isFriend(userId,vo.getRecvId());
if(!isFriends){
@ -60,7 +61,39 @@ public class PrivateMessageServiceImpl extends ServiceImpl<PrivateMessageMapper,
PrivateMessageInfo msgInfo = BeanUtils.copyProperties(msg, PrivateMessageInfo.class);
redisTemplate.opsForList().rightPush(sendKey,msgInfo);
}
log.info("发送私聊消息,发送id:{},接收id:{}",userId,vo.getRecvId());
log.info("发送私聊消息,发送id:{},接收id:{},内容:{}",userId,vo.getRecvId(),vo.getContent());
return msg.getId();
}
/**
* 撤回消息
*
* @param id 消息id
*/
@Override
public void recallMessage(Long id) {
Long userId = SessionContext.getSession().getId();
PrivateMessage msg = this.getById(id);
if(msg == null){
throw new GlobalException(ResultCode.PROGRAM_ERROR,"消息不存在");
}
if(msg.getSendId() != userId){
throw new GlobalException(ResultCode.PROGRAM_ERROR,"这条消息不是您发送的呢");
}
// 直接物理删除
this.removeById(id);
// 获取对方连接的channelId
String key = RedisKey.IM_USER_SERVER_ID+msg.getRecvId();
Integer serverId = (Integer)redisTemplate.opsForValue().get(key);
// 如果对方在线,将数据存储至redis,等待拉取推送
if(serverId != null){
String sendKey = RedisKey.IM_UNREAD_PRIVATE_MESSAGE + serverId;
PrivateMessageInfo msgInfo = BeanUtils.copyProperties(msg, PrivateMessageInfo.class);
msgInfo.setType(MessageTypeEnum.TIP.getCode());
msgInfo.setContent("对方撤回了一条消息");
redisTemplate.opsForList().rightPush(sendKey,msgInfo);
}
log.info("删除私聊消息,发送id:{},接收id:{},内容:{}",msg.getSendId(),msg.getRecvId(),msg.getContent());
}
/**
@ -90,6 +123,7 @@ public class PrivateMessageServiceImpl extends ServiceImpl<PrivateMessageMapper,
}).collect(Collectors.toList());
String sendKey = RedisKey.IM_UNREAD_PRIVATE_MESSAGE + serverId;
redisTemplate.opsForList().rightPushAll(sendKey,infos.toArray());
log.info("拉取未读私聊消息,用户id:{},数量:{}",userId,infos.size());
}
}
}

2
im-server/src/main/java/com/bx/imserver/websocket/WebsocketChannelCtxHolder.java

@ -2,7 +2,7 @@ package com.bx.imserver.websocket;
import io.netty.channel.ChannelHandlerContext;
import java.util.*;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

2
im-server/src/main/java/com/bx/imserver/websocket/endecode/MessageProtocolDecoder.java

@ -1,7 +1,7 @@
package com.bx.imserver.websocket.endecode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.bx.common.model.im.SendInfo;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageDecoder;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;

2
im-server/src/main/java/com/bx/imserver/websocket/endecode/MessageProtocolEncoder.java

@ -1,7 +1,7 @@
package com.bx.imserver.websocket.endecode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.bx.common.model.im.SendInfo;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageEncoder;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;

1
im-server/src/main/java/com/bx/imserver/websocket/processor/HeartbeatProcessor.java

@ -8,7 +8,6 @@ import com.bx.common.model.im.HeartbeatInfo;
import com.bx.common.model.im.SendInfo;
import com.bx.imserver.websocket.WebsocketServer;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.Attribute;
import io.netty.util.AttributeKey;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;

2
im-server/src/main/java/com/bx/imserver/websocket/processor/PrivateMessageProcessor.java

@ -2,8 +2,8 @@ package com.bx.imserver.websocket.processor;
import com.bx.common.contant.RedisKey;
import com.bx.common.enums.WSCmdEnum;
import com.bx.common.model.im.SendInfo;
import com.bx.common.model.im.PrivateMessageInfo;
import com.bx.common.model.im.SendInfo;
import com.bx.imserver.websocket.WebsocketChannelCtxHolder;
import io.netty.channel.ChannelHandlerContext;
import lombok.extern.slf4j.Slf4j;

54
im-ui/src/components/chat/ChatBox.vue

@ -13,7 +13,9 @@
<ul>
<li v-for="(msgInfo,idx) in chat.messages" :key="idx">
<message-item :mine="msgInfo.sendId == mine.id" :headImage="headImage(msgInfo)"
:showName="showName(msgInfo)" :msgInfo="msgInfo">
:showName="showName(msgInfo)" :msgInfo="msgInfo"
@delete="deleteMessage"
@recall="recallMessage">
</message-item>
</li>
</ul>
@ -108,10 +110,11 @@
url: this.messageAction,
method: 'post',
data: msgInfo
}).then((data) => {
}).then((id) => {
let info = {
type: this.chat.type,
targetId: file.raw.targetId,
msgId : id,
fileId: file.raw.uid,
content: JSON.stringify(res.data),
loadStatus: "ok"
@ -135,6 +138,7 @@
thumbUrl: url
}
let msgInfo = {
id:0,
fileId: file.uid,
sendId: this.mine.id,
content: JSON.stringify(data),
@ -168,11 +172,12 @@
url: this.messageAction,
method: 'post',
data: msgInfo
}).then(() => {
}).then((id) => {
let info = {
type: this.chat.type,
targetId: file.raw.targetId,
fileId: file.raw.uid,
msgId : id,
content: JSON.stringify(data),
loadStatus: "ok"
}
@ -196,6 +201,7 @@
url: url
}
let msgInfo = {
id: 0,
fileId: file.uid,
sendId: this.mine.id,
content: JSON.stringify(data),
@ -210,7 +216,7 @@
this.$store.commit("insertMessage", msgInfo);
//
this.scrollToBottom();
// fileid
// file
file.targetId = this.chat.targetId;
},
handleCloseSide() {
@ -232,7 +238,6 @@
},
showVoiceBox() {
this.showVoice = true;
},
closeVoiceBox() {
this.showVoice = false;
@ -248,8 +253,9 @@
url: this.messageAction,
method: 'post',
data: msgInfo
}).then(() => {
}).then((id) => {
this.$message.success("发送成功");
msgInfo.id = id;
msgInfo.sendTime = new Date().getTime();
msgInfo.sendId = this.$store.state.userStore.userInfo.id;
msgInfo.selfSend = true;
@ -270,7 +276,6 @@
}
},
sendTextMessage() {
if (!this.sendText.trim()) {
this.$message.error("不能发送空白信息");
return
@ -285,9 +290,10 @@
url: this.messageAction,
method: 'post',
data: msgInfo
}).then((data) => {
}).then((id) => {
this.$message.success("发送成功");
this.sendText = "";
msgInfo.id = id;
msgInfo.sendTime = new Date().getTime();
msgInfo.sendId = this.$store.state.userStore.userInfo.id;
msgInfo.selfSend = true;
@ -304,6 +310,35 @@
return false;
}
},
deleteMessage(msgInfo){
this.$confirm( '确认删除消息?','删除消息', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.$store.commit("deleteMessage",msgInfo);
});
},
recallMessage(msgInfo){
this.$confirm('确认撤回消息?','撤回消息', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
let url = `/message/${this.chat.type.toLowerCase()}/recall/${msgInfo.id}`
this.$http({
url: url,
method: 'delete'
}).then(() => {
this.$message.success("消息已撤回");
msgInfo = JSON.parse(JSON.stringify(msgInfo));
msgInfo.type = 10;
msgInfo.content = '你撤回了一条消息';
this.$store.commit("insertMessage",msgInfo);
})
});
},
loadGroup(groupId) {
this.$http({
url: `/group/find/${groupId}`,
@ -378,6 +413,7 @@
messageAction() {
return `/message/${this.chat.type.toLowerCase()}/send`;
}
},
watch: {
chat: {
@ -433,7 +469,7 @@
border: #dddddd solid 1px;
.im-chat-box {
ul {
>ul {
padding: 20px;
li {

64
im-ui/src/components/chat/MessageItem.vue

@ -1,5 +1,7 @@
<template>
<div class="im-msg-item" :class="{'im-chat-mine':mine}">
<div class="im-msg-item">
<div class="im-msg-tip" v-show="msgInfo.type==10">{{msgInfo.content}}</div>
<div class="im-msg-normal" v-show="msgInfo.type!=10" :class="{'im-chat-mine':mine}">
<div class="head-image">
<head-image :url="headImage" :id="msgInfo.sendId"></head-image>
</div>
@ -8,15 +10,11 @@
<span>{{showName}}</span>
<chat-time :time="msgInfo.sendTime"></chat-time>
</div>
<div class="im-msg-bottom">
<div class="im-msg-bottom" @contextmenu.prevent="showRightMenu($event)">
<span class="im-msg-text" v-if="msgInfo.type==0" v-html="$emo.transform(msgInfo.content)"></span>
<div class="im-msg-image" v-if="msgInfo.type==1">
<div class="img-load-box" v-loading="loading"
element-loading-text="上传中.."
element-loading-background="rgba(0, 0, 0, 0.4)">
<img class="send-image"
:src="JSON.parse(msgInfo.content).thumbUrl"
@click="showFullImageBox()"/>
<div class="img-load-box" v-loading="loading" element-loading-text="上传中.." element-loading-background="rgba(0, 0, 0, 0.4)">
<img class="send-image" :src="JSON.parse(msgInfo.content).thumbUrl" @click="showFullImageBox()" />
</div>
<span title="发送失败" v-show="loadFail" @click="handleSendFail" class="send-fail el-icon-warning"></span>
</div>
@ -37,18 +35,24 @@
</div>
</div>
</div>
</div>
<right-menu v-show="rightMenu.show" :pos="rightMenu.pos" :items="menuItems" @close="rightMenu.show=false"
@select="handleSelectMenu"></right-menu>
</div>
</template>
<script>
import ChatTime from "./ChatTime.vue";
import HeadImage from "../common/HeadImage.vue";
import RightMenu from '../common/RightMenu.vue';
export default {
name: "messageItem",
components: {
ChatTime,
HeadImage
HeadImage,
RightMenu
},
props: {
mine: {
@ -71,6 +75,13 @@
data() {
return {
audioPlayState: 'STOP',
rightMenu: {
show: false,
pos: {
x: 0,
y: 0
}
}
}
},
@ -91,6 +102,16 @@
this.audio.src = JSON.parse(this.msgInfo.content).url;
this.audio.play();
this.handlePlayVoice = 'RUNNING';
},
showRightMenu(e) {
this.rightMenu.pos = {
x: e.x,
y: e.y
};
this.rightMenu.show = "true";
},
handleSelectMenu(item) {
this.$emit(item.key.toLowerCase(), this.msgInfo);
}
},
computed: {
@ -113,6 +134,22 @@
}
return size + "B";
},
menuItems() {
let items = [];
items.push({
key: 'DELETE',
name: '删除',
icon: 'el-icon-delete'
});
if (this.msgInfo.selfSend && this.msgInfo.id > 0) {
items.push({
key: 'RECALL',
name: '撤回',
icon: 'el-icon-refresh-left'
});
}
return items;
}
},
mounted() {
//console.log(this.msgInfo);
@ -122,6 +159,12 @@
<style lang="scss">
.im-msg-item {
.im-msg-tip {
line-height: 50px;
}
.im-msg-normal {
position: relative;
font-size: 0;
margin-bottom: 10px;
@ -220,11 +263,13 @@
border-radius: 3px;
background-color: #eeeeee;
padding: 10px 15px;
.im-file-info {
flex: 1;
height: 100%;
text-align: left;
font-size: 14px;
.im-file-name {
font-size: 16px;
font-weight: 600;
@ -316,4 +361,5 @@
}
}
}
</style>

63
im-ui/src/components/common/RightMenu.vue

@ -0,0 +1,63 @@
<template>
<div class="right-menu-mask" @click="close()" @contextmenu.prevent="close()">
<div class="right-menu" :style="{'left':pos.x+'px','top':pos.y+'px'}">
<el-menu background-color="#f5f5f5" text-color="#333333">
<el-menu-item v-for="(item) in items" :key="item.key" :title="item.name" @click="handleSelectMenu(item)">
<i :class="item.icon"></i>
<span>{{item.name}}</span>
</el-menu-item>
</el-menu>
</div>
</div>
</template>
<script>
export default {
name: "rightMenu",
data() {
return {}
},
props: {
pos: {
type: Object
},
items:{
type: Array
}
},
methods:{
close(){
this.$emit("close");
},
handleSelectMenu(item){
this.$emit("select",item);
}
}
}
</script>
<style lang="scss">
.right-menu-mask {
position: fixed;
left: 0;
top: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
z-index: 9999;
}
.right-menu {
position: fixed;
.el-menu-item {
height: 40px;
line-height: 40px;
i {
color: #333333;
}
}
}
</style>

43
im-ui/src/store/chatStore.js

@ -84,7 +84,7 @@ export default {
break;
}
}
console.log(msgInfo.type)
// 插入新的数据
if(msgInfo.type == 1){
chat.lastContent = "[图片]";
}else if(msgInfo.type == 2){
@ -95,12 +95,48 @@ export default {
chat.lastContent = msgInfo.content;
}
chat.lastSendTime = msgInfo.sendTime;
chat.messages.push(msgInfo);
// 如果不是当前会话,未读加1
chat.unreadCount++;
if(msgInfo.selfSend){
chat.unreadCount=0;
}
console.log(msgInfo);
// 如果是已存在消息,则覆盖旧的消息数据
for (let idx in chat.messages) {
if(msgInfo.id && chat.messages[idx].id == msgInfo.id){
Object.assign(chat.messages[idx], msgInfo);
return;
}
}
// 新的消息
chat.messages.push(msgInfo);
},
deleteMessage(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;
}
}
for (let idx in chat.messages) {
// 已经发送成功的,根据id删除
if(chat.messages[idx].id && chat.messages[idx].id == msgInfo.id){
chat.messages.splice(idx, 1);
break;
}
// 没有发送成功的,根据发送时间删除
if(!chat.messages[idx].id && chat.messages[idx].sendTime == msgInfo.sendTime){
chat.messages.splice(idx, 1);
break;
}
}
},
handleFileUpload(state, info) {
// 文件上传后数据更新
@ -110,6 +146,9 @@ export default {
if (info.content) {
msg.content = info.content;
}
if(info.msgId){
msg.id = info.msgId;
}
},
updateChatFromFriend(state, friend) {
for (let i in state.chats) {

2
im-ui/src/store/friendStore.js

@ -25,7 +25,7 @@ export default {
state.friends.forEach((f,index)=>{
if(f.id==friend.id){
// 拷贝属性
state.friends[index] = Object.assign(state.friends[index], friend);
Object.assign(state.friends[index], friend);
}
})
},

2
im-ui/src/store/groupStore.js

@ -39,7 +39,7 @@ export default {
state.groups.forEach((g,idx)=>{
if(g.id==group.id){
// 拷贝属性
state.groups[idx] = Object.assign(state.groups[idx], group);
Object.assign(state.groups[idx], group);
}
})
}

1
im-ui/src/store/uiStore.js

@ -12,6 +12,7 @@ export default {
show: false,
url: ""
}
},
mutations: {
showUserInfoBox(state,user){

Loading…
Cancel
Save