Browse Source

群聊@功能(开发中)

master
xsx 2 years ago
parent
commit
4a89339611
  1. 9
      im-platform/src/main/java/com/bx/implatform/dto/GroupMessageDTO.java
  2. 11
      im-platform/src/main/java/com/bx/implatform/entity/GroupMessage.java
  3. 1
      im-platform/src/main/java/com/bx/implatform/service/IGroupMemberService.java
  4. 38
      im-platform/src/main/java/com/bx/implatform/service/impl/GroupMessageServiceImpl.java
  5. 7
      im-platform/src/main/java/com/bx/implatform/vo/GroupMessageVO.java
  6. 2
      im-platform/src/main/resources/db/db.sql
  7. 44
      im-ui/src/components/chat/ChatAtBox.vue
  8. 44
      im-ui/src/components/chat/ChatBox.vue
  9. 79
      im-ui/src/components/chat/ChatItem.vue
  10. 13
      im-ui/src/store/chatStore.js
  11. 1
      im-ui/src/store/friendStore.js
  12. 30
      im-ui/src/view/Chat.vue
  13. 109
      im-ui/src/view/Friend.vue
  14. 94
      im-ui/src/view/Group.vue
  15. 9
      im-ui/src/view/Home.vue

9
im-platform/src/main/java/com/bx/implatform/dto/GroupMessageDTO.java

@ -7,6 +7,8 @@ import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.util.List;
@Data @Data
@ApiModel("群聊消息DTO") @ApiModel("群聊消息DTO")
@ -16,8 +18,7 @@ public class GroupMessageDTO {
@ApiModelProperty(value = "群聊id") @ApiModelProperty(value = "群聊id")
private Long groupId; private Long groupId;
@Length(max=1024,message = "发送内容长度不得大于1024")
@Length(max=1024,message = "内容长度不得大于1024")
@NotEmpty(message="发送内容不可为空") @NotEmpty(message="发送内容不可为空")
@ApiModelProperty(value = "发送内容") @ApiModelProperty(value = "发送内容")
private String content; private String content;
@ -25,4 +26,8 @@ public class GroupMessageDTO {
@NotNull(message="消息类型不可为空") @NotNull(message="消息类型不可为空")
@ApiModelProperty(value = "消息类型") @ApiModelProperty(value = "消息类型")
private Integer type; private Integer type;
@Size(max = 20,message = "一次最多只能@20个小伙伴哦")
@ApiModelProperty(value = "被@用户列表")
private List<Long> atUserIds;
} }

11
im-platform/src/main/java/com/bx/implatform/entity/GroupMessage.java

@ -44,6 +44,17 @@ public class GroupMessage extends Model<GroupMessage> {
@TableField("send_id") @TableField("send_id")
private Long sendId; private Long sendId;
/**
* 发送用户昵称
*/
@TableField("send_nick_name")
private String sendNickName;
/**
* @用户列表
*/
@TableField("at_user_ids")
private String atUserIds;
/** /**
* 发送内容 * 发送内容
*/ */

1
im-platform/src/main/java/com/bx/implatform/service/IGroupMemberService.java

@ -18,7 +18,6 @@ public interface IGroupMemberService extends IService<GroupMember> {
List<Long> findUserIdsByGroupId(Long groupId); List<Long> findUserIdsByGroupId(Long groupId);
boolean saveOrUpdateBatch(Long groupId,List<GroupMember> members); boolean saveOrUpdateBatch(Long groupId,List<GroupMember> members);
void removeByGroupId(Long groupId); void removeByGroupId(Long groupId);

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

@ -4,12 +4,10 @@ import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.bx.imclient.IMClient; import com.bx.imclient.IMClient;
import com.bx.imcommon.contant.IMConstant; import com.bx.imcommon.contant.IMConstant;
import com.bx.implatform.entity.PrivateMessage;
import com.bx.implatform.util.DateTimeUtils; import com.bx.implatform.util.DateTimeUtils;
import com.bx.implatform.vo.GroupMessageVO; import com.bx.implatform.vo.GroupMessageVO;
import com.bx.imcommon.model.IMGroupMessage; import com.bx.imcommon.model.IMGroupMessage;
@ -30,16 +28,13 @@ import com.bx.implatform.session.SessionContext;
import com.bx.implatform.session.UserSession; import com.bx.implatform.session.UserSession;
import com.bx.implatform.util.BeanUtils; import com.bx.implatform.util.BeanUtils;
import com.bx.implatform.dto.GroupMessageDTO; import com.bx.implatform.dto.GroupMessageDTO;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.Collections; import java.util.*;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Slf4j @Slf4j
@ -64,26 +59,33 @@ public class GroupMessageServiceImpl extends ServiceImpl<GroupMessageMapper, Gro
public Long sendMessage(GroupMessageDTO dto) { public Long sendMessage(GroupMessageDTO dto) {
UserSession session = SessionContext.getSession(); UserSession session = SessionContext.getSession();
Group group = groupService.getById(dto.getGroupId()); Group group = groupService.getById(dto.getGroupId());
if (group == null) { if (Objects.isNull(group)) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "群聊不存在"); throw new GlobalException(ResultCode.PROGRAM_ERROR, "群聊不存在");
} }
if (group.getDeleted()) { if (group.getDeleted()) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "群聊已解散"); throw new GlobalException(ResultCode.PROGRAM_ERROR, "群聊已解散");
} }
// 判断是否在群里 // 是否在群
List<Long> userIds = groupMemberService.findUserIdsByGroupId(group.getId()); GroupMember member = groupMemberService.findByGroupAndUserId(dto.getGroupId(), session.getUserId());
if (!userIds.contains(session.getUserId())) { if (Objects.isNull(member)) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "您已不在群聊里面,无法发送消息"); throw new GlobalException(ResultCode.PROGRAM_ERROR, "您已不在群聊里面,无法撤回消息");
} }
// 群聊成员列表
List<Long> userIds = groupMemberService.findUserIdsByGroupId(group.getId());
// 不用发给自己
userIds = userIds.stream().filter(id -> !session.getUserId().equals(id)).collect(Collectors.toList());
// 保存消息 // 保存消息
GroupMessage msg = BeanUtils.copyProperties(dto, GroupMessage.class); GroupMessage msg = BeanUtils.copyProperties(dto, GroupMessage.class);
msg.setSendId(session.getUserId()); msg.setSendId(session.getUserId());
msg.setSendTime(new Date()); msg.setSendTime(new Date());
msg.setSendNickName(member.getAliasName());
if(CollectionUtil.isNotEmpty(dto.getAtUserIds())){
msg.setAtUserIds(StrUtil.join(",",dto.getAtUserIds()));
}
this.save(msg); this.save(msg);
// 不用发给自己
userIds = userIds.stream().filter(id -> !session.getUserId().equals(id)).collect(Collectors.toList());
// 群发 // 群发
GroupMessageVO msgInfo = BeanUtils.copyProperties(msg, GroupMessageVO.class); GroupMessageVO msgInfo = BeanUtils.copyProperties(msg, GroupMessageVO.class);
msgInfo.setAtUserIds(dto.getAtUserIds());
IMGroupMessage<GroupMessageVO> sendMessage = new IMGroupMessage<>(); IMGroupMessage<GroupMessageVO> sendMessage = new IMGroupMessage<>();
sendMessage.setSender(new IMUserInfo(session.getUserId(), session.getTerminal())); sendMessage.setSender(new IMUserInfo(session.getUserId(), session.getTerminal()));
sendMessage.setRecvIds(userIds); sendMessage.setRecvIds(userIds);
@ -215,7 +217,13 @@ public class GroupMessageServiceImpl extends ServiceImpl<GroupMessageMapper, Gro
List<GroupMessage> messages = this.list(wrapper); List<GroupMessage> messages = this.list(wrapper);
// 转成vo // 转成vo
List<GroupMessageVO> vos = messages.stream().map(m -> BeanUtils.copyProperties(m, GroupMessageVO.class)).collect(Collectors.toList()); List<GroupMessageVO> vos = messages.stream().map(m -> {
GroupMessageVO vo = BeanUtils.copyProperties(m, GroupMessageVO.class);
// 被@用户列表
List<String> atIds = Arrays.asList(StrUtil.split(m.getAtUserIds(),","));
vo.setAtUserIds(atIds.stream().map(id->Long.parseLong(id)).collect(Collectors.toList()));
return vo;
}).collect(Collectors.toList());
// 消息状态,数据库没有存群聊的消息状态,需要从redis取 // 消息状态,数据库没有存群聊的消息状态,需要从redis取
List<String> keys = ids.stream() List<String> keys = ids.stream()
.map(id -> String.join(":", RedisKey.IM_GROUP_READED_POSITION, id.toString(), session.getUserId().toString())) .map(id -> String.join(":", RedisKey.IM_GROUP_READED_POSITION, id.toString(), session.getUserId().toString()))

