Browse Source

!5 支持发送语音

Merge pull request !5 from blue/v_1.1.0
master
blue 3 years ago
committed by Gitee
parent
commit
13f96ed059
No known key found for this signature in database GPG Key ID: 173E9B9CA92EEF8F
  1. 2
      commom/src/main/java/com/bx/common/contant/Constant.java
  2. 4
      commom/src/main/java/com/bx/common/enums/FileTypeEnum.java
  3. 4
      im-platform/src/main/java/com/bx/implatform/service/impl/GroupMessageServiceImpl.java
  4. 4
      im-platform/src/main/java/com/bx/implatform/service/impl/PrivateMessageServiceImpl.java
  5. 2
      im-platform/src/main/resources/application.yml
  6. 17
      im-server/src/main/java/com/bx/imserver/websocket/processor/HeartbeatProcessor.java
  7. 9
      im-server/src/main/java/com/bx/imserver/websocket/processor/LoginProcessor.java
  8. 4
      im-ui/.env.production
  9. 3
      im-ui/package.json
  10. 1
      im-ui/src/api/emotion.js
  11. 494
      im-ui/src/components/chat/ChatBox.vue
  12. 319
      im-ui/src/components/chat/ChatGroup.vue
  13. 251
      im-ui/src/components/chat/ChatPrivate.vue
  14. 141
      im-ui/src/components/chat/ChatVoice.vue
  15. 33
      im-ui/src/components/chat/MessageItem.vue
  16. 24
      im-ui/src/components/common/Emotion.vue
  17. 2
      im-ui/src/components/common/FullImage.vue
  18. 4
      im-ui/src/components/group/AddGroupMember.vue
  19. 2
      im-ui/src/components/setting/Setting.vue
  20. 19
      im-ui/src/store/chatStore.js
  21. 149
      im-ui/src/view/Chat.vue
  22. 4
      im-ui/src/view/Friend.vue
  23. 10
      im-ui/src/view/Group.vue
  24. 8
      im-ui/src/view/Home.vue
  25. 34
      im-ui/src/view/Login.vue
  26. 3
      im-ui/src/view/Register.vue

2
commom/src/main/java/com/bx/common/contant/Constant.java

@ -10,4 +10,6 @@ public class Constant {
public static final long MAX_FILE_SIZE = 10*1024*1024;
// 群聊最大人数
public static final long MAX_GROUP_MEMBER = 500;
// 在线状态过期时间 600s
public static final long ONLINE_TIMEOUT_SECOND = 600;
}

4
commom/src/main/java/com/bx/common/enums/FileTypeEnum.java

@ -4,7 +4,9 @@ public enum FileTypeEnum {
FILE(0,"文件"),
IMAGE(1,"图片"),
VIDEO(2,"视频");
VIDEO(2,"视频"),
AUDIO(3,"声音");
private Integer code;

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

@ -16,6 +16,7 @@ import com.bx.implatform.service.IGroupMessageService;
import com.bx.implatform.service.IGroupService;
import com.bx.implatform.session.SessionContext;
import com.bx.implatform.vo.GroupMessageVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
@ -24,7 +25,7 @@ import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
@Slf4j
@Service
public class GroupMessageServiceImpl extends ServiceImpl<GroupMessageMapper, GroupMessage> implements IGroupMessageService {
@ -86,6 +87,7 @@ public class GroupMessageServiceImpl extends ServiceImpl<GroupMessageMapper, Gro
String key = RedisKey.IM_UNREAD_GROUP_MESSAGE +entry.getKey();
redisTemplate.opsForList().rightPush(key,msgInfo);
}
log.info("发送群聊消息,发送id:{},群聊id:{}",userId,vo.getGroupId());
}
/**

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

@ -14,6 +14,7 @@ import com.bx.implatform.service.IFriendService;
import com.bx.implatform.service.IPrivateMessageService;
import com.bx.implatform.session.SessionContext;
import com.bx.implatform.vo.PrivateMessageVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
@ -22,7 +23,7 @@ import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
@Slf4j
@Service
public class PrivateMessageServiceImpl extends ServiceImpl<PrivateMessageMapper, PrivateMessage> implements IPrivateMessageService {
@ -59,6 +60,7 @@ 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());
}
/**

2
im-platform/src/main/resources/application.yml

@ -5,7 +5,7 @@ server:
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/box-im?useSSL=false&useUnicode=true&characterEncoding=utf-8
url: jdbc:mysql://localhost:3306/box-im?useSSL=false&useUnicode=true&characterEncoding=utf-8&allowPublicKeyRetrieval=true
username: root
password: root

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

@ -1,17 +1,22 @@
package com.bx.imserver.websocket.processor;
import cn.hutool.core.bean.BeanUtil;
import com.bx.common.contant.Constant;
import com.bx.common.contant.RedisKey;
import com.bx.common.enums.WSCmdEnum;
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;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.concurrent.TimeUnit;
@Slf4j
@Component
@ -30,6 +35,18 @@ public class HeartbeatProcessor extends MessageProcessor<HeartbeatInfo> {
SendInfo sendInfo = new SendInfo();
sendInfo.setCmd(WSCmdEnum.HEART_BEAT.getCode());
ctx.channel().writeAndFlush(sendInfo);
// 设置属性
AttributeKey<Long> attr = AttributeKey.valueOf("HEARTBEAt_TIMES");
Long heartbeatTimes = ctx.channel().attr(attr).get();
ctx.channel().attr(attr).set(++heartbeatTimes);
if(heartbeatTimes%10 == 0){
// 每心跳10次,用户在线状态续一次命
attr = AttributeKey.valueOf("USER_ID");
Long userId = ctx.channel().attr(attr).get();
String key = RedisKey.IM_USER_SERVER_ID+userId;
redisTemplate.expire(key, Constant.ONLINE_TIMEOUT_SECOND, TimeUnit.SECONDS);
}
}

9
im-server/src/main/java/com/bx/imserver/websocket/processor/LoginProcessor.java

@ -1,6 +1,7 @@
package com.bx.imserver.websocket.processor;
import cn.hutool.core.bean.BeanUtil;
import com.bx.common.contant.Constant;
import com.bx.common.contant.RedisKey;
import com.bx.common.enums.WSCmdEnum;
import com.bx.common.model.im.LoginInfo;
@ -15,6 +16,7 @@ import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.concurrent.TimeUnit;
@Slf4j
@Component
@ -39,12 +41,15 @@ public class LoginProcessor extends MessageProcessor<LoginInfo> {
}
// 绑定用户和channel
WebsocketChannelCtxHolder.addChannelCtx(loginInfo.getUserId(),ctx);
// 设置属性
// 设置用户id属性
AttributeKey<Long> attr = AttributeKey.valueOf("USER_ID");
ctx.channel().attr(attr).set(loginInfo.getUserId());
// 心跳次数
attr = AttributeKey.valueOf("HEARTBEAt_TIMES");
ctx.channel().attr(attr).set(0L);
// 在redis上记录每个user的channelId,15秒没有心跳,则自动过期
String key = RedisKey.IM_USER_SERVER_ID+loginInfo.getUserId();
redisTemplate.opsForValue().set(key, WSServer.getServerId());
redisTemplate.opsForValue().set(key, WSServer.getServerId(), Constant.ONLINE_TIMEOUT_SECOND, TimeUnit.SECONDS);
// 响应ws
SendInfo sendInfo = new SendInfo();
sendInfo.setCmd(WSCmdEnum.LOGIN.getCode());

4
im-ui/.env.production

@ -1,6 +1,6 @@
ENV = 'production'
# 接口地址
VUE_APP_BASE_API = 'http://8.134.92.70/api'
VUE_APP_BASE_API = 'https://8.134.92.70:443/api'
VUE_APP_WS_URL = 'ws://8.134.92.70:81/im'
VUE_APP_WS_URL = 'wss://8.134.92.70:81/im'

3
im-ui/package.json

@ -11,6 +11,7 @@
"axios": "^1.1.3",
"core-js": "^3.6.5",
"element-ui": "^2.15.10",
"js-audio-recorder": "^1.0.7",
"sass": "^1.47.0",
"sass-loader": "^7.3.1",
"vue": "^2.6.11",
@ -50,7 +51,7 @@
"vue/no-unused-components": "off",
"no-irregular-whitespace": "off",
"no-debugger": "off",
"no-useless-escape": "off"
"no-useless-escape": "off"
}
},
"browserslist": [

1
im-ui/src/api/emotion.js

@ -2,7 +2,6 @@ const emoTextList = ['微笑', '撇嘴', '色', '发呆', '得意', '流泪', '
let transform = (content) => {
console.log(content)
return content.replace(/\#[\u4E00-\u9FA5]{1,3}\;/gi, textToImg);
}

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

@ -0,0 +1,494 @@
<template>
<el-container class="chat-box">
<el-header height="60px">
<span>{{title}}</span>
<span title="群聊信息" v-show="this.chat.type=='GROUP'" class="btn-side el-icon-more"
@click="showSide=!showSide"></span>
</el-header>
<el-main style="padding: 0;">
<el-container>
<el-container class="content-box">
<el-main class="im-chat-main" id="chatScrollBox">
<div class="im-chat-box">
<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">
</message-item>
</li>
</ul>
</div>
</el-main>
<el-footer height="200px" class="im-chat-footer">
<div class="chat-tool-bar">
<div title="表情" class="el-icon-eleme" ref="emotion" @click="switchEmotionBox()">
</div>
<div title="发送图片">
<file-upload :action="imageAction" :maxSize="5*1024*1024"
:fileTypes="['image/jpeg', 'image/png', 'image/jpg', 'image/webp','image/gif']"
@before="handleImageBefore" @success="handleImageSuccess" @fail="handleImageFail">
<i class="el-icon-picture-outline"></i>
</file-upload>
</div>
<div title="发送文件">
<file-upload :action="fileAction" :maxSize="10*1024*1024" @before="handleFileBefore"
@success="handleFileSuccess" @fail="handleFileFail">
<i class="el-icon-wallet"></i>
</file-upload>
</div>
<div title="发送语音" class="el-icon-microphone" @click="showVoiceBox()">
</div>
<div title="聊天记录" class="el-icon-chat-dot-round"></div>
</div>
<textarea v-model="sendText" ref="sendBox" class="send-text-area"
@keydown.enter="sendTextMessage()"></textarea>
<div class="im-chat-send">
<el-button type="primary" @click="sendTextMessage()">发送</el-button>
</div>
</el-footer>
</el-container>
<el-aside class="chat-group-side-box" width="300px" v-show="showSide">
<chat-group-side :group="group" :groupMembers="groupMembers" @reload="loadGroup(group.id)">
</chat-group-side>
</el-aside>
</el-container>
</el-main>
<emotion v-show="showEmotion" :pos="emoBoxPos" @emotion="handleEmotion"></Emotion>
<chat-voice :visible="showVoice" @close="closeVoiceBox" @send="handleSendVoice"></chat-voice>
</el-container>
</template>
<script>
import ChatGroupSide from "./ChatGroupSide.vue";
import MessageItem from "./MessageItem.vue";
import FileUpload from "../common/FileUpload.vue";
import Emotion from "../common/Emotion.vue";
import ChatVoice from "./ChatVoice.vue";
export default {
name: "chatPrivate",
components: {
MessageItem,
FileUpload,
ChatGroupSide,
Emotion,
ChatVoice
},
props: {
chat: {
type: Object
}
},
data() {
return {
friend: {},
group: {},
groupMembers: [],
sendText: "",
showVoice: false, //
showSide: false, //
showEmotion: false, // emoji
emoBoxPos: { // emoji
x: 0,
y: 0
}
}
},
methods: {
handleImageSuccess(res, file) {
let msgInfo = {
recvId: file.raw.targetId,
content: JSON.stringify(res.data),
type: 1
}
// id
this.setTargetId(msgInfo, this.chat.targetId);
this.$http({
url: this.messageAction,
method: 'post',
data: msgInfo
}).then((data) => {
let info = {
type: this.chat.type,
targetId: file.raw.targetId,
fileId: file.raw.uid,
content: JSON.stringify(res.data),
loadStatus: "ok"
}
this.$store.commit("handleFileUpload", info);
})
},
handleImageFail(res, file) {
let info = {
type: this.chat.type,
targetId: file.raw.targetId,
fileId: file.raw.uid,
loadStatus: "fail"
}
this.$store.commit("handleFileUpload", info);
},
handleImageBefore(file) {
let url = URL.createObjectURL(file);
let data = {
originUrl: url,
thumbUrl: url
}
let msgInfo = {
fileId: file.uid,
sendId: this.mine.id,
content: JSON.stringify(data),
sendTime: new Date().getTime(),
selfSend: true,
type: 1,
loadStatus: "loading"
}
// id
this.setTargetId(msgInfo, this.chat.targetId);
//
this.$store.commit("insertMessage", msgInfo);
//
this.scrollToBottom();
// fileid
file.targetId = this.chat.targetId;
},
handleFileSuccess(res, file) {
let data = {
name: file.name,
size: file.size,
url: res.data
}
let msgInfo = {
content: JSON.stringify(data),
type: 2
}
// id
this.setTargetId(msgInfo, this.chat.targetId);
this.$http({
url: this.messageAction,
method: 'post',
data: msgInfo
}).then(() => {
let info = {
type: this.chat.type,
targetId: file.raw.targetId,
fileId: file.raw.uid,
content: JSON.stringify(data),
loadStatus: "ok"
}
this.$store.commit("handleFileUpload", info);
})
},
handleFileFail(res, file) {
let info = {
type: this.chat.type,
targetId: file.raw.targetId,
fileId: file.raw.uid,
loadStatus: "fail"
}
this.$store.commit("handleFileUpload", info);
},
handleFileBefore(file) {
let url = URL.createObjectURL(file);
let data = {
name: file.name,
size: file.size,
url: url
}
let msgInfo = {
fileId: file.uid,
sendId: this.mine.id,
content: JSON.stringify(data),
sendTime: new Date().getTime(),
selfSend: true,
type: 2,
loadStatus: "loading"
}
// id
this.setTargetId(msgInfo, this.chat.targetId);
//
this.$store.commit("insertMessage", msgInfo);
//
this.scrollToBottom();
// fileid
file.targetId = this.chat.targetId;
},
handleCloseSide() {
this.showSide = false;
},
switchEmotionBox() {
this.showEmotion = !this.showEmotion;
let width = this.$refs.emotion.offsetWidth;
let left = this.$elm.fixLeft(this.$refs.emotion);
let top = this.$elm.fixTop(this.$refs.emotion);
this.emoBoxPos.y = top;
this.emoBoxPos.x = left + width / 2;
},
handleEmotion(emoText) {
this.sendText += emoText;
this.showEmotion = false;
//
this.$refs.sendBox.focus();
},
showVoiceBox() {
this.showVoice = true;
},
closeVoiceBox() {
this.showVoice = false;
},
handleSendVoice(data) {
let msgInfo = {
content: JSON.stringify(data),
type: 3
}
// id
this.setTargetId(msgInfo, this.chat.targetId);
this.$http({
url: this.messageAction,
method: 'post',
data: msgInfo
}).then(() => {
this.$message.success("发送成功");
msgInfo.sendTime = new Date().getTime();
msgInfo.sendId = this.$store.state.userStore.userInfo.id;
msgInfo.selfSend = true;
this.$store.commit("insertMessage", msgInfo);
//
this.$refs.sendBox.focus();
//
this.scrollToBottom();
//
this.showVoice = false;
})
},
setTargetId(msgInfo, targetId) {
if (this.chat.type == "GROUP") {
msgInfo.groupId = targetId;
} else {
msgInfo.recvId = targetId;
}
},
sendTextMessage() {
if (!this.sendText.trim()) {
this.$message.error("不能发送空白信息");
return
}
let msgInfo = {
content: this.sendText,
type: 0
}
// id
this.setTargetId(msgInfo, this.chat.targetId);
this.$http({
url: this.messageAction,
method: 'post',
data: msgInfo
}).then((data) => {
this.$message.success("发送成功");
this.sendText = "";
msgInfo.sendTime = new Date().getTime();
msgInfo.sendId = this.$store.state.userStore.userInfo.id;
msgInfo.selfSend = true;
this.$store.commit("insertMessage", msgInfo);
//
this.$refs.sendBox.focus();
//
this.scrollToBottom();
})
const e = window.event || arguments[0];
if (e.key === 'Enter' || e.code === 'Enter' || e.keyCode === 13) {
e.returnValue = false;
e.preventDefault();
return false;
}
},
loadGroup(groupId) {
this.$http({
url: `/group/find/${groupId}`,
method: 'get'
}).then((group) => {
this.group = group;
this.$store.commit("updateChatFromGroup", group);
this.$store.commit("updateGroup", group);
});
this.$http({
url: `/group/members/${groupId}`,
method: 'get'
}).then((groupMembers) => {
this.groupMembers = groupMembers;
});
},
loadFriend(friendId) {
//
this.$http({
url: `/user/find/${friendId}`,
method: 'get'
}).then((friend) => {
this.friend = friend;
this.$store.commit("updateChatFromFriend", friend);
this.$store.commit("updateFriend", friend);
})
},
showName(msgInfo) {
if (this.chat.type == 'GROUP') {
let member = this.groupMembers.find((m) => m.userId == msgInfo.sendId);
return member ? member.aliasName : "";
} else {
return msgInfo.sendId == this.mine.id ? this.mine.nickName : this.chat.showName
}
},
headImage(msgInfo) {
if (this.chat.type == 'GROUP') {
let member = this.groupMembers.find((m) => m.userId == msgInfo.sendId);
return member ? member.headImage : "";
} else {
return msgInfo.sendId == this.mine.id ? this.mine.headImageThumb : this.chat.headImage
}
},
scrollToBottom() {
this.$nextTick(() => {
const div = document.getElementById("chatScrollBox");
div.scrollTop = div.scrollHeight;
});
}
},
computed: {
mine() {
return this.$store.state.userStore.userInfo;
},
title() {
let title = this.chat.showName;
if (this.chat.type == "GROUP") {
let size = this.groupMembers.filter(m => !m.quit).length;
title += `(${size})`;
}
return title;
},
imageAction() {
return `${process.env.VUE_APP_BASE_API}/image/upload`;
},
fileAction() {
return `${process.env.VUE_APP_BASE_API}/file/upload`;
},
messageAction() {
return `/message/${this.chat.type.toLowerCase()}/send`;
}
},
watch: {
chat: {
handler(newChat, oldChat) {
if (newChat.targetId > 0 && (newChat.type != oldChat.type || newChat.targetId != oldChat.targetId)) {
if (this.chat.type == "GROUP") {
this.loadGroup(this.chat.targetId);
} else {
this.loadFriend(this.chat.targetId);
}
this.scrollToBottom();
this.sendText = "";
//
this.$refs.sendBox.focus();
}
},
deep: true
}
}
}
</script>
<style lang="scss">
.chat-box {
background: white;
border: #dddddd solid 1px;
.el-header {
padding: 5px;
background-color: white;
line-height: 50px;
font-size: 20px;
font-weight: 600;
border: #dddddd solid 1px;
.btn-side {
position: absolute;
right: 20px;
line-height: 60px;
font-size: 22px;
cursor: pointer;
&:hover {
font-size: 30px;
}
}
}
.im-chat-main {
padding: 0;
border: #dddddd solid 1px;
.im-chat-box {
ul {
padding: 20px;
li {
list-style-type: none;
}
}
}
}
.im-chat-footer {
display: flex;
flex-direction: column;
padding: 0;
border: #dddddd solid 1px;
.chat-tool-bar {
display: flex;
position: relative;
width: 100%;
height: 40px;
text-align: left;
box-sizing: border-box;
border: #dddddd solid 1px;
>div {
margin-left: 10px;
font-size: 22px;
cursor: pointer;
color: #333333;
line-height: 40px;
&:hover {
color: black;
}
}
}
.send-text-area {
box-sizing: border-box;
padding: 5px;
width: 100%;
flex: 1;
resize: none;
background-color: #f8f8f8 !important;
outline-color: rgba(83, 160, 231, 0.61);
}
.im-chat-send {
text-align: right;
padding: 7px;
}
}
.chat-group-side-box {
border: #dddddd solid 1px;
animation: rtl-drawer-in .3s 1ms;
}
}
</style>

319
im-ui/src/components/chat/ChatGroup.vue

@ -1,319 +0,0 @@
<template>
<el-container class="r-chat-box">
<el-header height="60px">
<span>{{title}}</span>
<span title="群聊信息" class="btn-side el-icon-more" @click="showSide=!showSide"></span>
</el-header>
<el-container>
<el-container class="content-box">
<el-main class="im-chat-main" id="chatScrollBox">
<div class="im-chat-box">
<ul>
<li v-for="msgInfo in chat.messages" :key="msgInfo.id">
<message-item :mine="msgInfo.sendId == mine.id" :headImage="headImage(msgInfo)" :showName="showName(msgInfo)"
:msgInfo="msgInfo">
</message-item>
</li>
</ul>
</div>
</el-main>
<el-footer height="25%" class="im-chat-footer">
<div class="chat-tool-bar">
<div title="表情" class="el-icon-eleme" ref="emotion" @click="switchEmotionBox()">
<emotion v-show="showEmotion" :pos="emoBoxPos" @emotion="handleEmotion"></Emotion>
</div>
<div title="发送图片">
<file-upload :action="imageAction" :maxSize="5*1024*1024" :fileTypes="['image/jpeg', 'image/png', 'image/jpg', 'image/webp','image/gif']"
@before="handleImageBefore" @success="handleImageSuccess" @fail="handleImageFail">
<i class="el-icon-picture-outline"></i>
</file-upload>
</div>
<div title="发送文件">
<file-upload :action="fileAction" :maxSize="10*1024*1024" @before="handleFileBefore" @success="handleFileSuccess"
@fail="handleFileFail">
<i class="el-icon-wallet"></i>
</file-upload>
</div>
<div title="聊天记录" class="el-icon-chat-dot-round"></div>
</div>
<textarea v-model="sendText" ref="sendBox" class="send-text-area" @keydown.enter="sendTextMessage()"></textarea>
<div class="im-chat-send">
<el-button type="primary" @click="sendTextMessage()">发送</el-button>
</div>
</el-footer>
</el-container>
<el-aside class="chat-group-side-box" width="20%" v-show="showSide">
<chat-group-side :group="group" :groupMembers="groupMembers" @reload="loadGroup(group.id)"></chat-group-side>
</el-aside>
</el-container>
</el-container>
</template>
<script>
import ChatGroupSide from "./ChatGroupSide.vue";
import MessageItem from "./MessageItem.vue";
import FileUpload from "../common/FileUpload.vue";
import Emotion from "../common/Emotion.vue";
export default {
name: "chatPrivate",
components: {
MessageItem,
FileUpload,
ChatGroupSide,
Emotion
},
props: {
chat: {
type: Object
}
},
data() {
return {
sendText: "",
showSide: false,
group: {},
groupMembers: [],
showEmotion: false,
emoBoxPos: {
x: 0,
y: 0
}
}
},
methods: {
handleImageSuccess(res, file) {
let msgInfo = {
groupId: file.raw.targetId,
content: JSON.stringify(res.data),
type: 1
}
this.$http({
url: '/message/group/send',
method: 'post',
data: msgInfo
}).then((data) => {
let info = {
type: 'GROUP',
targetId: file.raw.targetId,
fileId: file.raw.uid,
content: JSON.stringify(res.data),
loadStatus: "ok"
}
this.$store.commit("handleFileUpload", info);
})
},
handleImageFail(res, file) {
let info = {
type: 'GROUP',
targetId: file.raw.targetId,
fileId: file.raw.uid,
loadStatus: "fail"
}
this.$store.commit("handleFileUpload", info);
},
handleImageBefore(file) {
let url = URL.createObjectURL(file);
let data = {
originUrl: url,
thumbUrl: url
}
let msgInfo = {
fileId: file.uid,
sendId: this.mine.id,
groupId: this.chat.targetId,
content: JSON.stringify(data),
sendTime: new Date().getTime(),
selfSend: true,
type: 1,
loadStatus: "loading"
}
//
this.$store.commit("insertMessage", msgInfo);
//
this.scrollToBottom();
// fileid
file.targetId = this.chat.targetId;
},
handleFileSuccess(res, file) {
let data = {
name: file.name,
size: file.size,
url: res.data
}
let msgInfo = {
groupId: file.raw.targetId,
content: JSON.stringify(data),
type: 2
}
this.$http({
url: '/message/group/send',
method: 'post',
data: msgInfo
}).then(() => {
let info = {
type: 'GROUP',
targetId: file.raw.targetId,
fileId: file.raw.uid,
content: JSON.stringify(data),
loadStatus: "ok"
}
this.$store.commit("handleFileUpload", info);
})
},
handleFileFail(res, file) {
let info = {
type: 'GROUP',
targetId: file.raw.targetId,
fileId: file.raw.uid,
loadStatus: "fail"
}
this.$store.commit("handleFileUpload", info);
},
handleFileBefore(file) {
let url = URL.createObjectURL(file);
let data = {
name: file.name,
size: file.size,
url: url
}
let msgInfo = {
fileId: file.uid,
sendId: this.mine.id,
groupId: this.chat.targetId,
content: JSON.stringify(data),
sendTime: new Date().getTime(),
selfSend: true,
type: 2,
loadStatus: "loading"
}
//
this.$store.commit("insertMessage", msgInfo);
//
this.scrollToBottom();
// fileid
file.targetId = this.chat.targetId;
},
handleCloseSide() {
this.showSide = false;
},
switchEmotionBox() {
this.showEmotion = !this.showEmotion;
let width = this.$refs.emotion.offsetWidth;
let left = this.$elm.fixLeft(this.$refs.emotion);
let top = this.$elm.fixTop(this.$refs.emotion);
this.emoBoxPos.y = top;
this.emoBoxPos.x = left + width / 2;
},
handleEmotion(emoText) {
this.sendText += emoText;
},
sendTextMessage() {
if (!this.sendText.trim()) {
this.$message.error("不能发送空白信息");
return
}
let msgInfo = {
groupId: this.chat.targetId,
content: this.sendText,
type: 0
}
this.$http({
url: '/message/group/send',
method: 'post',
data: msgInfo
}).then((data) => {
this.$message.success("发送成功");
this.sendText = "";
msgInfo.sendTime = new Date().getTime();
msgInfo.sendId = this.$store.state.userStore.userInfo.id;
msgInfo.selfSend = true;
this.$store.commit("insertMessage", msgInfo);
//
this.$refs.sendBox.focus();
//
this.scrollToBottom();
})
const e = window.event || arguments[0]
if (e.key === 'Enter' || e.code === 'Enter' || e.keyCode === 13) {
e.returnValue = false;
e.preventDefault();
return false;
}
},
loadGroup(groupId) {
this.$http({
url: `/group/find/${groupId}`,
method: 'get'
}).then((group) => {
this.group = group;
this.$store.commit("updateChatFromGroup", group);
});
this.$http({
url: `/group/members/${groupId}`,
method: 'get'
}).then((groupMembers) => {
this.groupMembers = groupMembers;
});
},
showName(msgInfo) {
let member = this.groupMembers.find((m) => m.userId == msgInfo.sendId);
return member ? member.aliasName : "";
},
headImage(msgInfo) {
let member = this.groupMembers.find((m) => m.userId == msgInfo.sendId);
return member ? member.headImage : "";
},
scrollToBottom() {
this.$nextTick(() => {
const div = document.getElementById("chatScrollBox");
div.scrollTop = div.scrollHeight;
});
}
},
computed: {
mine() {
return this.$store.state.userStore.userInfo;
},
title() {
let size = this.groupMembers.filter(m => !m.quit).length;
return `${this.chat.showName}(${size})`;
},
imageAction() {
return `${process.env.VUE_APP_BASE_API}/image/upload`;
},
fileAction() {
return `${process.env.VUE_APP_BASE_API}/file/upload`;
}
},
mounted() {
console.log("group mount...")
this.loadGroup(this.chat.targetId);
this.scrollToBottom();
}
}
</script>
<style>
.btn-side {
position: absolute;
right: 20px;
line-height: 60px;
font-size: 22px;
cursor: pointer;
&:hover {
font-size: 30px;
}
}
.chat-group-side-box {
border: #dddddd solid 1px;
animation: rtl-drawer-in .3s 1ms;
}
</style>

251
im-ui/src/components/chat/ChatPrivate.vue

@ -1,251 +0,0 @@
<template>
<el-container class="r-chat-box">
<el-header height="60px">
{{chat.showName}}
</el-header>
<el-main class="im-chat-main" id="chatScrollBox">
<div class="im-chat-box">
<ul>
<li v-for="msgInfo in chat.messages" :key="msgInfo.id">
<message-item :mine="msgInfo.sendId == mine.id" :headImage="headImage(msgInfo)" :showName="showName(msgInfo)"
:msgInfo="msgInfo">
</message-item>
</li>
</ul>
</div>
</el-main>
<el-footer height="25%" class="im-chat-footer">
<div class="chat-tool-bar">
<div class="el-icon-eleme" ref="emotion" @click="switchEmotionBox()">
<emotion v-show="showEmotion" :pos="emoBoxPos" @emotion="handleEmotion"></Emotion>
</div>
<div>
<file-upload :action="imageAction" :maxSize="5*1024*1024" :fileTypes="['image/jpeg', 'image/png', 'image/jpg','image/webp', 'image/gif']"
@before="handleImageBefore" @success="handleImageSuccess" @fail="handleImageFail">
<i class="el-icon-picture-outline"></i>
</file-upload>
</div>
<div>
<file-upload :action="fileAction" :maxSize="10*1024*1024" @before="handleFileBefore" @success="handleFileSuccess"
@fail="handleFileFail">
<i class="el-icon-wallet"></i>
</file-upload>
</div>
<div class="el-icon-chat-dot-round"></div>
</div>
<textarea v-model="sendText" ref="sendBox" class="send-text-area" @keyup.enter="sendTextMessage()"></textarea>
<div class="im-chat-send">
<el-button type="primary" @click="sendTextMessage()">发送</el-button>
</div>
</el-footer>
</el-container>
</template>
<script>
import MessageItem from "./MessageItem.vue";
import FileUpload from "../common/FileUpload.vue";
import Emotion from "../common/Emotion.vue";
export default {
name: "chatPrivate",
components: {
MessageItem,
FileUpload,
Emotion
},
props: {
chat: {
type: Object
}
},
data() {
return {
sendText: "",
showEmotion: false,
emoBoxPos: {
x: 0,
y: 0
}
}
},
methods: {
handleImageSuccess(res, file) {
let msgInfo = {
recvId: file.raw.targetId,
content: JSON.stringify(res.data),
type: 1
}
this.$http({
url: '/message/private/send',
method: 'post',
data: msgInfo
}).then((data) => {
let info = {
type: 'PRIVATE',
targetId: file.raw.targetId,
fileId: file.raw.uid,
content: JSON.stringify(res.data),
loadStatus: "ok"
}
this.$store.commit("handleFileUpload", info);
})
},
handleImageFail(res, file) {
let info = {
type: 'PRIVATE',
targetId: file.raw.targetId,
fileId: file.raw.uid,
loadStatus: "fail"
}
this.$store.commit("handleFileUpload", info);
},
handleImageBefore(file) {
let url = URL.createObjectURL(file);
let data = {
originUrl: url,
thumbUrl: url
}
let msgInfo = {
fileId: file.uid,
sendId: this.mine.id,
recvId: this.chat.targetId,
content: JSON.stringify(data),
sendTime: new Date().getTime(),
selfSend: true,
type: 1,
loadStatus: "loading"
}
//
this.$store.commit("insertMessage", msgInfo);
//
this.scrollToBottom();
// fileid
file.targetId = this.chat.targetId;
},
handleFileSuccess(res, file) {
console.log(res.data);
let data = {
name: file.name,
size: file.size,
url: res.data
}
let msgInfo = {
recvId: file.raw.targetId,
content: JSON.stringify(data),
type: 2
}
this.$http({
url: '/message/private/send',
method: 'post',
data: msgInfo
}).then(() => {
let info = {
type: 'PRIVATE',
targetId: file.raw.targetId,
fileId: file.raw.uid,
content: JSON.stringify(data),
loadStatus: "ok"
}
this.$store.commit("handleFileUpload", info);
})
},
handleFileFail(res, file) {
let info = {
type: 'PRIVATE',
targetId: file.raw.targetId,
fileId: file.raw.uid,
loadStatus: "fail"
}
this.$store.commit("handleFileUpload", info);
},
handleFileBefore(file) {
let url = URL.createObjectURL(file);
let data = {
name: file.name,
size: file.size,
url: url
}
let msgInfo = {
fileId: file.uid,
sendId: this.mine.id,
recvId: this.chat.targetId,
content: JSON.stringify(data),
sendTime: new Date().getTime(),
selfSend: true,
type: 2,
loadStatus: "loading"
}
//
this.$store.commit("insertMessage", msgInfo);
//
this.scrollToBottom();
// fileid
file.targetId = this.chat.targetId;
},
switchEmotionBox() {
this.showEmotion = !this.showEmotion;
let width = this.$refs.emotion.offsetWidth;
let left = this.$elm.fixLeft(this.$refs.emotion);
let top = this.$elm.fixTop(this.$refs.emotion);
this.emoBoxPos.y = top;
this.emoBoxPos.x = left + width/2;
},
handleEmotion(emoText) {
this.sendText += emoText;
},
sendTextMessage() {
let msgInfo = {
recvId: this.chat.targetId,
content: this.sendText,
type: 0
}
this.$http({
url: '/message/private/send',
method: 'post',
data: msgInfo
}).then((data) => {
this.$message.success("发送成功");
this.sendText = "";
msgInfo.sendTime = new Date().getTime();
msgInfo.sendId = this.$store.state.userStore.userInfo.id;
msgInfo.selfSend = true;
this.$store.commit("insertMessage", msgInfo);
//
this.$refs.sendBox.focus();
//
this.scrollToBottom();
})
},
showName(msg) {
return msg.sendId == this.mine.id ? this.mine.nickName : this.chat.showName
},
headImage(msg) {
return msg.sendId == this.mine.id ? this.mine.headImageThumb : this.chat.headImage
},
scrollToBottom() {
this.$nextTick(() => {
const div = document.getElementById("chatScrollBox");
div.scrollTop = div.scrollHeight;
});
}
},
computed: {
mine() {
return this.$store.state.userStore.userInfo;
},
imageAction() {
return `${process.env.VUE_APP_BASE_API}/image/upload`;
},
fileAction() {
return `${process.env.VUE_APP_BASE_API}/file/upload`;
}
},
mounted() {
console.log("private mount...")
this.scrollToBottom();
}
}
</script>
<style>
</style>

141
im-ui/src/components/chat/ChatVoice.vue

@ -0,0 +1,141 @@
<template>
<el-dialog class="chat-voice" title="语音录制" :visible.sync="visible" width="600px" :before-close="handleClose">
<div v-show="mode=='RECORD'">
<div class="chat-voice-tip">{{stateTip}}</div>
<div>时长: {{state=='STOP'?0:parseInt(rc.duration)}}s</div>
</div>
<audio v-show="mode=='PLAY'" :src="url" controls ref="audio" @ended="handleStopAudio()"></audio>
<el-divider content-position="center"></el-divider>
<el-row class="chat-voice-btn-group">
<el-button round type="primary" v-show="state=='STOP'" @click="handleStartRecord()">开始录音</el-button>
<el-button round type="warning" v-show="state=='RUNNING'" @click="handlePauseRecord()">暂停录音</el-button>
<el-button round type="primary" v-show="state=='PAUSE'" @click="handleResumeRecord()">继续录音</el-button>
<el-button round type="danger" v-show="state=='RUNNING'||state=='PAUSE'" @click="handleCompleteRecord()">
结束录音</el-button>
<el-button round type="success" v-show="state=='COMPLETE' && mode!='PLAY'" @click="handlePlayAudio()">播放录音
</el-button>
<el-button round type="warning" v-show="state=='COMPLETE' && mode=='PLAY'" @click="handleStopAudio()">停止播放
</el-button>
<el-button round type="primary" v-show="state=='COMPLETE'" @click="handleRestartRecord()">重新录音</el-button>
<el-button round type="primary" v-show="state=='COMPLETE'" @click="handleSendRecord()">立即发送</el-button>
</el-row>
</el-dialog>
</template>
<script>
import Recorder from 'js-audio-recorder';
export default {
name: 'chatVoice',
props: {
visible: {
type: Boolean
}
},
data() {
return {
rc: new Recorder(),
audio: new Audio(),
state: 'STOP', // STOPRUNNINGPAUSECOMPLETE
stateTip: "未开始",
mode: 'RECORD', // RECORD PLAY
duration: 0,
url: ""
}
},
methods: {
handleClose() {
//
this.rc.destroy();
this.rc = new Recorder();
this.audio.pause();
this.mode = 'RECORD';
this.state = 'STOP';
this.stateTip = '未开始';
this.$emit("close");
},
handleStartRecord() {
this.rc.start().then((stream) => {
this.state = 'RUNNING';
this.stateTip = "正在录音...";
}).catch(error => {
this.$message.error("录音失败");
console.log(error);
});
},
handlePauseRecord() {
this.rc.pause();
this.state = 'PAUSE';
this.stateTip = "已暂停录音";
},
handleResumeRecord() {
this.rc.resume();
this.state = 'RUNNING';
this.stateTip = "正在录音...";
},
handleCompleteRecord() {
this.rc.pause();
this.state = 'COMPLETE';
this.stateTip = "已结束录音";
},
handlePlayAudio() {
let wav = this.rc.getWAVBlob();
let url = URL.createObjectURL(wav);
this.$refs.audio.src = url;
this.$refs.audio.play();
this.mode = 'PLAY';
},
handleStopAudio() {
console.log(this.$refs.audio);
this.$refs.audio.pause();
this.mode = 'RECORD';
},
handleRestartRecord() {
this.rc.destroy();
this.rc.start();
this.state = 'RUNNING';
this.mode = 'RECORD';
this.stateTip = "正在录音...";
},
handleSendRecord() {
let wav = this.rc.getWAVBlob();
let name = new Date().getDate() + '.wav';
var formData = new window.FormData()
formData.append('file', wav, name);
this.$http({
url: '/file/upload',
data: formData,
method: 'post',
headers: {
'Content-Type': 'multipart/form-data'
}
}).then((url) => {
let data = {
duration: parseInt(this.rc.duration),
url: url
}
this.$emit("send", data);
this.handleClose();
})
}
}
}
</script>
<style lang="scss">
.chat-voice {
.chat-voice-tip {
font-size: 18px;
}
.chat-voice-btn-group {
margin-bottom: 20px;
}
}
</style>

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

@ -14,7 +14,9 @@
<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"/>
<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>
@ -30,6 +32,9 @@
</div>
<span title="发送失败" v-show="loadFail" @click="handleSendFail" class="send-fail el-icon-warning"></span>
</div>
<div class="im-msg-voice" v-if="msgInfo.type==3" @click="handlePlayVoice()">
<audio controls :src="JSON.parse(msgInfo.content).url"></audio>
</div>
</div>
</div>
</div>
@ -63,9 +68,29 @@
required: true
}
},
data(){
return {
audioPlayState: 'STOP',
}
},
methods:{
handleSendFail(){
this.$message.error("该文件已发送失败,目前不支持自动重新发送,建议手动重新发送")
},
showFullImageBox(){
let imageUrl = JSON.parse(this.msgInfo.content).originUrl;
if(imageUrl){
this.$store.commit('showFullImageBox',imageUrl);
}
},
handlePlayVoice(){
if(!this.audio){
this.audio = new Audio();
}
this.audio.src = JSON.parse(this.msgInfo.content).url;
this.audio.play();
this.handlePlayVoice = 'RUNNING';
}
},
computed:{
@ -160,6 +185,7 @@
flex-wrap: nowrap;
flex-direction: row;
align-items: center;
.send-image{
min-width: 300px;
min-height: 200px;
@ -220,6 +246,11 @@
}
}
.im-msg-voice {
font-size: 14px;
cursor: pointer;
}
}
}

24
im-ui/src/components/common/Emotion.vue

@ -1,11 +1,13 @@
<template>
<div class="emotion-box" :style="{'left':x+'px','top':y+'px'}">
<el-scrollbar style="height:200px">
<div class="emotion-item-list">
<div class="emotion-item" v-for="(emoText, i) in $emo.emoTextList" :key="i" @click="clickHandler(emoText)" v-html="$emo.textToImg(emoText)">
<div class="emotion-mask" @click="$emit('emotion','')">
<div class="emotion-box" :style="{'left':x+'px','top':y+'px'}">
<el-scrollbar style="height:200px">
<div class="emotion-item-list">
<div class="emotion-item" v-for="(emoText, i) in $emo.emoTextList" :key="i" @click="clickHandler(emoText)" v-html="$emo.textToImg(emoText)">
</div>
</div>
</div>
</el-scrollbar>
</el-scrollbar>
</div>
</div>
</template>
@ -37,6 +39,16 @@
}
</script>
<style scoped lang="scss">
.emotion-mask {
position: fixed;
left: 0;
top: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
}
.emotion-box {
position: fixed;
width: 400px;

2
im-ui/src/components/common/FullImage.vue

@ -1,5 +1,5 @@
<template>
<el-dialog width="30%" :visible.sync="visible" :before-close="handleClose">
<el-dialog width="40%" :visible.sync="visible" :before-close="handleClose">
<img class="full-img" :src="url" />
</el-dialog>
</template>

4
im-ui/src/components/group/AddGroupMember.vue

@ -5,7 +5,7 @@
<el-input width="200px" placeholder="搜索好友" class="input-with-select" v-model="searchText" @keyup.enter.native="handleSearch()">
<el-button slot="append" icon="el-icon-search" @click="handleSearch()"></el-button>
</el-input>
<el-scrollbar style="height:500px;">
<el-scrollbar style="height:400px;">
<div v-for="(friend,index) in friends" :key="friend.id">
<friend-item v-show="friend.nickName.startsWith(searchText)" :showDelete="false" @click.native="handleSwitchCheck(friend)"
:friend="friend" :index="index" :active="index === activeIndex">
@ -17,7 +17,7 @@
</div>
<div class="agm-r-box">
<div class="agm-select-tip"> 已勾选{{checkCount}}位好友</div>
<el-scrollbar style="height:500px;">
<el-scrollbar style="height:400px;">
<div v-for="(friend,index) in friends" :key="friend.id">
<friend-item v-if="friend.isCheck && !friend.disabled" :friend="friend" :index="index" :active="false" @del="handleRemoveFriend(friend,index)">
</friend-item>

2
im-ui/src/components/setting/Setting.vue

@ -1,5 +1,5 @@
<template>
<el-dialog class="setting" title="设置" :visible.sync="visible" width="30%" :before-close="handleClose">
<el-dialog class="setting" title="设置" :visible.sync="visible" width="500px" :before-close="handleClose">
<el-form :model="userInfo" label-width="80px" :rules="rules" ref="settingForm">
<el-form-item label="头像">
<file-upload class="avatar-uploader"

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

@ -84,7 +84,16 @@ export default {
break;
}
}
chat.lastContent = msgInfo.type == 1 ? "[图片]" : msgInfo.type == 2 ? "[文件]" : msgInfo.content;
console.log(msgInfo.type)
if(msgInfo.type == 1){
chat.lastContent = "[图片]";
}else if(msgInfo.type == 2){
chat.lastContent = "[文件]";
}else if(msgInfo.type == 3){
chat.lastContent = "[语音]";
}else{
chat.lastContent = msgInfo.content;
}
chat.lastSendTime = msgInfo.sendTime;
chat.messages.push(msgInfo);
// 如果不是当前会话,未读加1
@ -102,12 +111,12 @@ export default {
msg.content = info.content;
}
},
updateChatFromUser(state, user) {
updateChatFromFriend(state, friend) {
for (let i in state.chats) {
let chat = state.chats[i];
if (chat.type=='PRIVATE' && chat.targetId == user.id) {
chat.headImage = user.headImageThumb;
chat.showName = user.nickName;
if (chat.type=='PRIVATE' && chat.targetId == friend.id) {
chat.headImage = friend.headImageThumb;
chat.showName = friend.nickName;
break;
}
}

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

@ -7,38 +7,27 @@
</el-input>
</div>
<el-scrollbar class="l-chat-list" >
<div v-for="(chat,index) in chatStore.chats" :key="chat.type+chat.targetId">
<div v-for="(chat,index) in chatStore.chats" :key="index">
<chat-item :chat="chat" :index="index" @click.native="handleActiveItem(index)" @del="handleDelItem(chat,index)"
:active="index === chatStore.activeIndex"></chat-item>
</div>
</el-scrollbar>
</el-aside>
<el-container class="r-chat-box">
<chat-private :chat="activeChat" v-if="activeChat.type=='PRIVATE'"></chat-private>
<chat-Group :chat="activeChat" v-if="activeChat.type=='GROUP'"></chat-Group>
<chat-box v-show="activeChat.targetId>0" :chat="activeChat"></chat-box>
</el-container>
</el-container>
</template>
<script>
import ChatItem from "../components/chat/ChatItem.vue";
import ChatTime from "../components/chat/ChatTime.vue";
import MessageItem from "../components/chat/MessageItem.vue";
import HeadImage from "../components/common/HeadImage.vue";
import FileUpload from "../components/common/FileUpload.vue";
import ChatPrivate from "../components/chat/ChatPrivate.vue";
import ChatGroup from "../components/chat/ChatGroup.vue";
import ChatBox from "../components/chat/ChatBox.vue";
export default {
name: "chat",
components: {
ChatItem,
ChatTime,
HeadImage,
FileUpload,
MessageItem,
ChatPrivate,
ChatGroup
ChatBox
},
data() {
return {
@ -51,67 +40,10 @@
methods: {
handleActiveItem(index) {
this.$store.commit("activeChat", index);
let chat = this.chatStore.chats[index];
if (chat.type == "PRIVATE") {
this.refreshNameAndHeadImage(chat);
}
},
handleDelItem(chat, index) {
this.$store.commit("removeChat", index);
},
sendGroupMessage() {
let msgInfo = {
groupId: this.activeChat.targetId,
content: this.messageContent,
type: 0
}
this.$http({
url: '/message/group/send',
method: 'post',
data: msgInfo
}).then((data) => {
this.$message.success("发送成功");
this.messageContent = "";
msgInfo.sendTime = new Date().getTime();
msgInfo.sendId = this.$store.state.userStore.userInfo.id;
msgInfo.selfSend = true;
this.$store.commit("insertMessage", msgInfo);
//
this.$refs.sendBox.focus();
//
this.scrollToBottom();
})
},
refreshNameAndHeadImage(chat){
//
let userId = chat.targetId;
this.$http({
url: `/user/find/${userId}`,
method: 'get'
}).then((user) => {
//
if (user.headImageThumb != chat.headImage ||
user.nickName != chat.showName) {
this.updateFriendInfo(user)
this.$store.commit("updateChatFromUser", user);
}
})
},
updateFriendInfo(user) {
let friendInfo = {
id: user.id,
nickName: user.nickName,
headImage: user.headImageThumb
};
this.$http({
url: "/friend/update",
method: "put",
data: friendInfo
}).then(() => {
this.$store.commit("updateFriend", friendInfo);
})
}
},
computed: {
chatStore() {
@ -155,78 +87,5 @@
flex: 1;
}
}
.r-chat-box {
background: white;
border: #dddddd solid 1px;
.el-header {
padding: 5px;
background-color: white;
line-height: 50px;
font-size: 20px;
font-weight: 600;
border: #dddddd solid 1px;
}
.im-chat-main {
padding: 0;
border: #dddddd solid 1px;
.im-chat-box {
ul {
padding: 20px;
li {
list-style-type: none;
}
}
}
}
.im-chat-footer {
display: flex;
flex-direction: column;
padding: 0;
border: #dddddd solid 1px;
.chat-tool-bar {
display: flex;
position: relative;
width: 100%;
height: 40px;
text-align: left;
box-sizing: border-box;
border: #dddddd solid 1px;
>div {
margin-left: 10px;
font-size: 22px;
cursor: pointer;
color: #333333;
line-height: 40px;
&:hover {
color: black;
}
}
}
.send-text-area {
box-sizing: border-box;
padding: 5px;
width: 100%;
flex: 1;
resize: none;
background-color: #f8f8f8 !important;
outline-color: rgba(83, 160, 231, 0.61);
}
.im-chat-send {
text-align: right;
padding: 7px;
}
}
}
}
</style>

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

@ -13,7 +13,7 @@
</add-friend>
</div>
<el-scrollbar class="l-friend-list" >
<div v-for="(friend,index) in $store.state.friendStore.friends" :key="friend.id">
<div v-for="(friend,index) in $store.state.friendStore.friends" :key="index">
<friend-item v-show="friend.nickName.startsWith(searchText)" :friend="friend" :index="index" :active="index === $store.state.friendStore.activeIndex"
@del="handleDelItem(friend,index)" @click.native="handleActiveItem(friend,index)">
</friend-item>
@ -122,7 +122,7 @@
data: friend
}).then(() => {
this.$store.commit("updateFriend", friend);
this.$store.commit("updateChatFromUser", user);
this.$store.commit("updateChatFromFriend", user);
})
},
loadUserInfo(friend,index){

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

@ -11,7 +11,7 @@
@click="handleCreateGroup()"></el-button>
</div>
<el-scrollbar class="l-group-list">
<div v-for="(group,index) in groupStore.groups" :key="group.id">
<div v-for="(group,index) in groupStore.groups" :key="index">
<group-item v-show="group.remark.startsWith(searchText)" :group="group" :active="index === groupStore.activeIndex"
@click.native="handleActiveItem(group,index)">
</group-item>
@ -22,7 +22,7 @@
<div class="r-group-header" v-show="activeGroup.id">
{{activeGroup.remark}}({{groupMembers.length}})
</div>
<div class="r-group-container">
<el-scrollbar class="r-group-container">
<div v-show="activeGroup.id">
<div class="r-group-info">
<div>
@ -74,7 +74,7 @@
</div>
</el-scrollbar>
</div>
</div>
</el-scrollbar>
</el-container>
</el-container>
</template>
@ -306,7 +306,7 @@
.r-group-info {
display: flex;
padding: 20px;
padding: 5px 20px;
.r-group-form {
flex: 1;
@ -351,7 +351,7 @@
}
.r-group-member-list {
padding: 20px;
padding: 5px 20px;
display: flex;
align-items: center;
flex-wrap: wrap;

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

@ -23,7 +23,7 @@
</router-link>
</el-menu-item>
<el-menu-item title="设置" @click="onClickSetting()">
<el-menu-item title="设置" @click="showSetting()">
<span class="el-icon-setting"></span>
</el-menu-item>
</el-menu>
@ -34,7 +34,7 @@
<el-main class="content-box">
<router-view></router-view>
</el-main>
<setting :visible="showSettingDialog" @close="onCloseSetting()"></setting>
<setting :visible="showSettingDialog" @close="closeSetting()"></setting>
<user-info v-show="uiStore.userInfo.show"
:pos="uiStore.userInfo.pos"
:user="uiStore.userInfo.user"
@ -166,10 +166,10 @@
location.href = "/";
})
},
onClickSetting() {
showSetting() {
this.showSettingDialog = true;
},
onCloseSetting() {
closeSetting() {
this.showSettingDialog = false;
}
},

34
im-ui/src/view/Login.vue

@ -68,6 +68,8 @@
params: this.loginForm
})
.then((data) => {
this.setCookie('username',this.loginForm.username);
this.setCookie('password',this.loginForm.password);
this.$message.success("登陆成功");
this.$router.push("/home/chat");
})
@ -77,9 +79,37 @@
},
resetForm(formName) {
this.$refs[formName].resetFields();
}
},
// cookie
getCookie(name) {
let reg = new RegExp("(^| )" + name + "=([^;]*)(;|$)");
let arr = document.cookie.match(reg)
if (arr){
return unescape(arr[2]);
}
return '';
},
// cookie,vue便
setCookie (name, value, expiredays) {
var exdate = new Date();
exdate.setDate(exdate.getDate() + expiredays);
document.cookie = name + "=" + escape(value) + ((expiredays == null) ? "" : ";expires=" + exdate.toGMTString());
},
// cookie
delCookie (name) {
var exp = new Date();
exp.setTime(exp.getTime() - 1);
var cval = this.getCookie(name);
if (cval != null){
document.cookie = name + "=" + cval + ";expires=" + exp.toGMTString();
}
}
},
mounted() {
this.loginForm.username = this.getCookie("username");
// cookie便
this.loginForm.password = this.getCookie("password");
}
}
</script>

3
im-ui/src/view/Register.vue

@ -3,7 +3,7 @@
<div>
<el-form :model="registerForm" status-icon :rules="rules" ref="registerForm" label-width="80px" class="web-ruleForm">
<div class="register-brand">欢迎注册成为FLY CHAT用户</div>
<div class="register-brand">欢迎注册</div>
<el-form-item label="用户名" prop="userName">
<el-input type="userName" v-model="registerForm.userName" autocomplete="off"></el-input>
</el-form-item>
@ -142,6 +142,7 @@
font-size: 22px;
font-weight: 600;
letter-spacing: 2px;
text-align: center;
text-transform: uppercase;
}

Loading…
Cancel
Save