7
im-platform/src/main/java/com/bx/implatform/vo/GroupMessageVO.java

@ -6,6 +6,7 @@ import io.swagger.annotations.ApiModelProperty;
import lombok.Data; import lombok.Data;
import java.util.Date; import java.util.Date;
import java.util.List;
@Data @Data
public class GroupMessageVO { public class GroupMessageVO {
@ -19,12 +20,18 @@ public class GroupMessageVO {
@ApiModelProperty(value = " 发送者id") @ApiModelProperty(value = " 发送者id")
private Long sendId; private Long sendId;
@ApiModelProperty(value = " 发送者昵称")
private String sendNickName;
@ApiModelProperty(value = "消息内容") @ApiModelProperty(value = "消息内容")
private String content; private String content;
@ApiModelProperty(value = "消息内容类型 具体枚举值由应用层定义") @ApiModelProperty(value = "消息内容类型 具体枚举值由应用层定义")
private Integer type; private Integer type;
@ApiModelProperty(value = "@用户列表")
private List<Long> atUserIds;
@ApiModelProperty(value = " 状态") @ApiModelProperty(value = " 状态")
private Integer status; private Integer status;

2
im-platform/src/main/resources/db/db.sql

@ -67,7 +67,9 @@ create table `im_group_message`(
`id` bigint not null auto_increment primary key comment 'id', `id` bigint not null auto_increment primary key comment 'id',
`group_id` bigint not null comment '群id', `group_id` bigint not null comment '群id',
`send_id` bigint not null comment '发送用户id', `send_id` bigint not null comment '发送用户id',
`send_nick_name` varchar(255) DEFAULT '' comment '发送用户昵称',
`content` text comment '发送内容', `content` text comment '发送内容',
`at_user_ids` varchar(1024) comment '@的用户id列表',
`type` tinyint(1) NOT NULL comment '消息类型 0:文字 1:图片 2:文件 3:语音 10:系统提示' , `type` tinyint(1) NOT NULL comment '消息类型 0:文字 1:图片 2:文件 3:语音 10:系统提示' ,
`status` tinyint(1) DEFAULT 0 comment '状态 0:正常 2:撤回', `status` tinyint(1) DEFAULT 0 comment '状态 0:正常 2:撤回',
`send_time` datetime DEFAULT CURRENT_TIMESTAMP comment '发送时间', `send_time` datetime DEFAULT CURRENT_TIMESTAMP comment '发送时间',

44
im-ui/src/components/chat/ChatAtBox.vue

@ -2,8 +2,7 @@
<el-scrollbar v-show="show" ref="scrollBox" class="group-member-choose" <el-scrollbar v-show="show" ref="scrollBox" class="group-member-choose"
:style="{'left':pos.x+'px','top':pos.y-300+'px'}"> :style="{'left':pos.x+'px','top':pos.y-300+'px'}">
<div v-for="(member,idx) in showMembers" :key="member.id"> <div v-for="(member,idx) in showMembers" :key="member.id">
<div class="member-item" <div class="member-item" :class="idx==activeIdx?'active':''" @click="onSelectMember(member)">
:class="idx==activeIdx?'active':''" @click="onSelectMember(member)">
<div class="member-avatar"> <div class="member-avatar">
<head-image :size="30" :name="member.aliasName" :url="member.headImage"> </head-image> <head-image :size="30" :name="member.aliasName" :url="member.headImage"> </head-image>
</div> </div>
@ -28,6 +27,9 @@
type: String, type: String,
default: "" default: ""
}, },
ownerId: {
type: Number,
},
members: { members: {
type: Array type: Array
} }
@ -40,19 +42,27 @@
y: 0 y: 0
}, },
activeIdx: 0, activeIdx: 0,
showMembers:[] showMembers: []
}; };
}, },
methods: { methods: {
init(){ init() {
this.activeIdx = 0;
this.$refs.scrollBox.wrap.scrollTop = 0; this.$refs.scrollBox.wrap.scrollTop = 0;
this.showMembers=[]; this.showMembers = [];
this.members.forEach((m)=>{ let userId = this.$store.state.userStore.userInfo.id;
if(m.aliasName.startsWith(this.searchText)){ let name = "全体成员";
if (this.ownerId == userId && name.startsWith(this.searchText)) {
this.showMembers.push({
userId: -1,
aliasName: name
})
}
this.members.forEach((m) => {
if (m.userId != userId && m.aliasName.startsWith(this.searchText)) {
this.showMembers.push(m); this.showMembers.push(m);
} }
}) })
this.activeIdx = this.showMembers.length > 0 ? 0: -1;
}, },
open(pos) { open(pos) {
this.show = true; this.show = true;
@ -69,38 +79,41 @@
} }
}, },
moveDown() { moveDown() {
console.log(this.activeIdx)
if (this.activeIdx < this.showMembers.length - 1) { if (this.activeIdx < this.showMembers.length - 1) {
this.activeIdx++; this.activeIdx++;
this.scrollToActive() this.scrollToActive()
} }
}, },
select() { select() {
this.onSelectMember(this.showMembers[this.activeIdx]) if (this.activeIdx >= 0) {
this.onSelectMember(this.showMembers[this.activeIdx])
}
this.close();
}, },
scrollToActive() { scrollToActive() {
console.log(this.$refs.scrollBox.wrap)
if (this.activeIdx * 35 - this.$refs.scrollBox.wrap.clientHeight > this.$refs.scrollBox.wrap.scrollTop) { if (this.activeIdx * 35 - this.$refs.scrollBox.wrap.clientHeight > this.$refs.scrollBox.wrap.scrollTop) {
this.$refs.scrollBox.wrap.scrollTop += 140; this.$refs.scrollBox.wrap.scrollTop += 140;
if (this.$refs.scrollBox.wrap.scrollTop > this.$refs.scrollBox.wrap.scrollHeight) { if (this.$refs.scrollBox.wrap.scrollTop > this.$refs.scrollBox.wrap.scrollHeight) {
this.$refs.scrollBox.wrap.scrollTop = this.$refs.scrollBox.wrap.scrollHeight this.$refs.scrollBox.wrap.scrollTop = this.$refs.scrollBox.wrap.scrollHeight
} }
} }
if (this.activeIdx * 35 < this.$refs.scrollBox.wrap.scrollTop) { if (this.activeIdx * 35 < this.$refs.scrollBox.wrap.scrollTop) {
this.$refs.scrollBox.wrap.scrollTop -= 140; this.$refs.scrollBox.wrap.scrollTop -= 140;
if (this.$refs.scrollBox.wrap.scrollTop < 0) { if (this.$refs.scrollBox.wrap.scrollTop < 0) {
this.$refs.scrollBox.wrap.scrollTop = 0; this.$refs.scrollBox.wrap.scrollTop = 0;
} }
} }
}, },
onSelectMember(member) { onSelectMember(member) {
this.$emit("select", member); this.$emit("select", member);
this.show = false; this.show = false;
} }
}, },
computed: {
isOwner() {
return this.$store.state.userStore.userInfo.id == this.ownerId;
}
},
watch: { watch: {
searchText: { searchText: {
handler(newText, oldText) { handler(newText, oldText) {
@ -132,6 +145,7 @@
padding-right: 5px; padding-right: 5px;
background-color: #fafafa; background-color: #fafafa;
white-space: nowrap; white-space: nowrap;
box-sizing: border-box;
&:hover { &:hover {
background-color: #eeeeee; background-color: #eeeeee;
@ -151,7 +165,7 @@
padding-left: 10px; padding-left: 10px;
height: 100%; height: 100%;
text-align: left; text-align: left;
line-height: 35px; line-height: 40px;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
font-size: 14px; font-size: 14px;

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

@ -1,5 +1,5 @@
<template> <template>
<div class="chat-box" @click="closeRefBox()"> <div class="chat-box" @click="closeRefBox()" @mousemove="readedMessage()">
<el-container> <el-container>
<el-header height="60px"> <el-header height="60px">
<span>{{title}}</span> <span>{{title}}</span>
@ -29,8 +29,7 @@
<div title="发送图片"> <div title="发送图片">
<file-upload :action="imageAction" :maxSize="5*1024*1024" <file-upload :action="imageAction" :maxSize="5*1024*1024"
:fileTypes="['image/jpeg', 'image/png', 'image/jpg', 'image/webp','image/gif']" :fileTypes="['image/jpeg', 'image/png', 'image/jpg', 'image/webp','image/gif']"
@before="onImageBefore" @success="onImageSuccess" @before="onImageBefore" @success="onImageSuccess" @fail="onImageFail">
@fail="onImageFail">
<i class="el-icon-picture-outline"></i> <i class="el-icon-picture-outline"></i>
</file-upload> </file-upload>
</div> </div>
@ -52,8 +51,8 @@
:disabled="lockMessage" @paste.prevent="onEditorPaste" :disabled="lockMessage" @paste.prevent="onEditorPaste"
@compositionstart="onEditorCompositionStart" @compositionstart="onEditorCompositionStart"
@compositionend="onEditorCompositionEnd" @input="onEditorInput" @compositionend="onEditorCompositionEnd" @input="onEditorInput"
placeholder="温馨提示:可以粘贴截图到这里了哦~" @blur="onEditBoxBlur()" placeholder="温馨提示:可以粘贴截图到这里了哦~" @blur="onEditBoxBlur()" @keydown.down="onKeyDown"
@keydown.down="onKeyDown" @keydown.up="onKeyUp" @keydown.enter.prevent="onKeyEnter"> @keydown.up="onKeyUp" @keydown.enter.prevent="onKeyEnter">
</div> </div>
<div v-show="sendImageUrl" class="send-image-area"> <div v-show="sendImageUrl" class="send-image-area">
@ -76,7 +75,7 @@
</el-container> </el-container>
</el-main> </el-main>
<emotion ref="emoBox" @emotion="onEmotion"></Emotion> <emotion ref="emoBox" @emotion="onEmotion"></Emotion>
<chat-at-box ref="atBox" :members="groupMembers" :search-text="atSearchText" <chat-at-box ref="atBox" :ownerId="group.ownerId" :members="groupMembers" :search-text="atSearchText"
@select="onAtSelect"></chat-at-box> @select="onAtSelect"></chat-at-box>
<chat-voice :visible="showVoice" @close="closeVoiceBox" @send="onSendVoice"></chat-voice> <chat-voice :visible="showVoice" @close="closeVoiceBox" @send="onSendVoice"></chat-voice>
<chat-history :visible="showHistory" :chat="chat" :friend="friend" :group="group" <chat-history :visible="showHistory" :chat="chat" :friend="friend" :group="group"
@ -154,7 +153,7 @@
}, },
onEditBoxBlur() { onEditBoxBlur() {
let selection = window.getSelection() let selection = window.getSelection()
// // (emoji)
this.focusNode = selection.focusNode; this.focusNode = selection.focusNode;
this.focusOffset = selection.focusOffset; this.focusOffset = selection.focusOffset;
}, },
@ -199,7 +198,7 @@
onAtSelect(member) { onAtSelect(member) {
let range = window.getSelection().getRangeAt(0) let range = window.getSelection().getRangeAt(0)
// @xx // @xx
range.setStart(this.focusNode, this.focusOffset - 1 -this.atSearchText.length) range.setStart(this.focusNode, this.focusOffset - 1 - this.atSearchText.length)
range.setEnd(this.focusNode, this.focusOffset) range.setEnd(this.focusNode, this.focusOffset)
range.deleteContents() range.deleteContents()
// //
@ -220,6 +219,7 @@
}, },
createSendText() { createSendText() {
let sendText = "" let sendText = ""
console.log(this.$refs.editBox.childNodes);
this.$refs.editBox.childNodes.forEach((node) => { this.$refs.editBox.childNodes.forEach((node) => {
if (node.nodeName == "#text") { if (node.nodeName == "#text") {
sendText += node.textContent; sendText += node.textContent;
@ -233,15 +233,16 @@
}, },
createAtUserIds() { createAtUserIds() {
let ids = []; let ids = [];
console.log(this.$refs.editBox.childNodes);
this.$refs.editBox.childNodes.forEach((node) => { this.$refs.editBox.childNodes.forEach((node) => {
if (node.nodeName == "SPAN") { if (node.nodeName == "SPAN") {
console.log(node);
ids.push(node.dataset.id); ids.push(node.dataset.id);
} }
}) })
return ids; return ids;
}, },
onEditorPaste(e) { onEditorPaste(e) {
let txt = event.clipboardData.getData('Text') let txt = event.clipboardData.getData('Text')
if (typeof(txt) == 'string') { if (typeof(txt) == 'string') {
let range = window.getSelection().getRangeAt(0) let range = window.getSelection().getRangeAt(0)
@ -457,6 +458,8 @@
} else { } else {
this.sendTextMessage(); this.sendTextMessage();
} }
//
this.readedMessage()
}, },
sendImageMessage() { sendImageMessage() {
let file = this.sendImageFile; let file = this.sendImageFile;
@ -479,8 +482,6 @@
}, },
sendTextMessage() { sendTextMessage() {
let sendText = this.createSendText(); let sendText = this.createSendText();
//
this.$refs.editBox.innerHTML = "";
if (!sendText.trim()) { if (!sendText.trim()) {
return return
} }
@ -491,6 +492,10 @@
} }
// id // id
this.fillTargetId(msgInfo, this.chat.targetId); this.fillTargetId(msgInfo, this.chat.targetId);
// @
if (this.chat.type == "GROUP") {
msgInfo.atUserIds = this.createAtUserIds();
}
this.lockMessage = true; this.lockMessage = true;
this.$http({ this.$http({
url: this.messageAction, url: this.messageAction,
@ -541,6 +546,9 @@
}); });
}, },
readedMessage() { readedMessage() {
if(this.chat.unreadCount==0){
return;
}
if (this.chat.type == "GROUP") { if (this.chat.type == "GROUP") {
var url = `/message/group/readed?groupId=${this.chat.targetId}` var url = `/message/group/readed?groupId=${this.chat.targetId}`
} else { } else {
@ -551,7 +559,6 @@
method: 'put' method: 'put'
}).then(() => { }).then(() => {
this.$store.commit("resetUnreadCount", this.chat) this.$store.commit("resetUnreadCount", this.chat)
this.scrollToBottom();
}) })
}, },
loadGroup(groupId) { loadGroup(groupId) {
@ -600,12 +607,13 @@
return msgInfo.sendId == this.mine.id ? this.mine.headImageThumb : this.chat.headImage return msgInfo.sendId == this.mine.id ? this.mine.headImageThumb : this.chat.headImage
} }
}, },
resetEditor(){ resetEditor() {
this.sendImageUrl = ""; this.sendImageUrl = "";
this.sendImageFile = null; this.sendImageFile = null;
this.$refs.editBox.innerHTML = ""; this.$nextTick(() => {
this.$refs.editBox.foucs(); this.$refs.editBox.innerHTML = "";
this.$refs.editBox.focus();
});
}, },
scrollToBottom() { scrollToBottom() {
this.$nextTick(() => { this.$nextTick(() => {
@ -666,8 +674,8 @@
unreadCount: { unreadCount: {
handler(newCount, oldCount) { handler(newCount, oldCount) {
if (newCount > 0) { if (newCount > 0) {
// //
this.readedMessage() this.scrollToBottom();
} }
} }
} }

79
im-ui/src/components/chat/ChatItem.vue

@ -1,20 +1,23 @@
<template> <template>
<div class="chat-item" :class="active ? 'active' : ''" @contextmenu.prevent="showRightMenu($event)"> <div class="chat-item" :class="active ? 'active' : ''" @contextmenu.prevent="showRightMenu($event)">
<div class="chat-left"> <div class="chat-left">
<head-image :url="chat.headImage" :name="chat.showName" :size="50" :id="chat.type=='PRIVATE'?chat.targetId:0"></head-image> <head-image :url="chat.headImage" :name="chat.showName" :size="50"
:id="chat.type=='PRIVATE'?chat.targetId:0"></head-image>
<div v-show="chat.unreadCount>0" class="unread-text">{{chat.unreadCount}}</div> <div v-show="chat.unreadCount>0" class="unread-text">{{chat.unreadCount}}</div>
</div> </div>
<div class="chat-right"> <div class="chat-right">
<div class="chat-name"> <div class="chat-name">
{{ chat.showName}} <div class="chat-name-text">{{chat.showName}}</div>
<div class="chat-time-text">{{showTime}}</div>
</div> </div>
<div class="chat-content"> <div class="chat-content">
<div class="chat-at-text">{{atText}}</div>
<div class="chat-send-name" v-show="chat.sendNickName">{{chat.sendNickName+':&nbsp;'}}</div>
<div class="chat-content-text" v-html="$emo.transform(chat.lastContent)"></div> <div class="chat-content-text" v-html="$emo.transform(chat.lastContent)"></div>
<div class="chat-time">{{showTime}}</div>
</div> </div>
</div> </div>
<right-menu v-show="rightMenu.show" :pos="rightMenu.pos" :items="rightMenu.items" <right-menu v-show="rightMenu.show" :pos="rightMenu.pos" :items="rightMenu.items" @close="rightMenu.show=false"
@close="rightMenu.show=false" @select="handleSelectMenu"></right-menu> @select="onSelectMenu"></right-menu>
</div> </div>
</template> </template>
@ -68,13 +71,22 @@
}; };
this.rightMenu.show = "true"; this.rightMenu.show = "true";
}, },
handleSelectMenu(item) { onSelectMenu(item) {
this.$emit(item.key.toLowerCase(), this.msgInfo); this.$emit(item.key.toLowerCase(), this.msgInfo);
} }
}, },
computed: { computed: {
showTime() { showTime() {
return this.$date.toTimeText(this.chat.lastSendTime, true) return this.$date.toTimeText(this.chat.lastSendTime, true)
},
atText() {
console.log(this.chat.atMe)
if (this.chat.atMe) {
return "[有人@我]"
} else if (this.chat.atAll) {
return "[@全体成员]"
}
return "";
} }
} }
} }
@ -131,36 +143,57 @@
padding-left: 10px; padding-left: 10px;
text-align: left; text-align: left;
overflow: hidden; overflow: hidden;
.chat-name { .chat-name {
font-size: 15px; display: flex;
font-weight: 600; line-height: 25px;
line-height: 30px;
white-space: nowrap; .chat-name-text {
overflow: hidden; flex: 1;
font-size: 15px;
font-weight: 600;
white-space: nowrap;
overflow: hidden;
}
.chat-time-text{
font-size: 13px;
text-align: right;
color: #888888;
white-space: nowrap;
overflow: hidden;
padding-left: 10px;
}
} }
.chat-content { .chat-content {
display: flex; display: flex;
line-height: 30px; line-height: 22px;
.chat-at-text {
color: #c70b0b;
font-size: 12px;
}
.chat-send-name{
font-size: 13px;
}
.chat-content-text { .chat-content-text {
flex:1; flex: 1;
font-size: 14px;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
font-size: 13px;
img { img {
width: 30px !important; width: 20px !important;
height: 30px !important; height: 20px !important;
vertical-align: bottom;
} }
} }
.chat-time {
font-size: 13px;
text-align: right;
color: #888888;
white-space: nowrap;
overflow: hidden;
}
} }
} }
} }

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

@ -73,6 +73,8 @@ export default {
if (state.chats[idx].type == chatInfo.type && if (state.chats[idx].type == chatInfo.type &&
state.chats[idx].targetId == chatInfo.targetId) { state.chats[idx].targetId == chatInfo.targetId) {
state.chats[idx].unreadCount = 0; state.chats[idx].unreadCount = 0;
state.chats[idx].atMe = false;
state.chats[idx].atAll = false;
} }
} }
this.commit("saveToStorage"); this.commit("saveToStorage");
@ -144,10 +146,21 @@ export default {
chat.lastContent = msgInfo.content; chat.lastContent = msgInfo.content;
} }
chat.lastSendTime = msgInfo.sendTime; chat.lastSendTime = msgInfo.sendTime;
chat.sendNickName = msgInfo.sendNickName;
// 未读加1 // 未读加1
if (!msgInfo.selfSend && msgInfo.status != MESSAGE_STATUS.READED) { if (!msgInfo.selfSend && msgInfo.status != MESSAGE_STATUS.READED) {
chat.unreadCount++; chat.unreadCount++;
} }
// 是否有人@我
if(!msgInfo.selfSend && chat.type=="GROUP" && msgInfo.atUserIds){
let userId = userStore.state.userInfo.id;
if(msgInfo.atUserIds.indexOf(userId)>=0){
chat.atMe = true;
}
if(msgInfo.atUserIds.indexOf(-1)>=0){
chat.atAll = true;
}
}
// 记录消息的最大id // 记录消息的最大id
if (msgInfo.id && type == "PRIVATE" && msgInfo.id > state.privateMsgMaxId) { if (msgInfo.id && type == "PRIVATE" && msgInfo.id > state.privateMsgMaxId) {
state.privateMsgMaxId = msgInfo.id; state.privateMsgMaxId = msgInfo.id;

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

@ -33,6 +33,7 @@ export default {
}, },
addFriend(state, friend) { addFriend(state, friend) {
state.friends.push(friend); state.friends.push(friend);
state.activeIndex = state.friends.length-1;
}, },
refreshOnlineStatus(state){ refreshOnlineStatus(state){
let userIds = []; let userIds = [];

30
im-ui/src/view/Chat.vue

@ -1,24 +1,24 @@
<template> <template>
<el-container> <el-container class="chat-page">
<el-aside width="250px" class="l-chat-box"> <el-aside width="260px" class="chat-list-box">
<div class="l-chat-header"> <div class="chat-list-header">
<el-input width="200px" placeholder="搜索" v-model="searchText"> <el-input width="200px" placeholder="搜索" v-model="searchText">
<el-button slot="append" icon="el-icon-search"></el-button> <el-button slot="append" icon="el-icon-search"></el-button>
</el-input> </el-input>
</div> </div>
<div class="l-chat-loadding" v-if="loading" v-loading="true" element-loading-text="消息接收中..." <div class="chat-list-loadding" v-if="loading" v-loading="true" element-loading-text="消息接收中..."
element-loading-spinner="el-icon-loading" element-loading-background="#eee"> element-loading-spinner="el-icon-loading" element-loading-background="#eee">
<div class="chat-loading-box"></div> <div class="chat-loading-box"></div>
</div> </div>
<el-scrollbar class="l-chat-list"> <el-scrollbar class="chat-list-items">
<div v-for="(chat,index) in chatStore.chats" :key="index"> <div v-for="(chat,index) in chatStore.chats" :key="index">
<chat-item v-show="chat.showName.startsWith(searchText)" :chat="chat" :index="index" <chat-item v-show="chat.showName.startsWith(searchText)" :chat="chat" :index="index"
@click.native="handleActiveItem(index)" @delete="handleDelItem(index)" @top="handleTop(index)" @click.native="onActiveItem(index)" @delete="onDelItem(index)" @top="onTop(index)"
:active="index === chatStore.activeIndex"></chat-item> :active="index === chatStore.activeIndex"></chat-item>
</div> </div>
</el-scrollbar> </el-scrollbar>
</el-aside> </el-aside>
<el-container class="r-chat-box"> <el-container class="chat-box">
<chat-box v-show="activeChat.targetId>0" :chat="activeChat"></chat-box> <chat-box v-show="activeChat.targetId>0" :chat="activeChat"></chat-box>
</el-container> </el-container>
</el-container> </el-container>
@ -43,13 +43,13 @@
} }
}, },
methods: { methods: {
handleActiveItem(index) { onActiveItem(index) {
this.$store.commit("activeChat", index); this.$store.commit("activeChat", index);
}, },
handleDelItem(index) { onDelItem(index) {
this.$store.commit("removeChat", index); this.$store.commit("removeChat", index);
}, },
handleTop(chatIdx) { onTop(chatIdx) {
this.$store.commit("moveTop", chatIdx); this.$store.commit("moveTop", chatIdx);
}, },
}, },
@ -80,21 +80,21 @@
</script> </script>
<style lang="scss"> <style lang="scss">
.el-container { .chat-page {
.l-chat-box { .chat-list-box {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
border: #dddddd solid 1px; border: #dddddd solid 1px;
background: white; background: white;
width: 3rem; width: 3rem;
.l-chat-header { .chat-list-header {
padding: 5px; padding: 5px;
background-color: white; background-color: white;
line-height: 50px; line-height: 50px;
} }
.l-chat-loadding{ .chat-list-loadding{
height: 50px; height: 50px;
background-color: #eee; background-color: #eee;
@ -103,7 +103,7 @@
} }
} }
.l-friend-ist { .chat-list-items {
flex: 1; flex: 1;
} }
} }

109
im-ui/src/view/Friend.vue

@ -1,50 +1,57 @@
<template> <template>
<el-container> <el-container class="friend-page">
<el-aside width="250px" class="l-friend-box"> <el-aside width="260px" class="friend-list-box">
<div class="l-friend-header"> <div class="friend-list-header">
<div class="l-friend-search"> <div class="friend-list-search">
<el-input width="200px" placeholder="搜索好友" v-model="searchText"> <el-input width="200px" placeholder="搜索好友" v-model="searchText">
<el-button slot="append" icon="el-icon-search"></el-button> <el-button slot="append" icon="el-icon-search"></el-button>
</el-input> </el-input>
</div> </div>
<el-button plain icon="el-icon-plus" style="border: none; padding:12px; font-size: 20px;color: black;" <el-button plain icon="el-icon-plus" style="border: none; padding:12px; font-size: 20px;color: black;"
title="添加好友" @click="handleShowAddFriend()"></el-button> title="添加好友" @click="onShowAddFriend()"></el-button>
<add-friend :dialogVisible="showAddFriend" @close="handleCloseAddFriend"> <add-friend :dialogVisible="showAddFriend" @close="onCloseAddFriend">
</add-friend> </add-friend>
</div> </div>
<el-scrollbar class="l-friend-list"> <el-scrollbar class="friend-list-items">
<div v-for="(friend,index) in $store.state.friendStore.friends" :key="index"> <div v-for="(friend,index) in $store.state.friendStore.friends" :key="index">
<friend-item v-show="friend.nickName.startsWith(searchText)" :index="index" <friend-item v-show="friend.nickName.startsWith(searchText)" :index="index"
:active="index === $store.state.friendStore.activeIndex" @chat="handleSendMessage(friend)" :active="index === $store.state.friendStore.activeIndex" @chat="onSendMessage(friend)"
@delete="handleDelItem(friend,index)" @click.native="handleActiveItem(friend,index)"> @delete="onDelItem(friend,index)" @click.native="onActiveItem(friend,index)">
</friend-item> </friend-item>
</div> </div>
</el-scrollbar> </el-scrollbar>
</el-aside> </el-aside>
<el-container class="r-friend-box"> <el-container class="friend-box">
<div class="r-friend-header" v-show="userInfo.id"> <div class="friend-header" v-show="userInfo.id">
{{userInfo.nickName}} {{userInfo.nickName}}
</div> </div>
<div v-show="userInfo.id"> <div v-show="userInfo.id">
<div class="user-detail"> <div class="friend-detail">
<head-image class="detail-head-image" :size="200" <head-image :size="200"
:name="userInfo.nickName" :name="userInfo.nickName"
:url="userInfo.headImage" :url="userInfo.headImage"
@click.native="showFullImage()"></head-image> @click.native="showFullImage()"></head-image>
<div class="info-item"> <div>
<el-descriptions title="好友信息" class="description" :column="1"> <div class="info-item">
<el-descriptions-item label="用户名">{{ userInfo.userName }} <el-descriptions title="好友信息" class="description" :column="1">
</el-descriptions-item> <el-descriptions-item label="用户名">{{ userInfo.userName }}
<el-descriptions-item label="昵称">{{ userInfo.nickName }} </el-descriptions-item>
</el-descriptions-item> <el-descriptions-item label="昵称">{{ userInfo.nickName }}
<el-descriptions-item label="性别">{{ userInfo.sex==0?"男":"女" }}</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="签名">{{ userInfo.signature }}</el-descriptions-item> <el-descriptions-item label="性别">{{ userInfo.sex==0?"男":"女" }}</el-descriptions-item>
</el-descriptions> <el-descriptions-item label="签名">{{ userInfo.signature }}</el-descriptions-item>
</el-descriptions>
</div>
<div class="frient-btn-group">
<el-button v-show="isFriend" icon="el-icon-chat-dot-round" type="primary" @click="onSendMessage(userInfo)">发送消息</el-button>
<el-button v-show="!isFriend" icon="el-icon-plus" type="primary" @click="onAddFriend(userInfo)">加为好友</el-button>
<el-button v-show="isFriend" icon="el-icon-delete" type="danger" @click="onDelItem(userInfo,friendStore.activeIndex)">删除好友</el-button>
</div>
</div> </div>
</div> </div>
<div class="btn-group"> <el-divider content-position="center"></el-divider>
<el-button class="send-btn" @click="handleSendMessage(userInfo)">发送消息</el-button>
</div>
</div> </div>
</el-container> </el-container>
</el-container> </el-container>
@ -71,17 +78,17 @@
} }
}, },
methods: { methods: {
handleShowAddFriend() { onShowAddFriend() {
this.showAddFriend = true; this.showAddFriend = true;
}, },
handleCloseAddFriend() { onCloseAddFriend() {
this.showAddFriend = false; this.showAddFriend = false;
}, },
handleActiveItem(friend, index) { onActiveItem(friend, index) {
this.$store.commit("activeFriend", index); this.$store.commit("activeFriend", index);
this.loadUserInfo(friend, index); this.loadUserInfo(friend, index);
}, },
handleDelItem(friend, index) { onDelItem(friend, index) {
this.$confirm(`确认要解除与 '${friend.nickName}'的好友关系吗?`, '确认解除?', { this.$confirm(`确认要解除与 '${friend.nickName}'的好友关系吗?`, '确认解除?', {
confirmButtonText: '确定', confirmButtonText: '确定',
cancelButtonText: '取消', cancelButtonText: '取消',
@ -97,7 +104,25 @@
}) })
}) })
}, },
handleSendMessage(user) { onAddFriend(user){
this.$http({
url: "/friend/add",
method: "post",
params: {
friendId: user.id
}
}).then((data) => {
this.$message.success("添加成功,对方已成为您的好友");
let friend = {
id:user.id,
nickName: user.nickName,
headImage: user.headImage,
online: user.online
}
this.$store.commit("addFriend",friend);
})
},
onSendMessage(user) {
let chat = { let chat = {
type: 'PRIVATE', type: 'PRIVATE',
targetId: user.id, targetId: user.id,
@ -112,7 +137,6 @@
if (this.userInfo.headImage) { if (this.userInfo.headImage) {
this.$store.commit('showFullImageBox', this.userInfo.headImage); this.$store.commit('showFullImageBox', this.userInfo.headImage);
} }
}, },
updateFriendInfo(friend, user, index) { updateFriendInfo(friend, user, index) {
// storestore // storestore
@ -145,6 +169,9 @@
computed: { computed: {
friendStore() { friendStore() {
return this.$store.state.friendStore; return this.$store.state.friendStore;
},
isFriend(){
return this.friendStore.friends.find((f)=>f.id==this.userInfo.id);
} }
}, },
mounted() { mounted() {
@ -158,36 +185,36 @@
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.el-container { .friend-page {
.l-friend-box { .friend-list-box {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
border: #dddddd solid 1px; border: #dddddd solid 1px;
background: white; background: white;
.l-friend-header { .friend-list-header {
height: 50px; height: 50px;
display: flex; display: flex;
align-items: center; align-items: center;
padding: 5px; padding: 5px;
background-color: white; background-color: white;
.l-friend-search { .friend-list-search {
flex: 1; flex: 1;
} }
} }
.l-friend-ist { .friend-list-items {
flex: 1; flex: 1;
} }
} }
.r-friend-box { .friend-box {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
border: #dddddd solid 1px; border: #dddddd solid 1px;
.r-friend-header { .friend-header {
width: 100%; width: 100%;
height: 50px; height: 50px;
padding: 5px; padding: 5px;
@ -200,7 +227,7 @@
border: #dddddd solid 1px; border: #dddddd solid 1px;
} }
.user-detail { .friend-detail {
display: flex; display: flex;
padding: 50px 80px 20px 80px; padding: 50px 80px 20px 80px;
text-align: center; text-align: center;
@ -215,9 +242,9 @@
} }
} }
.btn-group { .frient-btn-group {
text-align: left !important; text-align: left !important;
padding-left: 120px; padding: 20px;
} }
} }
} }

94
im-ui/src/view/Group.vue

@ -1,33 +1,33 @@
<template> <template>
<el-container class="im-group-box"> <el-container class="group-page">
<el-aside width="250px" class="l-group-box"> <el-aside width="260px" class="group-list-box">
<div class="l-group-header"> <div class="group-list-header">
<div class="l-group-search"> <div class="group-list-search">
<el-input width="200px" placeholder="搜索群聊" v-model="searchText"> <el-input width="200px" placeholder="搜索群聊" v-model="searchText">
<el-button slot="append" icon="el-icon-search"></el-button> <el-button slot="append" icon="el-icon-search"></el-button>
</el-input> </el-input>
</div> </div>
<el-button plain icon="el-icon-plus" style="border: none; padding: 12px; font-size: 20px;color: black;" <el-button plain icon="el-icon-plus" style="border: none; padding: 12px; font-size: 20px;color: black;"
title="创建群聊" @click="handleCreateGroup()"></el-button> title="创建群聊" @click="onCreateGroup()"></el-button>
</div> </div>
<el-scrollbar class="l-group-list"> <el-scrollbar class="group-list-items">
<div v-for="(group,index) in groupStore.groups" :key="index"> <div v-for="(group,index) in groupStore.groups" :key="index">
<group-item v-show="group.remark.startsWith(searchText)" :group="group" <group-item v-show="group.remark.startsWith(searchText)" :group="group"
:active="index === groupStore.activeIndex" @click.native="handleActiveItem(group,index)"> :active="index === groupStore.activeIndex" @click.native="onActiveItem(group,index)">
</group-item> </group-item>
</div> </div>
</el-scrollbar> </el-scrollbar>
</el-aside> </el-aside>
<el-container class="r-group-box"> <el-container class="group-box">
<div class="r-group-header" v-show="activeGroup.id"> <div class="group-header" v-show="activeGroup.id">
{{activeGroup.remark}}({{groupMembers.length}}) {{activeGroup.remark}}({{groupMembers.length}})
</div> </div>
<el-scrollbar class="r-group-container"> <el-scrollbar class="group-container">
<div v-show="activeGroup.id"> <div v-show="activeGroup.id">
<div class="r-group-info"> <div class="group-info">
<div> <div>
<file-upload v-show="isOwner" class="avatar-uploader" :action="imageAction" <file-upload v-show="isOwner" class="avatar-uploader" :action="imageAction"
:showLoading="true" :maxSize="maxSize" @success="handleUploadSuccess" :showLoading="true" :maxSize="maxSize" @success="onUploadSuccess"
:fileTypes="['image/jpeg', 'image/png', 'image/jpg','image/webp']"> :fileTypes="['image/jpeg', 'image/png', 'image/jpg','image/webp']">
<img v-if="activeGroup.headImage" :src="activeGroup.headImage" class="avatar"> <img v-if="activeGroup.headImage" :src="activeGroup.headImage" class="avatar">
<i v-else class="el-icon-plus avatar-uploader-icon"></i> <i v-else class="el-icon-plus avatar-uploader-icon"></i>
@ -36,9 +36,9 @@
:url="activeGroup.headImage" :url="activeGroup.headImage"
:name="activeGroup.remark"> :name="activeGroup.remark">
</head-image> </head-image>
<el-button class="send-btn" @click="handleSendMessage()">消息</el-button> <el-button class="send-btn" icon="el-icon-chat-dot-round" type="primary" @click="onSendMessage()">发消息</el-button>
</div> </div>
<el-form class="r-group-form" label-width="130px" :model="activeGroup" :rules="rules" <el-form class="group-form" label-width="130px" :model="activeGroup" :rules="rules"
ref="groupForm"> ref="groupForm">
<el-form-item label="群聊名称" prop="name"> <el-form-item label="群聊名称" prop="name">
<el-input v-model="activeGroup.name" :disabled="!isOwner" maxlength="20"></el-input> <el-input v-model="activeGroup.name" :disabled="!isOwner" maxlength="20"></el-input>
@ -57,29 +57,29 @@
<el-input v-model="activeGroup.notice" :disabled="!isOwner" type="textarea" <el-input v-model="activeGroup.notice" :disabled="!isOwner" type="textarea"
maxlength="1024" placeholder="群主未设置"></el-input> maxlength="1024" placeholder="群主未设置"></el-input>
</el-form-item> </el-form-item>
<div class="btn-group"> <div>
<el-button type="success" @click="handleSaveGroup()">提交</el-button> <el-button type="success" @click="onSaveGroup()">提交</el-button>
<el-button type="danger" v-show="!isOwner" @click="handleQuit()">退出群聊</el-button> <el-button type="danger" v-show="!isOwner" @click="onQuit()">退出群聊</el-button>
<el-button type="danger" v-show="isOwner" @click="handleDissolve()">解散群聊</el-button> <el-button type="danger" v-show="isOwner" @click="onDissolve()">解散群聊</el-button>
</div> </div>
</el-form> </el-form>
</div> </div>
<el-divider content-position="center"></el-divider> <el-divider content-position="center"></el-divider>
<el-scrollbar style="height:400px;"> <el-scrollbar style="height:400px;">
<div class="r-group-member-list"> <div class="group-member-list">
<div v-for="(member) in groupMembers" :key="member.id"> <div v-for="(member) in groupMembers" :key="member.id">
<group-member v-show="!member.quit" class="r-group-member" :member="member" <group-member v-show="!member.quit" class="group-member" :member="member"
:showDel="isOwner&&member.userId!=activeGroup.ownerId" :showDel="isOwner&&member.userId!=activeGroup.ownerId"
@del="handleKick"></group-member> @del="onKick"></group-member>
</div> </div>
<div class="r-group-invite"> <div class="group-invite">
<div class="invite-member-btn" title="邀请好友进群聊" @click="handleInviteMember()"> <div class="invite-member-btn" title="邀请好友进群聊" @click="onInviteMember()">
<i class="el-icon-plus"></i> <i class="el-icon-plus"></i>
</div> </div>
<div class="invite-member-text">邀请</div> <div class="invite-member-text">邀请</div>
<add-group-member :visible="showAddGroupMember" :groupId="activeGroup.id" <add-group-member :visible="showAddGroupMember" :groupId="activeGroup.id"
:members="groupMembers" @reload="loadGroupMembers" :members="groupMembers" @reload="loadGroupMembers"
@close="handleCloseAddGroupMember"></add-group-member> @close="onCloseAddGroupMember"></add-group-member>
</div> </div>
</div> </div>
</el-scrollbar> </el-scrollbar>
@ -122,7 +122,7 @@
}; };
}, },
methods: { methods: {
handleCreateGroup() { onCreateGroup() {
this.$prompt('请输入群聊名称', '创建群聊', { this.$prompt('请输入群聊名称', '创建群聊', {
confirmButtonText: '确定', confirmButtonText: '确定',
cancelButtonText: '取消', cancelButtonText: '取消',
@ -147,24 +147,24 @@
}) })
}) })
}, },
handleActiveItem(group, index) { onActiveItem(group, index) {
this.$store.commit("activeGroup", index); this.$store.commit("activeGroup", index);
// store // store
this.activeGroup = JSON.parse(JSON.stringify(group)); this.activeGroup = JSON.parse(JSON.stringify(group));
// //
this.loadGroupMembers(); this.loadGroupMembers();
}, },
handleInviteMember() { onInviteMember() {
this.showAddGroupMember = true; this.showAddGroupMember = true;
}, },
handleCloseAddGroupMember() { onCloseAddGroupMember() {
this.showAddGroupMember = false; this.showAddGroupMember = false;
}, },
handleUploadSuccess(res) { onUploadSuccess(res) {
this.activeGroup.headImage = res.data.originUrl; this.activeGroup.headImage = res.data.originUrl;
this.activeGroup.headImageThumb = res.data.thumbUrl; this.activeGroup.headImageThumb = res.data.thumbUrl;
}, },
handleSaveGroup() { onSaveGroup() {
this.$refs['groupForm'].validate((valid) => { this.$refs['groupForm'].validate((valid) => {
if (valid) { if (valid) {
let vo = this.activeGroup; let vo = this.activeGroup;
@ -179,7 +179,7 @@
} }
}); });
}, },
handleDissolve() { onDissolve() {
this.$confirm('确认要解散群聊吗?', '确认解散?', { this.$confirm('确认要解散群聊吗?', '确认解散?', {
confirmButtonText: '确定', confirmButtonText: '确定',
cancelButtonText: '取消', cancelButtonText: '取消',
@ -198,7 +198,7 @@
}) })
}, },
handleKick(member) { onKick(member) {
this.$confirm(`确定将成员'${member.aliasName}'移出群聊吗?`, '确认移出?', { this.$confirm(`确定将成员'${member.aliasName}'移出群聊吗?`, '确认移出?', {
confirmButtonText: '确定', confirmButtonText: '确定',
cancelButtonText: '取消', cancelButtonText: '取消',
@ -217,7 +217,7 @@
}) })
}, },
handleQuit() { onQuit() {
this.$confirm('退出群聊后将不再接受群里的消息,确认退出吗?', '确认退出?', { this.$confirm('退出群聊后将不再接受群里的消息,确认退出吗?', '确认退出?', {
confirmButtonText: '确定', confirmButtonText: '确定',
cancelButtonText: '取消', cancelButtonText: '取消',
@ -234,7 +234,7 @@
}) })
}, },
handleSendMessage() { onSendMessage() {
let chat = { let chat = {
type: 'GROUP', type: 'GROUP',
targetId: this.activeGroup.id, targetId: this.activeGroup.id,
@ -282,36 +282,36 @@
</script> </script>
<style lang="scss"> <style lang="scss">
.im-group-box { .group-page {
.l-group-box { .group-list-box {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
border: #dddddd solid 1px; border: #dddddd solid 1px;
background: white; background: white;
.l-group-header { .group-list-header {
height: 50px; height: 50px;
display: flex; display: flex;
align-items: center; align-items: center;
padding: 5px; padding: 5px;
background-color: white; background-color: white;
.l-group-search { .group-list-search {
flex: 1; flex: 1;
} }
} }
.l-group-ist { .group-list-items {
flex: 1; flex: 1;
} }
} }
.r-group-box { .group-box {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
border: #dddddd solid 1px; border: #dddddd solid 1px;
.r-group-header { .group-header {
width: 100%; width: 100%;
height: 50px; height: 50px;
padding: 5px; padding: 5px;
@ -324,14 +324,14 @@
border: #dddddd solid 1px; border: #dddddd solid 1px;
} }
.r-group-container { .group-container {
padding: 50px; padding: 50px;
.r-group-info { .group-info {
display: flex; display: flex;
padding: 5px 20px; padding: 5px 20px;
.r-group-form { .group-form {
flex: 1; flex: 1;
padding-left: 40px; padding-left: 40px;
max-width: 800px; max-width: 800px;
@ -373,7 +373,7 @@
} }
} }
.r-group-member-list { .group-member-list {
padding: 5px 20px; padding: 5px 20px;
display: flex; display: flex;
align-items: center; align-items: center;
@ -381,11 +381,11 @@
font-size: 16px; font-size: 16px;
text-align: center; text-align: center;
.r-group-member { .group-member {
margin-right: 15px; margin-right: 15px;
} }
.r-group-invite { .group-invite {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;

9
im-ui/src/view/Home.vue

@ -1,5 +1,5 @@
<template> <template>
<el-container> <el-container class="home-page">
<el-aside width="80px" class="navi-bar"> <el-aside width="80px" class="navi-bar">
<div class="user-head-image"> <div class="user-head-image">
<head-image :name="$store.state.userStore.userInfo.nickName" <head-image :name="$store.state.userStore.userInfo.nickName"
@ -31,7 +31,7 @@
</el-menu-item> </el-menu-item>
</el-menu> </el-menu>
<div class="exit-box" @click="handleExit()" title="退出"> <div class="exit-box" @click="onExit()" title="退出">
<span class="el-icon-circle-close"></span> <span class="el-icon-circle-close"></span>
</div> </div>
</el-aside> </el-aside>
@ -252,8 +252,8 @@
this.playAudioTip(); this.playAudioTip();
} }
}, },
handleExit() { onExit() {
this.$wsApi.close(); this.$wsApi.close(3000);
sessionStorage.removeItem("accessToken"); sessionStorage.removeItem("accessToken");
location.href = "/"; location.href = "/";
}, },
@ -338,6 +338,7 @@
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.navi-bar { .navi-bar {
background: #333333; background: #333333;
padding: 10px; padding: 10px;

Loading…
Cancel
Save