Browse Source

前端代码格式化(使用vscode默认格式)

master
xsx 1 year ago
parent
commit
d0e684907f
  1. 1
      .gitignore
  2. 6
      README.md
  3. 2
      im-web/src/App.vue
  4. 10
      im-web/src/api/camera.js
  5. 6
      im-web/src/api/date.js
  6. 6
      im-web/src/api/element.js
  7. 4
      im-web/src/api/emotion.js
  8. 2
      im-web/src/api/httpRequest.js
  9. 24
      im-web/src/api/messageType.js
  10. 36
      im-web/src/api/rtcGroupApi.js
  11. 16
      im-web/src/api/rtcPrivateApi.js
  12. 26
      im-web/src/api/webrtc.js
  13. 36
      im-web/src/api/wssocket.js
  14. 20
      im-web/src/components/chat/ChatAtBox.vue
  15. 68
      im-web/src/components/chat/ChatBox.vue
  16. 10
      im-web/src/components/chat/ChatGroupMember.vue
  17. 19
      im-web/src/components/chat/ChatGroupSide.vue
  18. 37
      im-web/src/components/chat/ChatHistory.vue
  19. 30
      im-web/src/components/chat/ChatInput.vue
  20. 32
      im-web/src/components/chat/ChatItem.vue
  21. 22
      im-web/src/components/chat/ChatMessageItem.vue
  22. 35
      im-web/src/components/chat/ChatRecord.vue
  23. 10
      im-web/src/components/common/Emotion.vue
  24. 11
      im-web/src/components/common/FileUpload.vue
  25. 8
      im-web/src/components/common/FullImage.vue
  26. 15
      im-web/src/components/common/HeadImage.vue
  27. 4
      im-web/src/components/common/Icp.vue
  28. 18
      im-web/src/components/common/RightMenu.vue
  29. 22
      im-web/src/components/common/UserInfo.vue
  30. 53
      im-web/src/components/friend/AddFriend.vue
  31. 22
      im-web/src/components/friend/FriendItem.vue
  32. 12
      im-web/src/components/group/AddGroupMember.vue
  33. 15
      im-web/src/components/group/GroupItem.vue
  34. 40
      im-web/src/components/group/GroupMember.vue
  35. 16
      im-web/src/components/group/GroupMemberItem.vue
  36. 18
      im-web/src/components/group/GroupMemberSelector.vue
  37. 30
      im-web/src/components/rtc/RtcGroupJoin.vue
  38. 10
      im-web/src/components/rtc/RtcGroupVideo.vue
  39. 19
      im-web/src/components/rtc/RtcPrivateAcceptor.vue
  40. 16
      im-web/src/components/rtc/RtcPrivateVideo.vue
  41. 8
      im-web/src/components/setting/Setting.vue
  42. 2
      im-web/src/main.js
  43. 2
      im-web/src/router/index.js
  44. 4
      im-web/src/store/chatStore.js
  45. 12
      im-web/src/store/configStore.js
  46. 46
      im-web/src/store/friendStore.js
  47. 4
      im-web/src/store/index.js
  48. 20
      im-web/src/store/uiStore.js
  49. 16
      im-web/src/store/userStore.js
  50. 136
      im-web/src/utils/directive/dialogDrag.js
  51. 4
      im-web/src/view/Chat.vue
  52. 24
      im-web/src/view/Friend.vue
  53. 26
      im-web/src/view/Group.vue
  54. 10
      im-web/src/view/Home.vue
  55. 13
      im-web/src/view/Login.vue
  56. 31
      im-web/src/view/Register.vue
  57. BIN
      截图/交流群2.png

1
.gitignore

@ -12,4 +12,3 @@
/im-web/dist/ /im-web/dist/
/im-uniapp/node_modules/ /im-uniapp/node_modules/
/im-uniapp/package-lock.json /im-uniapp/package-lock.json
/im-uniapp/unpackage/

6
README.md

@ -122,9 +122,9 @@ https://www.yuque.com/u1475064/mufu2a/vn5u10ephxh9sau8
![输入图片说明](%E6%88%AA%E5%9B%BE/app/2.jpg) ![输入图片说明](%E6%88%AA%E5%9B%BE/app/2.jpg)
#### 加入交流群 #### 加入交流群
1群目前已满员,扫码进入2群: 群1: 741174521(已满)
群2: 937470451(已满)
![输入图片说明](%E6%88%AA%E5%9B%BE/%E4%BA%A4%E6%B5%81%E7%BE%A42.png) 群3:
欢迎进群与小伙们一起交流, **申请加群前请务必先star哦** 欢迎进群与小伙们一起交流, **申请加群前请务必先star哦**

2
im-web/src/App.vue

@ -12,7 +12,6 @@ export default {
</script> </script>
<style lang="scss"> <style lang="scss">
#app { #app {
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
@ -22,5 +21,4 @@ export default {
color: var(--im-text-color); color: var(--im-text-color);
font-family: var(--im-font-family); font-family: var(--im-font-family);
} }
</style> </style>

10
im-web/src/api/camera.js

@ -5,13 +5,13 @@ class ImCamera {
} }
} }
ImCamera.prototype.isEnable = function() { ImCamera.prototype.isEnable = function () {
return !!navigator && !!navigator.mediaDevices && !!navigator.mediaDevices.getUserMedia; return !!navigator && !!navigator.mediaDevices && !!navigator.mediaDevices.getUserMedia;
} }
ImCamera.prototype.openVideo = function() { ImCamera.prototype.openVideo = function () {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if(this.stream){ if (this.stream) {
this.close() this.close()
} }
let constraints = { let constraints = {
@ -38,7 +38,7 @@ ImCamera.prototype.openVideo = function() {
} }
ImCamera.prototype.openAudio = function() { ImCamera.prototype.openAudio = function () {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let constraints = { let constraints = {
video: false, video: false,
@ -61,7 +61,7 @@ ImCamera.prototype.openAudio = function() {
}) })
} }
ImCamera.prototype.close = function() { ImCamera.prototype.close = function () {
// 停止流 // 停止流
if (this.stream) { if (this.stream) {
this.stream.getTracks().forEach((track) => { this.stream.getTracks().forEach((track) => {

6
im-web/src/api/date.js

@ -20,8 +20,8 @@ let toTimeText = (timeStamp, simple) => {
} else { } else {
//不属于今年 //不属于今年
timeText = formatDateTime(dateTime); timeText = formatDateTime(dateTime);
if(simple){ if (simple) {
timeText = timeText.substr(2,8); timeText = timeText.substr(2, 8);
} }
} }
return timeText; return timeText;
@ -58,7 +58,7 @@ let formatDateTime = (date) => {
} }
export{ export {
toTimeText, toTimeText,
isYestday, isYestday,
isYear, isYear,

6
im-web/src/api/element.js

@ -16,14 +16,14 @@ let fixLeft = (e) => {
let setTitleTip = (tip) => { let setTitleTip = (tip) => {
let title = process.env.VUE_APP_NAME; let title = process.env.VUE_APP_NAME;
if(tip){ if (tip) {
title = `(${tip})${title}`; title = `(${tip})${title}`;
} }
document.title =title; document.title = title;
} }
export default{ export default {
fixTop, fixTop,
fixLeft, fixLeft,
setTitleTip setTitleTip

4
im-web/src/api/emotion.js

@ -15,7 +15,7 @@ let transform = (content) => {
let textToImg = (emoText) => { let textToImg = (emoText) => {
let word = emoText.replace(/\#|\;/gi, ''); let word = emoText.replace(/\#|\;/gi, '');
let idx = emoTextList.indexOf(word); let idx = emoTextList.indexOf(word);
if(idx==-1){ if (idx == -1) {
return emoText; return emoText;
} }
let url = require(`@/assets/emoji/${idx}.gif`); let url = require(`@/assets/emoji/${idx}.gif`);
@ -25,7 +25,7 @@ let textToImg = (emoText) => {
let textToUrl = (emoText) => { let textToUrl = (emoText) => {
let word = emoText.replace(/\#|\;/gi, ''); let word = emoText.replace(/\#|\;/gi, '');
let idx = emoTextList.indexOf(word); let idx = emoTextList.indexOf(word);
if(idx==-1){ if (idx == -1) {
return ""; return "";
} }
let url = require(`@/assets/emoji/${idx}.gif`); let url = require(`@/assets/emoji/${idx}.gif`);

2
im-web/src/api/httpRequest.js

@ -42,7 +42,7 @@ http.interceptors.response.use(async response => {
headers: { headers: {
refreshToken: refreshToken refreshToken: refreshToken
} }
}).catch(()=>{ }).catch(() => {
location.href = "/"; location.href = "/";
}) })
// 保存token // 保存token

24
im-web/src/api/messageType.js

@ -1,32 +1,32 @@
// 是否普通消息 // 是否普通消息
let isNormal = function(type){ let isNormal = function (type) {
return type>=0 && type < 10; return type >= 0 && type < 10;
} }
// 是否状态消息 // 是否状态消息
let isStatus = function(type){ let isStatus = function (type) {
return type>=10 && type < 20; return type >= 10 && type < 20;
} }
// 是否提示消息 // 是否提示消息
let isTip = function(type){ let isTip = function (type) {
return type>=20 && type < 30; return type >= 20 && type < 30;
} }
// 操作交互类消息 // 操作交互类消息
let isAction = function(type){ let isAction = function (type) {
return type>=40 && type < 50; return type >= 40 && type < 50;
} }
// 单人通话信令 // 单人通话信令
let isRtcPrivate = function(type){ let isRtcPrivate = function (type) {
return type>=100 && type < 200; return type >= 100 && type < 200;
} }
// 多人通话信令 // 多人通话信令
let isRtcGroup = function(type){ let isRtcGroup = function (type) {
return type>=200 && type < 300; return type >= 200 && type < 300;
} }

36
im-web/src/api/rtcGroupApi.js

@ -1,8 +1,8 @@
import http from './httpRequest.js' import http from './httpRequest.js'
class RtcGroupApi {} class RtcGroupApi { }
RtcGroupApi.prototype.setup = function(groupId, userInfos) { RtcGroupApi.prototype.setup = function (groupId, userInfos) {
let formData = { let formData = {
groupId, groupId,
userInfos userInfos
@ -14,21 +14,21 @@ RtcGroupApi.prototype.setup = function(groupId, userInfos) {
}) })
} }
RtcGroupApi.prototype.accept = function(groupId) { RtcGroupApi.prototype.accept = function (groupId) {
return http({ return http({
url: '/webrtc/group/accept?groupId='+groupId, url: '/webrtc/group/accept?groupId=' + groupId,
method: 'post' method: 'post'
}) })
} }
RtcGroupApi.prototype.reject = function(groupId) { RtcGroupApi.prototype.reject = function (groupId) {
return http({ return http({
url: '/webrtc/group/reject?groupId='+groupId, url: '/webrtc/group/reject?groupId=' + groupId,
method: 'post' method: 'post'
}) })
} }
RtcGroupApi.prototype.failed = function(groupId,reason) { RtcGroupApi.prototype.failed = function (groupId, reason) {
let formData = { let formData = {
groupId, groupId,
reason reason
@ -41,14 +41,14 @@ RtcGroupApi.prototype.failed = function(groupId,reason) {
} }
RtcGroupApi.prototype.join = function(groupId) { RtcGroupApi.prototype.join = function (groupId) {
return http({ return http({
url: '/webrtc/group/join?groupId='+groupId, url: '/webrtc/group/join?groupId=' + groupId,
method: 'post' method: 'post'
}) })
} }
RtcGroupApi.prototype.invite = function(groupId, userInfos) { RtcGroupApi.prototype.invite = function (groupId, userInfos) {
let formData = { let formData = {
groupId, groupId,
userInfos userInfos
@ -61,7 +61,7 @@ RtcGroupApi.prototype.invite = function(groupId, userInfos) {
} }
RtcGroupApi.prototype.offer = function(groupId, userId, offer) { RtcGroupApi.prototype.offer = function (groupId, userId, offer) {
let formData = { let formData = {
groupId, groupId,
userId, userId,
@ -74,7 +74,7 @@ RtcGroupApi.prototype.offer = function(groupId, userId, offer) {
}) })
} }
RtcGroupApi.prototype.answer = function(groupId, userId, answer) { RtcGroupApi.prototype.answer = function (groupId, userId, answer) {
let formData = { let formData = {
groupId, groupId,
userId, userId,
@ -87,21 +87,21 @@ RtcGroupApi.prototype.answer = function(groupId, userId, answer) {
}) })
} }
RtcGroupApi.prototype.quit = function(groupId) { RtcGroupApi.prototype.quit = function (groupId) {
return http({ return http({
url: '/webrtc/group/quit?groupId=' + groupId, url: '/webrtc/group/quit?groupId=' + groupId,
method: 'post' method: 'post'
}) })
} }
RtcGroupApi.prototype.cancel = function(groupId) { RtcGroupApi.prototype.cancel = function (groupId) {
return http({ return http({
url: '/webrtc/group/cancel?groupId=' + groupId, url: '/webrtc/group/cancel?groupId=' + groupId,
method: 'post' method: 'post'
}) })
} }
RtcGroupApi.prototype.candidate = function(groupId, userId, candidate) { RtcGroupApi.prototype.candidate = function (groupId, userId, candidate) {
let formData = { let formData = {
groupId, groupId,
userId, userId,
@ -114,7 +114,7 @@ RtcGroupApi.prototype.candidate = function(groupId, userId, candidate) {
}) })
} }
RtcGroupApi.prototype.device = function(groupId, isCamera, isMicroPhone) { RtcGroupApi.prototype.device = function (groupId, isCamera, isMicroPhone) {
let formData = { let formData = {
groupId, groupId,
isCamera, isCamera,
@ -128,7 +128,7 @@ RtcGroupApi.prototype.device = function(groupId, isCamera, isMicroPhone) {
} }
RtcGroupApi.prototype.candidate = function(groupId, userId, candidate) { RtcGroupApi.prototype.candidate = function (groupId, userId, candidate) {
let formData = { let formData = {
groupId, groupId,
userId, userId,
@ -141,7 +141,7 @@ RtcGroupApi.prototype.candidate = function(groupId, userId, candidate) {
}) })
} }
RtcGroupApi.prototype.heartbeat = function(groupId) { RtcGroupApi.prototype.heartbeat = function (groupId) {
return http({ return http({
url: '/webrtc/group/heartbeat?groupId=' + groupId, url: '/webrtc/group/heartbeat?groupId=' + groupId,
method: 'post' method: 'post'

16
im-web/src/api/rtcPrivateApi.js

@ -3,7 +3,7 @@ import http from './httpRequest.js'
class RtcPrivateApi { class RtcPrivateApi {
} }
RtcPrivateApi.prototype.call = function(uid, mode, offer) { RtcPrivateApi.prototype.call = function (uid, mode, offer) {
return http({ return http({
url: `/webrtc/private/call?uid=${uid}&mode=${mode}`, url: `/webrtc/private/call?uid=${uid}&mode=${mode}`,
method: 'post', method: 'post',
@ -14,7 +14,7 @@ RtcPrivateApi.prototype.call = function(uid, mode, offer) {
}) })
} }
RtcPrivateApi.prototype.accept = function(uid, answer) { RtcPrivateApi.prototype.accept = function (uid, answer) {
return http({ return http({
url: `/webrtc/private/accept?uid=${uid}`, url: `/webrtc/private/accept?uid=${uid}`,
method: 'post', method: 'post',
@ -26,35 +26,35 @@ RtcPrivateApi.prototype.accept = function(uid, answer) {
} }
RtcPrivateApi.prototype.handup = function(uid) { RtcPrivateApi.prototype.handup = function (uid) {
return http({ return http({
url: `/webrtc/private/handup?uid=${uid}`, url: `/webrtc/private/handup?uid=${uid}`,
method: 'post' method: 'post'
}) })
} }
RtcPrivateApi.prototype.cancel = function(uid) { RtcPrivateApi.prototype.cancel = function (uid) {
return http({ return http({
url: `/webrtc/private/cancel?uid=${uid}`, url: `/webrtc/private/cancel?uid=${uid}`,
method: 'post' method: 'post'
}) })
} }
RtcPrivateApi.prototype.reject = function(uid) { RtcPrivateApi.prototype.reject = function (uid) {
return http({ return http({
url: `/webrtc/private/reject?uid=${uid}`, url: `/webrtc/private/reject?uid=${uid}`,
method: 'post' method: 'post'
}) })
} }
RtcPrivateApi.prototype.failed = function(uid, reason) { RtcPrivateApi.prototype.failed = function (uid, reason) {
return http({ return http({
url: `/webrtc/private/failed?uid=${uid}&reason=${reason}`, url: `/webrtc/private/failed?uid=${uid}&reason=${reason}`,
method: 'post' method: 'post'
}) })
} }
RtcPrivateApi.prototype.sendCandidate = function(uid, candidate) { RtcPrivateApi.prototype.sendCandidate = function (uid, candidate) {
return http({ return http({
url: `/webrtc/private/candidate?uid=${uid}`, url: `/webrtc/private/candidate?uid=${uid}`,
method: 'post', method: 'post',
@ -65,7 +65,7 @@ RtcPrivateApi.prototype.sendCandidate = function(uid, candidate) {
}); });
} }
RtcPrivateApi.prototype.heartbeat = function(uid) { RtcPrivateApi.prototype.heartbeat = function (uid) {
return http({ return http({
url: `/webrtc/private/heartbeat?uid=${uid}`, url: `/webrtc/private/heartbeat?uid=${uid}`,
method: 'post' method: 'post'

26
im-web/src/api/webrtc.js

@ -6,7 +6,7 @@ class ImWebRtc {
} }
} }
ImWebRtc.prototype.isEnable = function() { ImWebRtc.prototype.isEnable = function () {
window.RTCPeerConnection = window.RTCPeerConnection || window.webkitRTCPeerConnection || window window.RTCPeerConnection = window.RTCPeerConnection || window.webkitRTCPeerConnection || window
.mozRTCPeerConnection; .mozRTCPeerConnection;
window.RTCSessionDescription = window.RTCSessionDescription || window.webkitRTCSessionDescription || window window.RTCSessionDescription = window.RTCSessionDescription || window.webkitRTCSessionDescription || window
@ -16,11 +16,11 @@ ImWebRtc.prototype.isEnable = function() {
return !!window.RTCPeerConnection; return !!window.RTCPeerConnection;
} }
ImWebRtc.prototype.init = function(configuration) { ImWebRtc.prototype.init = function (configuration) {
this.configuration = configuration; this.configuration = configuration;
} }
ImWebRtc.prototype.setupPeerConnection = function(callback) { ImWebRtc.prototype.setupPeerConnection = function (callback) {
this.peerConnection = new RTCPeerConnection(this.configuration); this.peerConnection = new RTCPeerConnection(this.configuration);
this.peerConnection.ontrack = (e) => { this.peerConnection.ontrack = (e) => {
// 对方的视频流 // 对方的视频流
@ -29,11 +29,11 @@ ImWebRtc.prototype.setupPeerConnection = function(callback) {
} }
ImWebRtc.prototype.setStream = function(stream) { ImWebRtc.prototype.setStream = function (stream) {
if(this.stream){ if (this.stream) {
this.peerConnection.removeStream(this.stream) this.peerConnection.removeStream(this.stream)
} }
if(stream){ if (stream) {
stream.getTracks().forEach((track) => { stream.getTracks().forEach((track) => {
this.peerConnection.addTrack(track, stream); this.peerConnection.addTrack(track, stream);
}); });
@ -42,7 +42,7 @@ ImWebRtc.prototype.setStream = function(stream) {
} }
ImWebRtc.prototype.onIcecandidate = function(callback) { ImWebRtc.prototype.onIcecandidate = function (callback) {
this.peerConnection.onicecandidate = (event) => { this.peerConnection.onicecandidate = (event) => {
// 追踪到候选信息 // 追踪到候选信息
if (event.candidate) { if (event.candidate) {
@ -51,7 +51,7 @@ ImWebRtc.prototype.onIcecandidate = function(callback) {
} }
} }
ImWebRtc.prototype.onStateChange = function(callback) { ImWebRtc.prototype.onStateChange = function (callback) {
// 监听连接状态 // 监听连接状态
this.peerConnection.oniceconnectionstatechange = (event) => { this.peerConnection.oniceconnectionstatechange = (event) => {
let state = event.target.iceConnectionState; let state = event.target.iceConnectionState;
@ -60,7 +60,7 @@ ImWebRtc.prototype.onStateChange = function(callback) {
}; };
} }
ImWebRtc.prototype.createOffer = function() { ImWebRtc.prototype.createOffer = function () {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const offerParam = {}; const offerParam = {};
offerParam.offerToRecieveAudio = 1; offerParam.offerToRecieveAudio = 1;
@ -78,7 +78,7 @@ ImWebRtc.prototype.createOffer = function() {
} }
ImWebRtc.prototype.createAnswer = function(offer) { ImWebRtc.prototype.createAnswer = function (offer) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// 设置远端的sdp // 设置远端的sdp
this.setRemoteDescription(offer); this.setRemoteDescription(offer);
@ -97,17 +97,17 @@ ImWebRtc.prototype.createAnswer = function(offer) {
}); });
} }
ImWebRtc.prototype.setRemoteDescription = function(offer) { ImWebRtc.prototype.setRemoteDescription = function (offer) {
// 设置对方的sdp信息 // 设置对方的sdp信息
this.peerConnection.setRemoteDescription(new RTCSessionDescription(offer)); this.peerConnection.setRemoteDescription(new RTCSessionDescription(offer));
} }
ImWebRtc.prototype.addIceCandidate = function(candidate) { ImWebRtc.prototype.addIceCandidate = function (candidate) {
// 添加对方的候选人信息 // 添加对方的候选人信息
this.peerConnection.addIceCandidate(new RTCIceCandidate(candidate)); this.peerConnection.addIceCandidate(new RTCIceCandidate(candidate));
} }
ImWebRtc.prototype.close = function(uid) { ImWebRtc.prototype.close = function (uid) {
// 关闭RTC连接 // 关闭RTC连接
if (this.peerConnection) { if (this.peerConnection) {
this.peerConnection.close(); this.peerConnection.close();

36
im-web/src/api/wssocket.js

@ -1,19 +1,19 @@
var websock = null; var websock = null;
let rec; //断线重连后,延迟5秒重新创建WebSocket连接 rec用来存储延迟请求的代码 let rec; //断线重连后,延迟5秒重新创建WebSocket连接 rec用来存储延迟请求的代码
let isConnect = false; //连接标识 避免重复连接 let isConnect = false; //连接标识 避免重复连接
let connectCallBack= null; let connectCallBack = null;
let messageCallBack = null; let messageCallBack = null;
let closeCallBack = null let closeCallBack = null
let connect = (wsurl,accessToken) => { let connect = (wsurl, accessToken) => {
try { try {
if (isConnect) { if (isConnect) {
return; return;
} }
console.log("连接WebSocket"); console.log("连接WebSocket");
websock = new WebSocket(wsurl); websock = new WebSocket(wsurl);
websock.onmessage = function(e) { websock.onmessage = function (e) {
let sendInfo = JSON.parse(e.data) let sendInfo = JSON.parse(e.data)
if (sendInfo.cmd == 0) { if (sendInfo.cmd == 0) {
heartCheck.start() heartCheck.start()
@ -25,16 +25,16 @@ let connect = (wsurl,accessToken) => {
heartCheck.reset(); heartCheck.reset();
} else { } else {
// 其他消息转发出去 // 其他消息转发出去
console.log("收到消息:",sendInfo); console.log("收到消息:", sendInfo);
messageCallBack && messageCallBack(sendInfo.cmd, sendInfo.data) messageCallBack && messageCallBack(sendInfo.cmd, sendInfo.data)
} }
} }
websock.onclose = function(e) { websock.onclose = function (e) {
console.log('WebSocket连接关闭') console.log('WebSocket连接关闭')
isConnect = false; //断开后修改标识 isConnect = false; //断开后修改标识
closeCallBack && closeCallBack(e); closeCallBack && closeCallBack(e);
} }
websock.onopen = function() { websock.onopen = function () {
console.log("WebSocket连接成功"); console.log("WebSocket连接成功");
isConnect = true; isConnect = true;
// 发送登录命令 // 发送登录命令
@ -48,27 +48,27 @@ let connect = (wsurl,accessToken) => {
} }
// 连接发生错误的回调方法 // 连接发生错误的回调方法
websock.onerror = function() { websock.onerror = function () {
console.log('WebSocket连接发生错误') console.log('WebSocket连接发生错误')
isConnect = false; //连接断开修改标识 isConnect = false; //连接断开修改标识
reconnect(wsurl,accessToken); reconnect(wsurl, accessToken);
} }
} catch (e) { } catch (e) {
console.log("尝试创建连接失败"); console.log("尝试创建连接失败");
reconnect(wsurl,accessToken); //如果无法连接上webSocket 那么重新连接!可能会因为服务器重新部署,或者短暂断网等导致无法创建连接 reconnect(wsurl, accessToken); //如果无法连接上webSocket 那么重新连接!可能会因为服务器重新部署,或者短暂断网等导致无法创建连接
} }
}; };
//定义重连函数 //定义重连函数
let reconnect = (wsurl,accessToken) => { let reconnect = (wsurl, accessToken) => {
console.log("尝试重新连接"); console.log("尝试重新连接");
if (isConnect){ if (isConnect) {
//如果已经连上就不在重连了 //如果已经连上就不在重连了
return; return;
} }
rec && clearTimeout(rec); rec && clearTimeout(rec);
rec = setTimeout(function() { // 延迟5秒重连 避免过多次过频繁请求重连 rec = setTimeout(function () { // 延迟5秒重连 避免过多次过频繁请求重连
connect(wsurl,accessToken); connect(wsurl, accessToken);
}, 15000); }, 15000);
}; };
//设置关闭连接 //设置关闭连接
@ -81,7 +81,7 @@ let close = (code) => {
let heartCheck = { let heartCheck = {
timeout: 5000, //每段时间发送一次心跳包 这里设置为20s timeout: 5000, //每段时间发送一次心跳包 这里设置为20s
timeoutObj: null, //延时发送消息对象(启动心跳新建这个对象,收到消息后重置对象) timeoutObj: null, //延时发送消息对象(启动心跳新建这个对象,收到消息后重置对象)
start: function() { start: function () {
if (isConnect) { if (isConnect) {
console.log('发送WebSocket心跳') console.log('发送WebSocket心跳')
let heartBeat = { let heartBeat = {
@ -92,9 +92,9 @@ let heartCheck = {
} }
}, },
reset: function() { reset: function () {
clearTimeout(this.timeoutObj); clearTimeout(this.timeoutObj);
this.timeoutObj = setTimeout(function() { this.timeoutObj = setTimeout(function () {
heartCheck.start(); heartCheck.start();
}, this.timeout); }, this.timeout);
@ -110,12 +110,12 @@ let sendMessage = (agentData) => {
websock.send(JSON.stringify(agentData)) websock.send(JSON.stringify(agentData))
} else if (websock.readyState === websock.CONNECTING) { } else if (websock.readyState === websock.CONNECTING) {
// 若是 正在开启状态,则等待1s后重新调用 // 若是 正在开启状态,则等待1s后重新调用
setTimeout(function() { setTimeout(function () {
sendMessage(agentData) sendMessage(agentData)
}, 1000) }, 1000)
} else { } else {
// 若未开启 ,则等待1s后重新调用 // 若未开启 ,则等待1s后重新调用
setTimeout(function() { setTimeout(function () {
sendMessage(agentData) sendMessage(agentData)
}, 1000) }, 1000)
} }

20
im-web/src/components/chat/ChatAtBox.vue

@ -1,16 +1,16 @@
<template> <template>
<el-scrollbar v-show="show&&showMembers.length" ref="scrollBox" class="group-member-choose" <el-scrollbar v-show="show && showMembers.length" 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">
<chat-group-member :member="member" :height="40" :active='activeIdx==idx' <chat-group-member :member="member" :height="40" :active='activeIdx == idx'
@click.native="onSelectMember(member)"></chat-group-member> @click.native="onSelectMember(member)"></chat-group-member>
</div> </div>
</el-scrollbar> </el-scrollbar>
</template> </template>
<script> <script>
import ChatGroupMember from "./ChatGroupMember.vue"; import ChatGroupMember from "./ChatGroupMember.vue";
export default { export default {
name: "chatAtBox", name: "chatAtBox",
components: { components: {
ChatGroupMember ChatGroupMember
@ -55,7 +55,7 @@
this.showMembers.push(m); this.showMembers.push(m);
} }
}) })
this.activeIdx = this.showMembers.length > 0 ? 0: -1; this.activeIdx = this.showMembers.length > 0 ? 0 : -1;
}, },
open(pos) { open(pos) {
this.show = true; this.show = true;
@ -115,11 +115,11 @@
} }
} }
} }
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.group-member-choose { .group-member-choose {
position: fixed; position: fixed;
width: 200px; width: 200px;
height: 300px; height: 300px;
@ -127,5 +127,5 @@
//border-radius: 5px; //border-radius: 5px;
background-color: #fff; background-color: #fff;
box-shadow: var(--im-box-shadow); box-shadow: var(--im-box-shadow);
} }
</style> </style>

68
im-web/src/components/chat/ChatBox.vue

@ -61,7 +61,8 @@
<ChatInput :ownerId="group.ownerId" ref="chatInputEditor" :group-members="groupMembers" <ChatInput :ownerId="group.ownerId" ref="chatInputEditor" :group-members="groupMembers"
@submit="sendMessage" /> @submit="sendMessage" />
<div class="send-btn-area"> <div class="send-btn-area">
<el-button type="primary" icon="el-icon-s-promotion" @click="notifySend()">发送</el-button> <el-button type="primary" icon="el-icon-s-promotion"
@click="notifySend()">发送</el-button>
</div> </div>
</div> </div>
</el-footer> </el-footer>
@ -83,19 +84,19 @@
</template> </template>
<script> <script>
import ChatGroupSide from "./ChatGroupSide.vue"; import ChatGroupSide from "./ChatGroupSide.vue";
import ChatMessageItem from "./ChatMessageItem.vue"; import ChatMessageItem from "./ChatMessageItem.vue";
import FileUpload from "../common/FileUpload.vue"; import FileUpload from "../common/FileUpload.vue";
import Emotion from "../common/Emotion.vue"; import Emotion from "../common/Emotion.vue";
import ChatRecord from "./ChatRecord.vue"; import ChatRecord from "./ChatRecord.vue";
import ChatHistory from "./ChatHistory.vue"; import ChatHistory from "./ChatHistory.vue";
import ChatAtBox from "./ChatAtBox.vue" import ChatAtBox from "./ChatAtBox.vue"
import GroupMemberSelector from "../group/GroupMemberSelector.vue" import GroupMemberSelector from "../group/GroupMemberSelector.vue"
import RtcGroupJoin from "../rtc/RtcGroupJoin.vue" import RtcGroupJoin from "../rtc/RtcGroupJoin.vue"
import ChatInput from "./ChatInput"; import ChatInput from "./ChatInput";
export default { export default {
name: "chatPrivate", name: "chatPrivate",
components: { components: {
ChatInput, ChatInput,
@ -129,7 +130,7 @@
lockMessage: false, // lockMessage: false, //
showMinIdx: 0, // showMinIdx showMinIdx: 0, // showMinIdx
reqQueue: [], reqQueue: [],
isSending : false isSending: false
} }
}, },
methods: { methods: {
@ -369,7 +370,7 @@
let msg = fullList[i]; let msg = fullList[i];
switch (msg.type) { switch (msg.type) {
case "text": case "text":
await this.sendTextMessage(sendText + msg.content,msg.atUserIds); await this.sendTextMessage(sendText + msg.content, msg.atUserIds);
break; break;
case "image": case "image":
await this.sendImageMessage(msg.content.file); await this.sendImageMessage(msg.content.file);
@ -382,7 +383,7 @@
} }
}, },
sendImageMessage(file) { sendImageMessage(file) {
return new Promise((resolve,reject)=>{ return new Promise((resolve, reject) => {
this.onImageBefore(file); this.onImageBefore(file);
let formData = new FormData() let formData = new FormData()
formData.append('file', file) formData.append('file', file)
@ -401,8 +402,8 @@
this.scrollToBottom(); this.scrollToBottom();
}); });
}, },
sendTextMessage(sendText,atUserIds) { sendTextMessage(sendText, atUserIds) {
return new Promise((resolve,reject)=>{ return new Promise((resolve, reject) => {
if (!sendText.trim()) { if (!sendText.trim()) {
reject(); reject();
} }
@ -432,7 +433,7 @@
}); });
}, },
sendFileMessage(file) { sendFileMessage(file) {
return new Promise((resolve,reject)=>{ return new Promise((resolve, reject) => {
let check = this.$refs.fileUpload.beforeUpload(file); let check = this.$refs.fileUpload.beforeUpload(file);
if (check) { if (check) {
this.$refs.fileUpload.onFileUpload({ file }); this.$refs.fileUpload.onFileUpload({ file });
@ -481,7 +482,7 @@
this.$http({ this.$http({
url: url, url: url,
method: 'put' method: 'put'
}).then(() => {}) }).then(() => { })
}, },
loadReaded(fId) { loadReaded(fId) {
this.$http({ this.$http({
@ -561,14 +562,14 @@
this.placeholder = "聊点什么吧~"; this.placeholder = "聊点什么吧~";
} }
}, },
sendMessageRequest(msgInfo){ sendMessageRequest(msgInfo) {
return new Promise((resolve,reject)=>{ return new Promise((resolve, reject) => {
// "" // ""
this.reqQueue.push({msgInfo,resolve,reject}); this.reqQueue.push({ msgInfo, resolve, reject });
this.processReqQueue(); this.processReqQueue();
}) })
}, },
processReqQueue(){ processReqQueue() {
if (this.reqQueue.length && !this.isSending) { if (this.reqQueue.length && !this.isSending) {
this.isSending = true; this.isSending = true;
const reqData = this.reqQueue.shift(); const reqData = this.reqQueue.shift();
@ -576,11 +577,11 @@
url: this.messageAction, url: this.messageAction,
method: 'post', method: 'post',
data: reqData.msgInfo data: reqData.msgInfo
}).then((res)=>{ }).then((res) => {
reqData.resolve(res) reqData.resolve(res)
}).catch((e)=>{ }).catch((e) => {
reqData.reject(e) reqData.reject(e)
}).finally(()=>{ }).finally(() => {
this.isSending = false; this.isSending = false;
// //
this.processReqQueue(); this.processReqQueue();
@ -660,11 +661,11 @@
let div = document.getElementById("chatScrollBox"); let div = document.getElementById("chatScrollBox");
div.addEventListener('scroll', this.onScroll) div.addEventListener('scroll', this.onScroll)
} }
} }
</script> </script>
<style lang="scss"> <style lang="scss">
.chat-box { .chat-box {
position: relative; position: relative;
width: 100%; width: 100%;
background: #fff; background: #fff;
@ -718,7 +719,7 @@
border-top: var(--im-border); border-top: var(--im-border);
padding: 4px 2px 2px 8px; padding: 4px 2px 2px 8px;
> div { >div {
font-size: 22px; font-size: 22px;
cursor: pointer; cursor: pointer;
line-height: 30px; line-height: 30px;
@ -737,7 +738,7 @@
} }
} }
> div:hover { >div:hover {
color: #333; color: #333;
} }
} }
@ -829,6 +830,5 @@
//animation: rtl-drawer-in .3s 1ms; //animation: rtl-drawer-in .3s 1ms;
} }
} }
</style> </style>

10
im-web/src/components/chat/ChatGroupMember.vue

@ -1,9 +1,9 @@
<template> <template>
<div class="chat-group-member" :class="active?'active':''" :style="{'height':height+'px'}"> <div class="chat-group-member" :class="active ? 'active' : ''" :style="{ 'height': height + 'px' }">
<div class="member-avatar"> <div class="member-avatar">
<head-image :size="headImageSize" :name="member.showNickName" :url="member.headImage"> </head-image> <head-image :size="headImageSize" :name="member.showNickName" :url="member.headImage"> </head-image>
</div> </div>
<div class="member-name" :style="{'line-height':height+'px'}"> <div class="member-name" :style="{ 'line-height': height + 'px' }">
<div>{{ member.showNickName }}</div> <div>{{ member.showNickName }}</div>
</div> </div>
</div> </div>
@ -22,7 +22,7 @@ export default {
type: Object, type: Object,
required: true required: true
}, },
height:{ height: {
type: Number, type: Number,
default: 50 default: 50
}, },
@ -31,8 +31,8 @@ export default {
default: false default: false
} }
}, },
computed:{ computed: {
headImageSize(){ headImageSize() {
return Math.ceil(this.height * 0.75) return Math.ceil(this.height * 0.75)
} }
} }

19
im-web/src/components/chat/ChatGroupSide.vue

@ -8,18 +8,16 @@
<div class="group-side-scrollbar"> <div class="group-side-scrollbar">
<div v-show="!group.quit" class="group-side-member-list"> <div v-show="!group.quit" class="group-side-member-list">
<div class="group-side-invite"> <div class="group-side-invite">
<div class="invite-member-btn" title="邀请好友进群聊" @click="showAddGroupMember=true"> <div class="invite-member-btn" title="邀请好友进群聊" @click="showAddGroupMember = true">
<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="group.id" :members="groupMembers" <add-group-member :visible="showAddGroupMember" :groupId="group.id" :members="groupMembers"
@reload="$emit('reload')" @reload="$emit('reload')" @close="showAddGroupMember = false"></add-group-member>
@close="showAddGroupMember=false"></add-group-member>
</div> </div>
<div v-for="(member) in groupMembers" :key="member.id"> <div v-for="(member) in groupMembers" :key="member.id">
<group-member class="group-side-member" v-show="!member.quit && member.showNickName.includes(searchText)" <group-member class="group-side-member" v-show="!member.quit && member.showNickName.includes(searchText)"
:member="member" :member="member" :showDel="false"></group-member>
:showDel="false"></group-member>
</div> </div>
</div> </div>
<el-divider v-if="!group.quit" content-position="center"></el-divider> <el-divider v-if="!group.quit" content-position="center"></el-divider>
@ -34,16 +32,14 @@
<el-input v-model="group.notice" disabled type="textarea" maxlength="1024"></el-input> <el-input v-model="group.notice" disabled type="textarea" maxlength="1024"></el-input>
</el-form-item> </el-form-item>
<el-form-item label="备注"> <el-form-item label="备注">
<el-input v-model="group.remarkGroupName" :disabled="!editing" <el-input v-model="group.remarkGroupName" :disabled="!editing" maxlength="20"></el-input>
maxlength="20"></el-input>
</el-form-item> </el-form-item>
<el-form-item label="我在本群的昵称"> <el-form-item label="我在本群的昵称">
<el-input v-model="group.remarkNickName" :disabled="!editing" maxlength="20" <el-input v-model="group.remarkNickName" :disabled="!editing" maxlength="20"></el-input>
></el-input>
</el-form-item> </el-form-item>
<div v-show="!group.quit" class="btn-group"> <div v-show="!group.quit" class="btn-group">
<el-button v-if="editing" type="success" @click="onSaveGroup()">保存</el-button> <el-button v-if="editing" type="success" @click="onSaveGroup()">保存</el-button>
<el-button v-if="!editing" type="primary" @click="editing=!editing">编辑</el-button> <el-button v-if="!editing" type="primary" @click="editing = !editing">编辑</el-button>
<el-button type="danger" v-show="!isOwner" @click="onQuit()">退出群聊</el-button> <el-button type="danger" v-show="!isOwner" @click="onQuit()">退出群聊</el-button>
</div> </div>
</el-form> </el-form>
@ -217,7 +213,8 @@ export default {
} }
} }
.el-input__inner, .el-textarea__inner { .el-input__inner,
.el-textarea__inner {
color: var(--im-text-color) !important; color: var(--im-text-color) !important;
} }

37
im-web/src/components/chat/ChatHistory.vue

@ -1,12 +1,11 @@
<template> <template>
<el-drawer title="聊天历史记录" size="700px" :visible.sync="visible" direction="rtl" :before-close="onClose"> <el-drawer title="聊天历史记录" size="700px" :visible.sync="visible" direction="rtl" :before-close="onClose">
<div class="chat-history" v-loading="loading" <div class="chat-history" v-loading="loading" element-loading-text="拼命加载中">
element-loading-text="拼命加载中"> <el-scrollbar class="chat-history-scrollbar" ref="scrollbar" id="historyScrollbar">
<el-scrollbar class="chat-history-scrollbar" ref="scrollbar" id="historyScrollbar" >
<ul> <ul>
<li v-for="(msgInfo,idx) in messages" :key="idx"> <li v-for="(msgInfo, idx) in messages" :key="idx">
<chat-message-item :mode="2" :mine="msgInfo.sendId == mine.id" :headImage="headImage(msgInfo)" :showName="showName(msgInfo)" <chat-message-item :mode="2" :mine="msgInfo.sendId == mine.id" :headImage="headImage(msgInfo)"
:msgInfo="msgInfo" :menu="false"> :showName="showName(msgInfo)" :msgInfo="msgInfo" :menu="false">
</chat-message-item> </chat-message-item>
</li> </li>
</ul> </ul>
@ -16,9 +15,9 @@
</template> </template>
<script> <script>
import ChatMessageItem from './ChatMessageItem.vue'; import ChatMessageItem from './ChatMessageItem.vue';
export default { export default {
name: 'chatHistory', name: 'chatHistory',
components: { components: {
ChatMessageItem ChatMessageItem
@ -60,14 +59,14 @@
onScroll() { onScroll() {
let high = this.$refs.scrollbar.$refs.wrap.scrollTop; // let high = this.$refs.scrollbar.$refs.wrap.scrollTop; //
let timeDiff = new Date().getTime() - this.lastScrollTime.getTime(); let timeDiff = new Date().getTime() - this.lastScrollTime.getTime();
if ( high < 30 && timeDiff>500) { if (high < 30 && timeDiff > 500) {
this.lastScrollTime = new Date(); this.lastScrollTime = new Date();
this.loadMessages(); this.loadMessages();
} }
}, },
loadMessages() { loadMessages() {
if(this.loadAll){ if (this.loadAll) {
return this.$message.success("已到达顶部"); return this.$message.success("已到达顶部");
} }
let param = { let param = {
@ -87,11 +86,11 @@
}).then(messages => { }).then(messages => {
messages.forEach(m => this.messages.unshift(m)); messages.forEach(m => this.messages.unshift(m));
this.loading = false; this.loading = false;
if(messages.length <this.size){ if (messages.length < this.size) {
this.loadAll = true; this.loadAll = true;
} }
this.refreshScrollPos(); this.refreshScrollPos();
}).catch(()=>{ }).catch(() => {
this.loading = false; this.loading = false;
}) })
}, },
@ -111,7 +110,7 @@
return msgInfo.sendId == this.mine.id ? this.mine.headImageThumb : this.chat.headImage return msgInfo.sendId == this.mine.id ? this.mine.headImageThumb : this.chat.headImage
} }
}, },
refreshScrollPos(){ refreshScrollPos() {
let scrollWrap = this.$refs.scrollbar.$refs.wrap; let scrollWrap = this.$refs.scrollbar.$refs.wrap;
let scrollHeight = scrollWrap.scrollHeight; let scrollHeight = scrollWrap.scrollHeight;
let scrollTop = scrollWrap.scrollTop; let scrollTop = scrollWrap.scrollTop;
@ -119,7 +118,7 @@
let offsetTop = scrollWrap.scrollHeight - scrollHeight; let offsetTop = scrollWrap.scrollHeight - scrollHeight;
scrollWrap.scrollTop = scrollTop + offsetTop; scrollWrap.scrollTop = scrollTop + offsetTop;
// //
if(scrollWrap.scrollHeight == scrollHeight){ if (scrollWrap.scrollHeight == scrollHeight) {
this.loadMessages(); this.loadMessages();
} }
}); });
@ -139,25 +138,27 @@
if (newValue) { if (newValue) {
this.loadMessages(); this.loadMessages();
this.$nextTick(() => { this.$nextTick(() => {
document.getElementById('historyScrollbar').addEventListener("mousewheel", this.onScroll,true); document.getElementById('historyScrollbar').addEventListener("mousewheel", this.onScroll, true);
}); });
} }
} }
} }
} }
} }
</script> </script>
<style lang="scss"> <style lang="scss">
.chat-history { .chat-history {
display: flex; display: flex;
height: 100%; height: 100%;
.chat-history-scrollbar { .chat-history-scrollbar {
flex: 1; flex: 1;
.el-scrollbar__thumb { .el-scrollbar__thumb {
background-color: #555555; background-color: #555555;
} }
ul { ul {
padding: 20px; padding: 20px;
@ -166,5 +167,5 @@
} }
} }
} }
} }
</style> </style>

30
im-web/src/components/chat/ChatInput.vue

@ -1,7 +1,7 @@
<template> <template>
<div class="chat-input-area"> <div class="chat-input-area">
<div :class="['edit-chat-container',isEmpty?'':'not-empty']" contenteditable="true" @paste.prevent="onPaste" <div :class="['edit-chat-container', isEmpty ? '' : 'not-empty']" contenteditable="true" @paste.prevent="onPaste"
@keydown="onKeydown" @compositionstart="compositionFlag=true" @compositionend="onCompositionEnd" @keydown="onKeydown" @compositionstart="compositionFlag = true" @compositionend="onCompositionEnd"
@input="onEditorInput" @mousedown="onMousedown" ref="content" @blur="onBlur"> @input="onEditorInput" @mousedown="onMousedown" ref="content" @blur="onBlur">
</div> </div>
<chat-at-box @select="onAtSelect" :search-text="atSearchText" ref="atBox" :ownerId="ownerId" <chat-at-box @select="onAtSelect" :search-text="atSearchText" ref="atBox" :ownerId="ownerId"
@ -10,9 +10,9 @@
</template> </template>
<script> <script>
import ChatAtBox from "./ChatAtBox"; import ChatAtBox from "./ChatAtBox";
export default { export default {
name: "ChatInput", name: "ChatInput",
components: { ChatAtBox }, components: { ChatAtBox },
props: { props: {
@ -45,7 +45,7 @@
range.deleteContents(); range.deleteContents();
} }
// //
if (txt && typeof(txt) == 'string') { if (txt && typeof (txt) == 'string') {
let textNode = document.createTextNode(txt); let textNode = document.createTextNode(txt);
range.insertNode(textNode) range.insertNode(textNode)
range.collapse(); range.collapse();
@ -143,7 +143,7 @@
let s = this.$refs.content.innerHTML.trim(); let s = this.$refs.content.innerHTML.trim();
// domdom // domdom
console.log(s); console.log(s);
if (s === '' || s === '<br>' || s === '<div>&nbsp;</div>' ) { if (s === '' || s === '<br>' || s === '<div>&nbsp;</div>') {
// dom // dom
this.empty(); this.empty();
this.isEmpty = true; this.isEmpty = true;
@ -173,7 +173,7 @@
// @xx // @xx
let blurRange = this.blurRange; let blurRange = this.blurRange;
let endContainer = blurRange.endContainer let endContainer = blurRange.endContainer
let startOffset = endContainer.data.indexOf("@"+this.atSearchText); let startOffset = endContainer.data.indexOf("@" + this.atSearchText);
let endOffset = startOffset + this.atSearchText.length + 1; let endOffset = startOffset + this.atSearchText.length + 1;
blurRange.setStart(blurRange.endContainer, startOffset); blurRange.setStart(blurRange.endContainer, startOffset);
blurRange.setEnd(blurRange.endContainer, endOffset); blurRange.setEnd(blurRange.endContainer, endOffset);
@ -351,7 +351,7 @@
let line = this.newLine(); let line = this.newLine();
let after = document.createTextNode('\u00A0'); let after = document.createTextNode('\u00A0');
line.appendChild(after); line.appendChild(after);
this.$nextTick(()=>this.selectElement(after)); this.$nextTick(() => this.selectElement(after));
}, },
showAtBox(e) { showAtBox(e) {
this.atIng = true; this.atIng = true;
@ -369,13 +369,13 @@
this.updateRange(); this.updateRange();
}, },
html2Escape(strHtml) { html2Escape(strHtml) {
return strHtml.replace(/[<>&"]/g, function(c) { return strHtml.replace(/[<>&"]/g, function (c) {
return { return {
'<': '&lt;', '<': '&lt;',
'>': '&gt;', '>': '&gt;',
'&': '&amp;', '&': '&amp;',
'"': '&quot;' '"': '&quot;'
} [c]; }[c];
}); });
}, },
submit() { submit() {
@ -440,10 +440,10 @@
each(node.childNodes); each(node.childNodes);
} }
} else if (nodeName === 'span') { } else if (nodeName === 'span') {
if(node.dataset.id){ if (node.dataset.id) {
tempText += node.innerHTML; tempText += node.innerHTML;
atUserIds.push(node.dataset.id) atUserIds.push(node.dataset.id)
}else { } else {
tempText += node.outerHtml; tempText += node.outerHtml;
} }
} }
@ -465,11 +465,11 @@
} }
} }
} }
</script> </script>
<style lang="scss"> <style lang="scss">
.chat-input-area { .chat-input-area {
width: 100%; width: 100%;
height: 100%; height: 100%;
position: relative; position: relative;
@ -563,5 +563,5 @@
content: none; content: none;
} }
} }
</style> </style>

32
im-web/src/components/chat/ChatItem.vue

@ -2,36 +2,34 @@
<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="42" <head-image :url="chat.headImage" :name="chat.showName" :size="42"
:id="chat.type=='PRIVATE'?chat.targetId:0" :isShowUserInfo="false"></head-image> :id="chat.type == 'PRIVATE' ? chat.targetId : 0" :isShowUserInfo="false"></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">
<div class="chat-name-text"> <div class="chat-name-text">
<div>{{chat.showName}}</div> <div>{{ chat.showName }}</div>
<el-tag v-if="chat.type=='GROUP'" size="mini" effect="dark"></el-tag> <el-tag v-if="chat.type == 'GROUP'" size="mini" effect="dark"></el-tag>
</div> </div>
<div class="chat-time-text">{{ showTime }}</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-at-text">{{ atText }}</div>
<div class="chat-send-name" v-show="isShowSendName">{{chat.sendNickName+':&nbsp;'}}</div> <div class="chat-send-name" v-show="isShowSendName">{{ 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> </div>
</div> </div>
<right-menu v-show="rightMenu.show" :pos="rightMenu.pos" :items="rightMenu.items" @close="rightMenu.show=false" <right-menu v-show="rightMenu.show" :pos="rightMenu.pos" :items="rightMenu.items"
@select="onSelectMenu"></right-menu> @close="rightMenu.show = false" @select="onSelectMenu"></right-menu>
</div> </div>
</template> </template>
<script> <script>
import HeadImage from '../common/HeadImage.vue'; import HeadImage from '../common/HeadImage.vue';
import RightMenu from '../common/RightMenu.vue'; import RightMenu from '../common/RightMenu.vue';
export default { export default {
name: "chatItem", name: "chatItem",
components: { components: {
HeadImage, HeadImage,
@ -105,11 +103,11 @@
return ""; return "";
} }
} }
} }
</script> </script>
<style lang="scss"> <style lang="scss">
.chat-item { .chat-item {
height: 50px; height: 50px;
display: flex; display: flex;
position: relative; position: relative;
@ -227,5 +225,5 @@
} }
} }
} }
</style> </style>

22
im-web/src/components/chat/ChatMessageItem.vue

@ -51,11 +51,11 @@
<audio controls :src="JSON.parse(msgInfo.content).url"></audio> <audio controls :src="JSON.parse(msgInfo.content).url"></audio>
</div> </div>
<div class="chat-action chat-msg-text" v-if="isAction"> <div class="chat-action chat-msg-text" v-if="isAction">
<span v-if="msgInfo.type==$enums.MESSAGE_TYPE.ACT_RT_VOICE" title="重新呼叫" @click="$emit('call')" <span v-if="msgInfo.type == $enums.MESSAGE_TYPE.ACT_RT_VOICE" title="重新呼叫" @click="$emit('call')"
class="iconfont icon-chat-voice"></span> class="iconfont icon-chat-voice"></span>
<span v-if="msgInfo.type==$enums.MESSAGE_TYPE.ACT_RT_VIDEO" title="重新呼叫" @click="$emit('call')" <span v-if="msgInfo.type == $enums.MESSAGE_TYPE.ACT_RT_VIDEO" title="重新呼叫" @click="$emit('call')"
class="iconfont icon-chat-video"></span> class="iconfont icon-chat-video"></span>
<span>{{msgInfo.content}}</span> <span>{{ msgInfo.content }}</span>
</div> </div>
<div class="chat-msg-status" v-if="!isAction"> <div class="chat-msg-status" v-if="!isAction">
<span class="chat-readed" v-show="msgInfo.selfSend && !msgInfo.groupId <span class="chat-readed" v-show="msgInfo.selfSend && !msgInfo.groupId
@ -65,7 +65,7 @@
</div> </div>
<div class="chat-receipt" v-show="msgInfo.receipt" @click="onShowReadedBox"> <div class="chat-receipt" v-show="msgInfo.receipt" @click="onShowReadedBox">
<span v-if="msgInfo.receiptOk" class="icon iconfont icon-ok" title="全体已读"></span> <span v-if="msgInfo.receiptOk" class="icon iconfont icon-ok" title="全体已读"></span>
<span v-else>{{msgInfo.readedCount}}人已读</span> <span v-else>{{ msgInfo.readedCount }}人已读</span>
</div> </div>
</div> </div>
</div> </div>
@ -77,10 +77,10 @@
</template> </template>
<script> <script>
import HeadImage from "../common/HeadImage.vue"; import HeadImage from "../common/HeadImage.vue";
import RightMenu from '../common/RightMenu.vue'; import RightMenu from '../common/RightMenu.vue';
import ChatGroupReaded from './ChatGroupReaded.vue'; import ChatGroupReaded from './ChatGroupReaded.vue';
export default { export default {
name: "messageItem", name: "messageItem",
components: { components: {
HeadImage, HeadImage,
@ -205,11 +205,11 @@
return this.$msgType.isNormal(type) || this.$msgType.isAction(type) return this.$msgType.isNormal(type) || this.$msgType.isAction(type)
} }
} }
} }
</script> </script>
<style lang="scss"> <style lang="scss">
.chat-msg-item { .chat-msg-item {
.chat-msg-tip { .chat-msg-tip {
line-height: 50px; line-height: 50px;
@ -471,5 +471,5 @@
} }
} }
} }
</style> </style>

35
im-web/src/components/chat/ChatRecord.vue

@ -1,32 +1,31 @@
<template> <template>
<el-dialog class="chat-record" title="语音录制" :visible.sync="visible" width="600px" :before-close="onClose"> <el-dialog class="chat-record" title="语音录制" :visible.sync="visible" width="600px" :before-close="onClose">
<div v-show="mode=='RECORD'"> <div v-show="mode == 'RECORD'">
<div class="tip">{{stateTip}}</div> <div class="tip">{{ stateTip }}</div>
<div>时长: {{state=='STOP'?0:parseInt(rc.duration)}}s</div> <div>时长: {{ state == 'STOP' ? 0 : parseInt(rc.duration) }}s</div>
</div> </div>
<audio v-show="mode=='PLAY'" :src="url" controls ref="audio" @ended="onStopAudio()"></audio> <audio v-show="mode == 'PLAY'" :src="url" controls ref="audio" @ended="onStopAudio()"></audio>
<el-divider content-position="center"></el-divider> <el-divider content-position="center"></el-divider>
<el-row class="btn-group"> <el-row class="btn-group">
<el-button round type="primary" v-show="state=='STOP'" @click="onStartRecord()">开始录音</el-button> <el-button round type="primary" v-show="state == 'STOP'" @click="onStartRecord()">开始录音</el-button>
<el-button round type="warning" v-show="state=='RUNNING'" @click="onPauseRecord()">暂停录音</el-button> <el-button round type="warning" v-show="state == 'RUNNING'" @click="onPauseRecord()">暂停录音</el-button>
<el-button round type="primary" v-show="state=='PAUSE'" @click="onResumeRecord()">继续录音</el-button> <el-button round type="primary" v-show="state == 'PAUSE'" @click="onResumeRecord()">继续录音</el-button>
<el-button round type="danger" v-show="state=='RUNNING'||state=='PAUSE'" @click="onCompleteRecord()"> <el-button round type="danger" v-show="state == 'RUNNING' || state == 'PAUSE'" @click="onCompleteRecord()">
结束录音</el-button> 结束录音</el-button>
<el-button round type="success" v-show="state=='COMPLETE' && mode!='PLAY'" @click="onPlayAudio()">播放录音 <el-button round type="success" v-show="state == 'COMPLETE' && mode != 'PLAY'" @click="onPlayAudio()">播放录音
</el-button> </el-button>
<el-button round type="warning" v-show="state=='COMPLETE' && mode=='PLAY'" @click="onStopAudio()">停止播放 <el-button round type="warning" v-show="state == 'COMPLETE' && mode == 'PLAY'" @click="onStopAudio()">停止播放
</el-button> </el-button>
<el-button round type="primary" v-show="state=='COMPLETE'" @click="onRestartRecord()">重新录音</el-button> <el-button round type="primary" v-show="state == 'COMPLETE'" @click="onRestartRecord()">重新录音</el-button>
<el-button round type="primary" v-show="state=='COMPLETE'" @click="onSendRecord()">立即发送</el-button> <el-button round type="primary" v-show="state == 'COMPLETE'" @click="onSendRecord()">立即发送</el-button>
</el-row> </el-row>
</el-dialog> </el-dialog>
</template> </template>
<script> <script>
import Recorder from 'js-audio-recorder'; import Recorder from 'js-audio-recorder';
export default { export default {
name: 'chatRecord', name: 'chatRecord',
props: { props: {
visible: { visible: {
@ -122,11 +121,11 @@
} }
} }
} }
</script> </script>
<style lang="scss"> <style lang="scss">
.chat-record { .chat-record {
.tip { .tip {
font-size: 18px; font-size: 18px;
@ -135,5 +134,5 @@
.btn-group { .btn-group {
margin-bottom: 20px; margin-bottom: 20px;
} }
} }
</style> </style>

10
im-web/src/components/common/Emotion.vue

@ -1,6 +1,6 @@
<template> <template>
<div v-show="show" @click="close()"> <div v-show="show" @click="close()">
<div class="emotion-box" :style="{'left':x+'px','top':y+'px'}"> <div class="emotion-box" :style="{ 'left': x + 'px', 'top': y + 'px' }">
<el-scrollbar style="height: 220px"> <el-scrollbar style="height: 220px">
<div class="emotion-item-list"> <div class="emotion-item-list">
<div class="emotion-item" v-for="(emoText, i) in $emo.emoTextList" :key="i" <div class="emotion-item" v-for="(emoText, i) in $emo.emoTextList" :key="i"
@ -13,7 +13,7 @@
</template> </template>
<script> <script>
export default { export default {
name: "emotion", name: "emotion",
data() { data() {
return { return {
@ -45,10 +45,10 @@
return this.pos.y - 234; return this.pos.y - 234;
} }
} }
} }
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.emotion-box { .emotion-box {
position: fixed; position: fixed;
width: 372px; width: 372px;
box-sizing: border-box; box-sizing: border-box;
@ -81,5 +81,5 @@
// overflow: hidden; // overflow: hidden;
// border-width: 15px; // border-width: 15px;
//} //}
} }
</style> </style>

11
im-web/src/components/common/FileUpload.vue

@ -1,12 +1,12 @@
<template> <template>
<el-upload :action="'#'" :http-request="onFileUpload" :accept="fileTypes==null?'':fileTypes.join(',')" :show-file-list="false" <el-upload :action="'#'" :http-request="onFileUpload" :accept="fileTypes == null ? '' : fileTypes.join(',')"
:disabled="disabled" :before-upload="beforeUpload" :multiple="true"> :show-file-list="false" :disabled="disabled" :before-upload="beforeUpload" :multiple="true">
<slot></slot> <slot></slot>
</el-upload> </el-upload>
</template> </template>
<script> <script>
export default { export default {
name: "fileUpload", name: "fileUpload",
data() { data() {
return { return {
@ -96,8 +96,7 @@
return this.maxSize + "B"; return this.maxSize + "B";
} }
} }
} }
</script> </script>
<style> <style></style>
</style>

8
im-web/src/components/common/FullImage.vue

@ -9,7 +9,7 @@
</template> </template>
<script> <script>
export default { export default {
name: "fullImage", name: "fullImage",
data() { data() {
return { return {
@ -29,11 +29,11 @@
type: String type: String
} }
} }
} }
</script> </script>
<style lang="scss"> <style lang="scss">
.full-image { .full-image {
position: fixed; position: fixed;
width: 100%; width: 100%;
height: 100%; height: 100%;
@ -74,5 +74,5 @@
font-size: 25px; font-size: 25px;
cursor: pointer; cursor: pointer;
} }
} }
</style> </style>

15
im-web/src/components/common/HeadImage.vue

@ -1,8 +1,8 @@
<template> <template>
<div class="head-image" @click="showUserInfo($event)" :style="{cursor : isShowUserInfo ? 'pointer': null}"> <div class="head-image" @click="showUserInfo($event)" :style="{ cursor: isShowUserInfo ? 'pointer' : null }">
<img class="avatar-image" v-show="url" :src="url" :style="avatarImageStyle" loading="lazy" /> <img class="avatar-image" v-show="url" :src="url" :style="avatarImageStyle" loading="lazy" />
<div class="avatar-text" v-show="!url" :style="avatarTextStyle"> <div class="avatar-text" v-show="!url" :style="avatarTextStyle">
{{name?.substring(0,2).toUpperCase()}} {{ name?.substring(0, 2).toUpperCase() }}
</div> </div>
<div v-show="online" class="online" title="用户当前在线"></div> <div v-show="online" class="online" title="用户当前在线"></div>
<slot></slot> <slot></slot>
@ -10,14 +10,13 @@
</template> </template>
<script> <script>
export default { export default {
name: "headImage", name: "headImage",
data() { data() {
return { return {
colors: ["#5daa31", "#c7515a", "#e03697", "#85029b", colors: ["#5daa31", "#c7515a", "#e03697", "#85029b",
"#c9b455", "#326eb6" "#c9b455", "#326eb6"
] ]
} }
}, },
props: { props: {
@ -81,7 +80,7 @@
return ` return `
width: ${w}px;height:${h}px; width: ${w}px;height:${h}px;
background-color: ${this.name ? this.textColor : '#fff'}; background-color: ${this.name ? this.textColor : '#fff'};
font-size:${w*0.35}px; font-size:${w * 0.35}px;
border-radius: ${this.radius}; border-radius: ${this.radius};
` `
}, },
@ -93,11 +92,11 @@
return this.colors[hash % this.colors.length]; return this.colors[hash % this.colors.length];
} }
} }
} }
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.head-image { .head-image {
position: relative; position: relative;
//cursor: pointer; //cursor: pointer;
@ -126,5 +125,5 @@
border-radius: 50%; border-radius: 50%;
border: 2px solid white; border: 2px solid white;
} }
} }
</style> </style>

4
im-web/src/components/common/Icp.vue

@ -9,7 +9,7 @@
</script> </script>
<style lang="scss"> <style lang="scss">
.icp { .icp {
position: fixed; position: fixed;
text-align: center; text-align: center;
bottom: 20px; bottom: 20px;
@ -22,5 +22,5 @@
height: 20px; height: 20px;
vertical-align: bottom; vertical-align: bottom;
} }
} }
</style> </style>

18
im-web/src/components/common/RightMenu.vue

@ -1,11 +1,11 @@
<template> <template>
<div class="right-menu-mask" @click="close()" @contextmenu.prevent="close()"> <div class="right-menu-mask" @click="close()" @contextmenu.prevent="close()">
<div class="right-menu" :style="{'left':pos.x+'px','top':pos.y+'px'}"> <div class="right-menu" :style="{ 'left': pos.x + 'px', 'top': pos.y + 'px' }">
<el-menu text-color="#333333"> <el-menu text-color="#333333">
<el-menu-item v-for="(item) in items" :key="item.key" :title="item.name" <el-menu-item v-for="(item) in items" :key="item.key" :title="item.name"
@click.native.stop="onSelectMenu(item)"> @click.native.stop="onSelectMenu(item)">
<!-- <span :class="item.icon"></span>--> <!-- <span :class="item.icon"></span>-->
<span>{{item.name}}</span> <span>{{ item.name }}</span>
</el-menu-item> </el-menu-item>
</el-menu> </el-menu>
</div> </div>
@ -13,7 +13,7 @@
</template> </template>
<script> <script>
export default { export default {
name: "rightMenu", name: "rightMenu",
data() { data() {
return {} return {}
@ -35,11 +35,11 @@
this.close(); this.close();
} }
} }
} }
</script> </script>
<style lang="scss"> <style lang="scss">
.right-menu-mask { .right-menu-mask {
position: fixed; position: fixed;
left: 0; left: 0;
top: 0; top: 0;
@ -48,9 +48,9 @@
width: 100%; width: 100%;
height: 100%; height: 100%;
z-index: 9999; z-index: 9999;
} }
.right-menu { .right-menu {
position: fixed; position: fixed;
border-radius: 8px; border-radius: 8px;
overflow: hidden; overflow: hidden;
@ -72,5 +72,5 @@
} }
} }
} }
} }
</style> </style>

22
im-web/src/components/common/UserInfo.vue

@ -1,11 +1,10 @@
<template> <template>
<div class="user-info-mask" @click="$emit('close')"> <div class="user-info-mask" @click="$emit('close')">
<div class="user-info" :style="{left: pos.x+'px',top: pos.y+'px'}" @click.stop> <div class="user-info" :style="{ left: pos.x + 'px', top: pos.y + 'px' }" @click.stop>
<div class="user-info-box"> <div class="user-info-box">
<div class="avatar"> <div class="avatar">
<head-image :name="user.nickName" :url="user.headImageThumb" :size="70" <head-image :name="user.nickName" :url="user.headImageThumb" :size="70" :online="user.online"
:online="user.online" radius="10%" radius="10%" @click.native="showFullImage()"> </head-image>
@click.native="showFullImage()"> </head-image>
</div> </div>
<div> <div>
<el-descriptions :column="1" :title="user.userName" class="user-info-items"> <el-descriptions :column="1" :title="user.userName" class="user-info-items">
@ -15,7 +14,6 @@
</el-descriptions-item> </el-descriptions-item>
</el-descriptions> </el-descriptions>
</div> </div>
</div> </div>
<el-divider content-position="center"></el-divider> <el-divider content-position="center"></el-divider>
<div class="user-btn-group"> <div class="user-btn-group">
@ -27,9 +25,9 @@
</template> </template>
<script> <script>
import HeadImage from './HeadImage.vue' import HeadImage from './HeadImage.vue'
export default { export default {
name: "userInfo", name: "userInfo",
components: { components: {
HeadImage HeadImage
@ -94,20 +92,20 @@
return friend != undefined; return friend != undefined;
} }
} }
} }
</script> </script>
<style lang="scss"> <style lang="scss">
.user-info-mask { .user-info-mask {
background-color: rgba($color: #000000, $alpha: 0); background-color: rgba($color: #000000, $alpha: 0);
position: fixed; position: fixed;
left: 0; left: 0;
top: 0; top: 0;
right: 0; right: 0;
bottom: 0; bottom: 0;
} }
.user-info { .user-info {
position: absolute; position: absolute;
width: 300px; width: 300px;
background-color: white; background-color: white;
@ -145,5 +143,5 @@
.user-btn-group { .user-btn-group {
text-align: center; text-align: center;
} }
} }
</style> </style>

53
im-web/src/components/friend/AddFriend.vue

@ -1,28 +1,28 @@
<template> <template>
<el-dialog title="添加好友" :visible.sync="dialogVisible" width="400px" :before-close="onClose" custom-class="add-friend-dialog"> <el-dialog title="添加好友" :visible.sync="dialogVisible" width="400px" :before-close="onClose"
<el-input placeholder="输入用户名或昵称按下enter搜索,最多展示20条" class="input-with-select" v-model="searchText" size="small" @keyup.enter.native="onSearch()"> custom-class="add-friend-dialog">
<i class="el-icon-search el-input__icon" slot="suffix" <el-input placeholder="输入用户名或昵称按下enter搜索,最多展示20条" class="input-with-select" v-model="searchText" size="small"
@click="onSearch()"> </i> @keyup.enter.native="onSearch()">
<i class="el-icon-search el-input__icon" slot="suffix" @click="onSearch()"> </i>
</el-input> </el-input>
<el-scrollbar style="height:400px"> <el-scrollbar style="height:400px">
<div v-for="(user) in users" :key="user.id" v-show="user.id != $store.state.userStore.userInfo.id"> <div v-for="(user) in users" :key="user.id" v-show="user.id != $store.state.userStore.userInfo.id">
<div class="item"> <div class="item">
<div class="avatar"> <div class="avatar">
<head-image :name="user.nickName" <head-image :name="user.nickName" :url="user.headImage" :online="user.online"></head-image>
:url="user.headImage"
:online="user.online"
></head-image>
</div> </div>
<div class="add-friend-text"> <div class="add-friend-text">
<div class="text-user-name"> <div class="text-user-name">
<div>{{user.userName}}</div> <div>{{ user.userName }}</div>
<div :class="user.online ? 'online-status online':'online-status'">{{ user.online?"[在线]":"[离线]"}}</div> <div :class="user.online ? 'online-status online' : 'online-status'">{{
user.online ? "[在线]" :"[离线]"}}</div>
</div> </div>
<div class="text-nick-name"> <div class="text-nick-name">
<div>昵称:{{user.nickName}}</div> <div>昵称:{{ user.nickName }}</div>
</div> </div>
</div> </div>
<el-button type="success" size="mini" v-show="!isFriend(user.id)" @click="onAddFriend(user)">添加</el-button> <el-button type="success" size="mini" v-show="!isFriend(user.id)"
@click="onAddFriend(user)">添加</el-button>
<el-button type="info" size="mini" v-show="isFriend(user.id)" plain disabled>已添加</el-button> <el-button type="info" size="mini" v-show="isFriend(user.id)" plain disabled>已添加</el-button>
</div> </div>
</div> </div>
@ -31,12 +31,12 @@
</template> </template>
<script> <script>
import HeadImage from '../common/HeadImage.vue' import HeadImage from '../common/HeadImage.vue'
export default { export default {
name: "addFriend", name: "addFriend",
components:{HeadImage}, components: { HeadImage },
data() { data() {
return { return {
users: [], users: [],
@ -53,7 +53,7 @@
this.$emit("close"); this.$emit("close");
}, },
onSearch() { onSearch() {
if(!this.searchText){ if (!this.searchText) {
this.users = []; this.users = [];
return; return;
} }
@ -67,7 +67,7 @@
this.users = data; this.users = data;
}) })
}, },
onAddFriend(user){ onAddFriend(user) {
this.$http({ this.$http({
url: "/friend/add", url: "/friend/add",
method: "post", method: "post",
@ -77,21 +77,21 @@
}).then((data) => { }).then((data) => {
this.$message.success("添加成功,对方已成为您的好友"); this.$message.success("添加成功,对方已成为您的好友");
let friend = { let friend = {
id:user.id, id: user.id,
nickName: user.nickName, nickName: user.nickName,
headImage: user.headImage, headImage: user.headImage,
online: user.online online: user.online
} }
this.$store.commit("addFriend",friend); this.$store.commit("addFriend", friend);
}) })
}, },
isFriend(userId){ isFriend(userId) {
let friends = this.$store.state.friendStore.friends; let friends = this.$store.state.friendStore.friends;
let friend = friends.find((f)=> f.id==userId); let friend = friends.find((f) => f.id == userId);
return friend != undefined; return friend != undefined;
} }
} }
} }
</script> </script>
<style lang="scss"> <style lang="scss">
@ -112,23 +112,24 @@
flex-shrink: 0; flex-shrink: 0;
overflow: hidden; overflow: hidden;
.text-user-name{ .text-user-name {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
font-weight: 600; font-weight: 600;
font-size: 16px; font-size: 16px;
line-height: 25px; line-height: 25px;
.online-status{ .online-status {
font-size: 12px; font-size: 12px;
font-weight: 600; font-weight: 600;
&.online{
&.online {
color: #5fb878; color: #5fb878;
} }
} }
} }
.text-nick-name{ .text-nick-name {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
font-size: 12px; font-size: 12px;

22
im-web/src/components/friend/FriendItem.vue

@ -5,7 +5,7 @@
</head-image> </head-image>
</div> </div>
<div class="friend-info"> <div class="friend-info">
<div class="friend-name">{{ friend.nickName}}</div> <div class="friend-name">{{ friend.nickName }}</div>
<div class="friend-online"> <div class="friend-online">
<i class="el-icon-monitor online" v-show="friend.onlineWeb" title="电脑设备在线"> <i class="el-icon-monitor online" v-show="friend.onlineWeb" title="电脑设备在线">
<span class="online-icon"></span> <span class="online-icon"></span>
@ -16,16 +16,16 @@
</div> </div>
</div> </div>
<right-menu v-show="menu && rightMenu.show" :pos="rightMenu.pos" :items="rightMenu.items" <right-menu v-show="menu && rightMenu.show" :pos="rightMenu.pos" :items="rightMenu.items"
@close="rightMenu.show=false" @select="onSelectMenu"></right-menu> @close="rightMenu.show = false" @select="onSelectMenu"></right-menu>
<slot></slot> <slot></slot>
</div> </div>
</template> </template>
<script> <script>
import HeadImage from '../common/HeadImage.vue'; import HeadImage from '../common/HeadImage.vue';
import RightMenu from "../common/RightMenu.vue"; import RightMenu from "../common/RightMenu.vue";
export default { export default {
name: "frinedItem", name: "frinedItem",
components: { components: {
HeadImage, HeadImage,
@ -63,8 +63,8 @@
this.$emit(item.key.toLowerCase(), this.msgInfo); this.$emit(item.key.toLowerCase(), this.msgInfo);
} }
}, },
computed:{ computed: {
friend(){ friend() {
return this.$store.state.friendStore.friends[this.index]; return this.$store.state.friendStore.friends[this.index];
} }
}, },
@ -81,11 +81,11 @@
} }
} }
} }
</script> </script>
<style scope lang="scss"> <style scope lang="scss">
.friend-item { .friend-item {
height: 50px; height: 50px;
display: flex; display: flex;
position: relative; position: relative;
@ -129,7 +129,7 @@
position: relative; position: relative;
} }
.online-icon{ .online-icon {
position: absolute; position: absolute;
right: 0; right: 0;
bottom: 0; bottom: 0;
@ -141,5 +141,5 @@
} }
} }
} }
} }
</style> </style>

12
im-web/src/components/group/AddGroupMember.vue

@ -8,10 +8,9 @@
</el-input> </el-input>
</div> </div>
<el-scrollbar style="height:400px;"> <el-scrollbar style="height:400px;">
<div v-for="(friend,index) in friends" :key="friend.id"> <div v-for="(friend, index) in friends" :key="friend.id">
<friend-item v-show="friend.nickName.includes(searchText)" :showDelete="false" <friend-item v-show="friend.nickName.includes(searchText)" :showDelete="false"
@click.native="onSwitchCheck(friend)" :menu="false" :friend="friend" :index="index" @click.native="onSwitchCheck(friend)" :menu="false" :friend="friend" :index="index" :active="false">
:active="false">
<el-checkbox :disabled="friend.disabled" @click.native.stop="" class="agm-friend-checkbox" <el-checkbox :disabled="friend.disabled" @click.native.stop="" class="agm-friend-checkbox"
v-model="friend.isCheck" size="medium"></el-checkbox> v-model="friend.isCheck" size="medium"></el-checkbox>
</friend-item> </friend-item>
@ -22,9 +21,9 @@
<div class="agm-r-box"> <div class="agm-r-box">
<div class="agm-select-tip"> 已勾选{{ checkCount }}位好友</div> <div class="agm-select-tip"> 已勾选{{ checkCount }}位好友</div>
<el-scrollbar style="height:400px;"> <el-scrollbar style="height:400px;">
<div v-for="(friend,index) in friends" :key="friend.id"> <div v-for="(friend, index) in friends" :key="friend.id">
<friend-item v-if="friend.isCheck && !friend.disabled" :friend="friend" :index="index" <friend-item v-if="friend.isCheck && !friend.disabled" :friend="friend" :index="index" :active="false"
:active="false" @del="onRemoveFriend(friend,index)" :menu="false"> @del="onRemoveFriend(friend, index)" :menu="false">
</friend-item> </friend-item>
</div> </div>
</el-scrollbar> </el-scrollbar>
@ -173,7 +172,6 @@ export default {
line-height: 40px; line-height: 40px;
text-indent: 6px; text-indent: 6px;
color: var(--im-text-color-light) color: var(--im-text-color-light)
} }
} }
} }

15
im-web/src/components/group/GroupItem.vue

@ -4,15 +4,15 @@
<head-image :size="42" :name="group.showGroupName" :url="group.headImage"> </head-image> <head-image :size="42" :name="group.showGroupName" :url="group.headImage"> </head-image>
</div> </div>
<div class="group-name"> <div class="group-name">
<div>{{group.showGroupName}}</div> <div>{{ group.showGroupName }}</div>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import HeadImage from '../common/HeadImage.vue'; import HeadImage from '../common/HeadImage.vue';
export default { export default {
name: "groupItem", name: "groupItem",
components: { components: {
HeadImage HeadImage
@ -28,12 +28,11 @@
type: Boolean type: Boolean
} }
} }
}
}
</script> </script>
<style lang="scss" > <style lang="scss">
.group-item { .group-item {
height: 50px; height: 50px;
display: flex; display: flex;
position: relative; position: relative;
@ -59,5 +58,5 @@
overflow: hidden; overflow: hidden;
font-size: var(--im-font-size); font-size: var(--im-font-size);
} }
} }
</style> </style>

40
im-web/src/components/group/GroupMember.vue

@ -1,46 +1,46 @@
<template> <template>
<div class="group-member"> <div class="group-member">
<head-image :id="member.userId" :name="member.showNickName" <head-image :id="member.userId" :name="member.showNickName" :url="member.headImage" :size="38"
:url="member.headImage" :size="38" :online="member.online">
:online="member.online" >
<div v-if="showDel" @click.stop="onDelete()" class="btn-kick el-icon-error"></div> <div v-if="showDel" @click.stop="onDelete()" class="btn-kick el-icon-error"></div>
</head-image> </head-image>
<div class="member-name">{{member.showNickName}}</div> <div class="member-name">{{ member.showNickName }}</div>
</div> </div>
</template> </template>
<script> <script>
import HeadImage from "../common/HeadImage.vue"; import HeadImage from "../common/HeadImage.vue";
export default{ export default {
name: "groupMember", name: "groupMember",
components:{HeadImage}, components: { HeadImage },
data(){ data() {
return {}; return {};
}, },
props:{ props: {
member:{ member: {
type: Object, type: Object,
required: true required: true
}, },
showDel:{ showDel: {
type: Boolean, type: Boolean,
default: false default: false
} }
}, },
methods:{ methods: {
onDelete(){ onDelete() {
this.$emit("del",this.member); this.$emit("del", this.member);
}
} }
} }
}
</script> </script>
<style lang="scss"> <style lang="scss">
.group-member{ .group-member {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
width: 50px; width: 50px;
.member-name { .member-name {
font-size: 12px; font-size: 12px;
text-align: center; text-align: center;
@ -48,8 +48,8 @@
height: 30px; height: 30px;
line-height: 30px; line-height: 30px;
white-space: nowrap; white-space: nowrap;
text-overflow:ellipsis; text-overflow: ellipsis;
overflow:hidden overflow: hidden
} }
.btn-kick { .btn-kick {
@ -62,9 +62,9 @@
cursor: pointer; cursor: pointer;
} }
&:hover .btn-kick{ &:hover .btn-kick {
display: block; display: block;
color: #ce1818; color: #ce1818;
} }
} }
</style> </style>

16
im-web/src/components/group/GroupMemberItem.vue

@ -1,10 +1,10 @@
<template> <template>
<div class="group-member-item" :style="{'height':height+'px'}"> <div class="group-member-item" :style="{ 'height': height + 'px' }">
<div class="member-avatar"> <div class="member-avatar">
<head-image :size="headImageSize" :name="member.showNickName" <head-image :size="headImageSize" :name="member.showNickName" :url="member.headImage"
:url="member.headImage" :online="member.online"> </head-image> :online="member.online"> </head-image>
</div> </div>
<div class="member-name" :style="{'line-height':height+'px'}"> <div class="member-name" :style="{ 'line-height': height + 'px' }">
<div>{{ member.showNickName }}</div> <div>{{ member.showNickName }}</div>
</div> </div>
<slot></slot> <slot></slot>
@ -24,13 +24,13 @@ export default {
type: Object, type: Object,
required: true required: true
}, },
height:{ height: {
type: Number, type: Number,
default: 50 default: 50
} }
}, },
computed:{ computed: {
headImageSize(){ headImageSize() {
return Math.ceil(this.height * 0.75) return Math.ceil(this.height * 0.75)
} }
} }
@ -55,7 +55,7 @@ export default {
} }
.member-name { .member-name {
flex:1; flex: 1;
padding-left: 10px; padding-left: 10px;
height: 100%; height: 100%;
text-align: left; text-align: left;

18
im-web/src/components/group/GroupMemberSelector.vue

@ -7,8 +7,8 @@
</el-input> </el-input>
<el-scrollbar style="height:400px;"> <el-scrollbar style="height:400px;">
<div v-for="m in members" :key="m.userId"> <div v-for="m in members" :key="m.userId">
<group-member-item v-show="!m.quit&&m.showNickName.includes(searchText)" <group-member-item v-show="!m.quit && m.showNickName.includes(searchText)" :member="m"
:member="m" @click.native="onClickMember(m)"> @click.native="onClickMember(m)">
<el-checkbox :disabled="m.locked" v-model="m.checked" @change="onChange(m)" <el-checkbox :disabled="m.locked" v-model="m.checked" @change="onChange(m)"
@click.native.stop=""></el-checkbox> @click.native.stop=""></el-checkbox>
</group-member-item> </group-member-item>
@ -17,7 +17,7 @@
</div> </div>
<div class="arrow el-icon-d-arrow-right"></div> <div class="arrow el-icon-d-arrow-right"></div>
<div class="right-box"> <div class="right-box">
<div class="select-tip"> 已勾选{{checkedMembers.length}}位成员</div> <div class="select-tip"> 已勾选{{ checkedMembers.length }}位成员</div>
<div class="checked-member-list"> <div class="checked-member-list">
<div v-for="m in members" :key="m.userId"> <div v-for="m in members" :key="m.userId">
<group-member class="member-item" v-if="m.checked" :member="m"></group-member> <group-member class="member-item" v-if="m.checked" :member="m"></group-member>
@ -33,10 +33,10 @@
</template> </template>
<script> <script>
import GroupMemberItem from './GroupMemberItem.vue'; import GroupMemberItem from './GroupMemberItem.vue';
import GroupMember from './GroupMember.vue'; import GroupMember from './GroupMember.vue';
export default { export default {
name: "addGroupMember", name: "addGroupMember",
components: { components: {
GroupMemberItem, GroupMemberItem,
@ -109,11 +109,11 @@
} }
} }
} }
</script> </script>
<style lang="scss"> <style lang="scss">
.group-member-selector { .group-member-selector {
display: flex; display: flex;
.left-box { .left-box {
@ -160,5 +160,5 @@
} }
} }
} }
} }
</style> </style>

30
im-web/src/components/rtc/RtcGroupJoin.vue

@ -3,10 +3,10 @@
<div class="rtc-group-join"> <div class="rtc-group-join">
<div class="host-info"> <div class="host-info">
<head-image :name="rtcInfo.host.nickName" :url="rtcInfo.host.headImage" :size="80"></head-image> <head-image :name="rtcInfo.host.nickName" :url="rtcInfo.host.headImage" :size="80"></head-image>
<div class="host-text">{{'发起人:'+rtcInfo.host.nickName}}</div> <div class="host-text">{{ '发起人:' + rtcInfo.host.nickName }}</div>
</div> </div>
<div class="users-info"> <div class="users-info">
<div>{{rtcInfo.userInfos.length+'人正在通话中'}}</div> <div>{{ rtcInfo.userInfos.length + '人正在通话中' }}</div>
<div class="user-list"> <div class="user-list">
<div class="user-item" v-for="user in rtcInfo.userInfos" :key="user.id"> <div class="user-item" v-for="user in rtcInfo.userInfos" :key="user.id">
<head-image :url="user.headImage" :name="user.nickName" :size="40"> <head-image :url="user.headImage" :name="user.nickName" :size="40">
@ -23,19 +23,19 @@
</template> </template>
<script> <script>
import HeadImage from '@/components/common/HeadImage' import HeadImage from '@/components/common/HeadImage'
export default{ export default {
name: "rtcGroupJoin", name: "rtcGroupJoin",
components:{ components: {
HeadImage HeadImage
}, },
data() { data() {
return { return {
isShow: false, isShow: false,
rtcInfo: { rtcInfo: {
host:{}, host: {},
userInfos:[] userInfos: []
} }
} }
}, },
@ -53,7 +53,7 @@
this.isShow = false; this.isShow = false;
let userInfos = this.rtcInfo.userInfos; let userInfos = this.rtcInfo.userInfos;
let mine = this.$store.state.userStore.userInfo; let mine = this.$store.state.userStore.userInfo;
if(!userInfos.find((user)=>user.id==mine.id)){ if (!userInfos.find((user) => user.id == mine.id)) {
// //
userInfos.push({ userInfos.push({
id: mine.id, id: mine.id,
@ -73,17 +73,18 @@
this.$eventBus.$emit("openGroupVideo", rtcInfo); this.$eventBus.$emit("openGroupVideo", rtcInfo);
}, },
onCancel(){ onCancel() {
this.isShow = false; this.isShow = false;
} }
} }
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.rtc-group-join { .rtc-group-join {
height: 260px; height: 260px;
padding: 10px; padding: 10px;
.host-info { .host-info {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -92,7 +93,7 @@
height: 100px; height: 100px;
align-items: center; align-items: center;
.host-text{ .host-text {
margin-top: 5px; margin-top: 5px;
} }
} }
@ -100,6 +101,7 @@
.users-info { .users-info {
font-size: 16px; font-size: 16px;
margin-top: 20px; margin-top: 20px;
.user-list { .user-list {
display: flex; display: flex;
padding: 5px 5px; padding: 5px 5px;
@ -107,10 +109,10 @@
flex-wrap: wrap; flex-wrap: wrap;
justify-content: center; justify-content: center;
.user-item{ .user-item {
padding: 2px; padding: 2px;
} }
} }
} }
} }
</style> </style>

10
im-web/src/components/rtc/RtcGroupVideo.vue

@ -19,7 +19,7 @@
</template> </template>
<script> <script>
export default { export default {
name: "rtcGroupVideo", name: "rtcGroupVideo",
data() { data() {
return { return {
@ -30,16 +30,16 @@
open() { open() {
this.isShow = true; this.isShow = true;
}, },
onRTCMessage(){ onRTCMessage() {
//this.isShow = true; //this.isShow = true;
} }
} }
} }
</script> </script>
<style lang="scss"> <style lang="scss">
.rtc-group-video { .rtc-group-video {
height: 300px; height: 300px;
background-color: #E8F2FF; background-color: #E8F2FF;
} }
</style> </style>

19
im-web/src/components/rtc/RtcPrivateAcceptor.vue

@ -1,8 +1,9 @@
<template> <template>
<div class="rtc-private-acceptor"> <div class="rtc-private-acceptor">
<head-image :id="friend.id" :name="friend.nickName" :url="friend.headImage" :size="100" :isShowUserInfo="false"></head-image> <head-image :id="friend.id" :name="friend.nickName" :url="friend.headImage" :size="100"
:isShowUserInfo="false"></head-image>
<div class="acceptor-text"> <div class="acceptor-text">
{{tip}} {{ tip }}
</div> </div>
<div class="acceptor-btn-group"> <div class="acceptor-btn-group">
<div class="icon iconfont icon-phone-accept accept" @click="$emit('accept')" title="接受"></div> <div class="icon iconfont icon-phone-accept accept" @click="$emit('accept')" title="接受"></div>
@ -12,9 +13,9 @@
</template> </template>
<script> <script>
import HeadImage from '../common/HeadImage.vue'; import HeadImage from '../common/HeadImage.vue';
export default { export default {
name: "rtcPrivateAcceptor", name: "rtcPrivateAcceptor",
components: { components: {
HeadImage HeadImage
@ -23,10 +24,10 @@
return {} return {}
}, },
props: { props: {
mode:{ mode: {
type: String type: String
}, },
friend:{ friend: {
type: Object type: Object
} }
}, },
@ -36,11 +37,11 @@
return `${this.friend.nickName} 请求和您进行${modeText}通话...` return `${this.friend.nickName} 请求和您进行${modeText}通话...`
} }
} }
} }
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.rtc-private-acceptor { .rtc-private-acceptor {
position: absolute; position: absolute;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -122,5 +123,5 @@
} }
} }
} }
} }
</style> </style>

16
im-web/src/components/rtc/RtcPrivateVideo.vue

@ -1,15 +1,7 @@
<template> <template>
<div> <div>
<el-dialog <el-dialog v-dialogDrag top="5vh" custom-class="rtc-private-video-dialog" :title="title" :width="width"
v-dialogDrag :visible.sync="showRoom" :close-on-click-modal="false" :close-on-press-escape="false" :before-close="onQuit">
top="5vh"
custom-class="rtc-private-video-dialog"
:title="title"
:width="width"
:visible.sync="showRoom"
:close-on-click-modal="false"
:close-on-press-escape="false"
:before-close="onQuit">
<div class="rtc-private-video"> <div class="rtc-private-video">
<div v-show="isVideo" class="rtc-video-box"> <div v-show="isVideo" class="rtc-video-box">
<div class="rtc-video-friend" v-loading="!isChating" element-loading-text="等待对方接听..." <div class="rtc-video-friend" v-loading="!isChating" element-loading-text="等待对方接听..."
@ -31,8 +23,7 @@
</head-image> </head-image>
</div> </div>
<div class="rtc-control-bar"> <div class="rtc-control-bar">
<div title="取消" class="icon iconfont icon-phone-reject reject" <div title="取消" class="icon iconfont icon-phone-reject reject" style="color: red;" @click="onQuit()"></div>
style="color: red;" @click="onQuit()"></div>
</div> </div>
</div> </div>
</el-dialog> </el-dialog>
@ -504,5 +495,4 @@ export default {
} }
} }
} }
</style> </style>

8
im-web/src/components/setting/Setting.vue

@ -2,12 +2,8 @@
<el-dialog class="setting" title="设置" :visible.sync="visible" width="420px" :before-close="onClose"> <el-dialog class="setting" title="设置" :visible.sync="visible" width="420px" :before-close="onClose">
<el-form :model="userInfo" label-width="80px" :rules="rules" ref="settingForm" size="small"> <el-form :model="userInfo" label-width="80px" :rules="rules" ref="settingForm" size="small">
<el-form-item label="头像" style="margin-bottom: 0 !important;"> <el-form-item label="头像" style="margin-bottom: 0 !important;">
<file-upload class="avatar-uploader" <file-upload class="avatar-uploader" :action="imageAction" :showLoading="true" :maxSize="maxSize"
:action="imageAction" @success="onUploadSuccess" :fileTypes="['image/jpeg', 'image/png', 'image/jpg', 'image/webp']">
:showLoading="true"
:maxSize="maxSize"
@success="onUploadSuccess"
:fileTypes="['image/jpeg', 'image/png', 'image/jpg','image/webp']">
<img v-if="userInfo.headImage" :src="userInfo.headImage" class="avatar"> <img v-if="userInfo.headImage" :src="userInfo.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>
</file-upload> </file-upload>

2
im-web/src/main.js

@ -32,6 +32,6 @@ new Vue({
// 配置路由 // 配置路由
router, router,
store, store,
render: h=>h(App) render: h => h(App)
}) })

2
im-web/src/router/index.js

@ -26,7 +26,7 @@ export default new VueRouter({
name: "Home", name: "Home",
path: '/home', path: '/home',
component: Home, component: Home,
children:[ children: [
{ {
name: "Chat", name: "Chat",
path: "/home/chat", path: "/home/chat",

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

@ -280,7 +280,7 @@ export default {
} }
}, },
refreshChats(state) { refreshChats(state) {
if(!cacheChats){ if (!cacheChats) {
return; return;
} }
// 排序 // 排序
@ -369,7 +369,7 @@ export default {
return state.loadingPrivateMsg || state.loadingGroupMsg return state.loadingPrivateMsg || state.loadingGroupMsg
}, },
findChats: (state, getters) => () => { findChats: (state, getters) => () => {
if(cacheChats && getters.isLoading()){ if (cacheChats && getters.isLoading()) {
return cacheChats; return cacheChats;
} }
return state.chats; return state.chats;

12
im-web/src/store/configStore.js

@ -8,21 +8,21 @@ export default {
setConfig(state, config) { setConfig(state, config) {
state.webrtc = config.webrtc; state.webrtc = config.webrtc;
}, },
clear(state){ clear(state) {
state.webrtc = {}; state.webrtc = {};
} }
}, },
actions:{ actions: {
loadConfig(context){ loadConfig(context) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
http({ http({
url: '/system/config', url: '/system/config',
method: 'GET' method: 'GET'
}).then((config) => { }).then((config) => {
console.log("系统配置",config) console.log("系统配置", config)
context.commit("setConfig",config); context.commit("setConfig", config);
resolve(); resolve();
}).catch((res)=>{ }).catch((res) => {
reject(res); reject(res);
}); });
}) })

46
im-web/src/store/friendStore.js

@ -1,5 +1,5 @@
import http from '../api/httpRequest.js' import http from '../api/httpRequest.js'
import {TERMINAL_TYPE} from "../api/enums.js" import { TERMINAL_TYPE } from "../api/enums.js"
export default { export default {
@ -10,20 +10,20 @@ export default {
}, },
mutations: { mutations: {
setFriends(state, friends) { setFriends(state, friends) {
friends.forEach((f)=>{ friends.forEach((f) => {
f.online = false; f.online = false;
f.onlineWeb = false; f.onlineWeb = false;
f.onlineApp = false; f.onlineApp = false;
}) })
state.friends = friends; state.friends = friends;
}, },
updateFriend(state,friend){ updateFriend(state, friend) {
state.friends.forEach((f,index)=>{ state.friends.forEach((f, index) => {
if(f.id==friend.id){ if (f.id == friend.id) {
// 拷贝属性 // 拷贝属性
let online = state.friends[index].online; let online = state.friends[index].online;
Object.assign(state.friends[index], friend); Object.assign(state.friends[index], friend);
state.friends[index].online =online; state.friends[index].online = online;
} }
}) })
}, },
@ -39,45 +39,45 @@ export default {
addFriend(state, friend) { addFriend(state, friend) {
state.friends.push(friend); state.friends.push(friend);
}, },
refreshOnlineStatus(state){ refreshOnlineStatus(state) {
let userIds = []; let userIds = [];
if(state.friends.length ==0){ if (state.friends.length == 0) {
return; return;
} }
state.friends.forEach((f)=>{userIds.push(f.id)}); state.friends.forEach((f) => { userIds.push(f.id) });
http({ http({
url: '/user/terminal/online', url: '/user/terminal/online',
method: 'get', method: 'get',
params: {userIds: userIds.join(',')} params: { userIds: userIds.join(',') }
}).then((onlineTerminals) => { }).then((onlineTerminals) => {
this.commit("setOnlineStatus",onlineTerminals); this.commit("setOnlineStatus", onlineTerminals);
}) })
// 30s后重新拉取 // 30s后重新拉取
state.timer && clearTimeout(state.timer); state.timer && clearTimeout(state.timer);
state.timer = setTimeout(()=>{ state.timer = setTimeout(() => {
this.commit("refreshOnlineStatus"); this.commit("refreshOnlineStatus");
},30000) }, 30000)
}, },
setOnlineStatus(state,onlineTerminals){ setOnlineStatus(state, onlineTerminals) {
state.friends.forEach((f)=>{ state.friends.forEach((f) => {
let userTerminal = onlineTerminals.find((o)=> f.id==o.userId); let userTerminal = onlineTerminals.find((o) => f.id == o.userId);
if(userTerminal){ if (userTerminal) {
f.online = true; f.online = true;
f.onlineWeb = userTerminal.terminals.indexOf(TERMINAL_TYPE.WEB)>=0 f.onlineWeb = userTerminal.terminals.indexOf(TERMINAL_TYPE.WEB) >= 0
f.onlineApp = userTerminal.terminals.indexOf(TERMINAL_TYPE.APP)>=0 f.onlineApp = userTerminal.terminals.indexOf(TERMINAL_TYPE.APP) >= 0
}else{ } else {
f.online = false; f.online = false;
f.onlineWeb = false; f.onlineWeb = false;
f.onlineApp = false; f.onlineApp = false;
} }
}); });
// 在线的在前面 // 在线的在前面
state.friends.sort((f1,f2)=>{ state.friends.sort((f1, f2) => {
if(f1.online&&!f2.online){ if (f1.online && !f2.online) {
return -1; return -1;
} }
if(f2.online&&!f1.online){ if (f2.online && !f1.online) {
return 1; return 1;
} }
return 0; return 0;

4
im-web/src/store/index.js

@ -10,7 +10,7 @@ import uiStore from './uiStore.js';
Vue.use(Vuex) Vue.use(Vuex)
export default new Vuex.Store({ export default new Vuex.Store({
modules: {chatStore,friendStore,userStore,groupStore,configStore,uiStore}, modules: { chatStore, friendStore, userStore, groupStore, configStore, uiStore },
state: {}, state: {},
mutations: { mutations: {
}, },
@ -25,7 +25,7 @@ export default new Vuex.Store({
return Promise.all(promises); return Promise.all(promises);
}) })
}, },
unload(context){ unload(context) {
context.commit("clear"); context.commit("clear");
} }
}, },

20
im-web/src/store/uiStore.js

@ -3,9 +3,9 @@ export default {
userInfo: { // 用户信息窗口 userInfo: { // 用户信息窗口
show: false, show: false,
user: {}, user: {},
pos:{ pos: {
x:0, x: 0,
y:0 y: 0
} }
}, },
fullImage: { // 全屏大图 fullImage: { // 全屏大图
@ -14,25 +14,25 @@ export default {
} }
}, },
mutations: { mutations: {
showUserInfoBox(state,user){ showUserInfoBox(state, user) {
state.userInfo.show = true; state.userInfo.show = true;
state.userInfo.user = user; state.userInfo.user = user;
}, },
setUserInfoBoxPos(state,pos){ setUserInfoBoxPos(state, pos) {
let w = document.documentElement.clientWidth; let w = document.documentElement.clientWidth;
let h = document.documentElement.clientHeight; let h = document.documentElement.clientHeight;
state.userInfo.pos.x = Math.min(pos.x,w-350); state.userInfo.pos.x = Math.min(pos.x, w - 350);
state.userInfo.pos.y = Math.min(pos.y,h-200); state.userInfo.pos.y = Math.min(pos.y, h - 200);
}, },
closeUserInfoBox(state){ closeUserInfoBox(state) {
state.userInfo.show = false; state.userInfo.show = false;
}, },
showFullImageBox(state,url){ showFullImageBox(state, url) {
state.fullImage.show = true; state.fullImage.show = true;
state.fullImage.url = url; state.fullImage.url = url;
}, },
closeFullImageBox(state){ closeFullImageBox(state) {
state.fullImage.show = false; state.fullImage.show = false;
} }
} }

16
im-web/src/store/userStore.js

@ -1,5 +1,5 @@
import http from '../api/httpRequest.js' import http from '../api/httpRequest.js'
import {RTC_STATE} from "../api/enums.js" import { RTC_STATE } from "../api/enums.js"
export default { export default {
state: { state: {
@ -17,13 +17,13 @@ export default {
setUserInfo(state, userInfo) { setUserInfo(state, userInfo) {
state.userInfo = userInfo state.userInfo = userInfo
}, },
setRtcInfo(state, rtcInfo ){ setRtcInfo(state, rtcInfo) {
state.rtcInfo = rtcInfo; state.rtcInfo = rtcInfo;
}, },
setRtcState(state,rtcState){ setRtcState(state, rtcState) {
state.rtcInfo.state = rtcState; state.rtcInfo.state = rtcState;
}, },
clear(state){ clear(state) {
state.userInfo = {}; state.userInfo = {};
state.rtcInfo = { state.rtcInfo = {
friend: {}, friend: {},
@ -32,16 +32,16 @@ export default {
}; };
} }
}, },
actions:{ actions: {
loadUser(context){ loadUser(context) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
http({ http({
url: '/user/self', url: '/user/self',
method: 'GET' method: 'GET'
}).then((userInfo) => { }).then((userInfo) => {
context.commit("setUserInfo",userInfo); context.commit("setUserInfo", userInfo);
resolve(); resolve();
}).catch((res)=>{ }).catch((res) => {
reject(res); reject(res);
}); });
}) })

136
im-web/src/utils/directive/dialogDrag.js

@ -1,72 +1,72 @@
import Vue from 'vue' import Vue from 'vue'
 
// v-dialogDrag: 弹窗拖拽 // v-dialogDrag: 弹窗拖拽
Vue.directive('dialogDrag', { Vue.directive('dialogDrag', {
  bind (el, binding, vnode, oldVnode) { bind(el, binding, vnode, oldVnode) {
    const dialogHeaderEl = el.querySelector('.el-dialog__header') const dialogHeaderEl = el.querySelector('.el-dialog__header')
    const dragDom = el.querySelector('.el-dialog') const dragDom = el.querySelector('.el-dialog')
    dialogHeaderEl.style.cursor = 'move' dialogHeaderEl.style.cursor = 'move'
 
    // 获取原有属性 ie dom元素.currentStyle 火狐谷歌 window.getComputedStyle(dom元素, null); // 获取原有属性 ie dom元素.currentStyle 火狐谷歌 window.getComputedStyle(dom元素, null);
    const sty = dragDom.currentStyle || window.getComputedStyle(dragDom, null) const sty = dragDom.currentStyle || window.getComputedStyle(dragDom, null)
 
    dialogHeaderEl.onmousedown = (e) => { dialogHeaderEl.onmousedown = (e) => {
      // 鼠标按下,计算当前元素距离可视区的距离 // 鼠标按下,计算当前元素距离可视区的距离
      const disX = e.clientX - dialogHeaderEl.offsetLeft const disX = e.clientX - dialogHeaderEl.offsetLeft
      const disY = e.clientY - dialogHeaderEl.offsetTop const disY = e.clientY - dialogHeaderEl.offsetTop
      const screenWidth = document.body.clientWidth; // body当前宽度 const screenWidth = document.body.clientWidth; // body当前宽度
      const screenHeight = document.documentElement.clientHeight; // 可见区域高度(应为body高度,可某些环境下无法获取) const screenHeight = document.documentElement.clientHeight; // 可见区域高度(应为body高度,可某些环境下无法获取)
      const dragDomWidth = dragDom.offsetWidth; // 对话框宽度 const dragDomWidth = dragDom.offsetWidth; // 对话框宽度
      const dragDomheight = dragDom.offsetHeight; // 对话框高度 const dragDomheight = dragDom.offsetHeight; // 对话框高度
      const minDragDomLeft = dragDom.offsetLeft; const minDragDomLeft = dragDom.offsetLeft;
      const maxDragDomLeft = screenWidth - dragDom.offsetLeft - dragDomWidth; const maxDragDomLeft = screenWidth - dragDom.offsetLeft - dragDomWidth;
      const minDragDomTop = dragDom.offsetTop; const minDragDomTop = dragDom.offsetTop;
      const maxDragDomTop = screenHeight - dragDom.offsetTop - dragDomheight; const maxDragDomTop = screenHeight - dragDom.offsetTop - dragDomheight;
 
      // 获取到的值带px 正则匹配替换 // 获取到的值带px 正则匹配替换
      let styL, styT let styL, styT
 
      // 注意在ie中 第一次获取到的值为组件自带50% 移动之后赋值为px // 注意在ie中 第一次获取到的值为组件自带50% 移动之后赋值为px
      if (sty.left.includes('%')) { if (sty.left.includes('%')) {
        styL = +document.body.clientWidth * (+sty.left.replace(/\%/g, '') / 100) styL = +document.body.clientWidth * (+sty.left.replace(/\%/g, '') / 100)
        styT = +document.body.clientHeight * (+sty.top.replace(/\%/g, '') / 100) styT = +document.body.clientHeight * (+sty.top.replace(/\%/g, '') / 100)
      } else { } else {
        styL = +sty.left.replace(/\px/g, '') styL = +sty.left.replace(/\px/g, '')
        styT = +sty.top.replace(/\px/g, '') styT = +sty.top.replace(/\px/g, '')
      } }
 
      document.onmousemove = function (e) { document.onmousemove = function (e) {
        // 获取body的页面可视宽高 // 获取body的页面可视宽高
        // var clientHeight = document.documentElement.clientHeight || document.body.clientHeight // var clientHeight = document.documentElement.clientHeight || document.body.clientHeight
        // var clientWidth = document.documentElement.clientWidth || document.body.clientWidth // var clientWidth = document.documentElement.clientWidth || document.body.clientWidth
 
        // 通过事件委托,计算移动的距离 // 通过事件委托,计算移动的距离
        var l = e.clientX - disX var l = e.clientX - disX
        var t = e.clientY - disY var t = e.clientY - disY
 
        // 边界处理 // 边界处理
        if (-l > minDragDomLeft) { if (-l > minDragDomLeft) {
          l = -minDragDomLeft; l = -minDragDomLeft;
        } else if (l > maxDragDomLeft) { } else if (l > maxDragDomLeft) {
          l = maxDragDomLeft; l = maxDragDomLeft;
        } }
        if (-t > minDragDomTop) { if (-t > minDragDomTop) {
          t = -minDragDomTop; t = -minDragDomTop;
        } else if (t > maxDragDomTop) { } else if (t > maxDragDomTop) {
          t = maxDragDomTop; t = maxDragDomTop;
        } }
        // 移动当前元素 // 移动当前元素
        dragDom.style.left = `${l + styL}px` dragDom.style.left = `${l + styL}px`
        dragDom.style.top = `${t + styT}px` dragDom.style.top = `${t + styT}px`
 
        // 将此时的位置传出去 // 将此时的位置传出去
        // binding.value({x:e.pageX,y:e.pageY}) // binding.value({x:e.pageX,y:e.pageY})
      } }
 
      document.onmouseup = function (e) { document.onmouseup = function (e) {
        document.onmousemove = null document.onmousemove = null
        document.onmouseup = null document.onmouseup = null
      } }
    } }
  } }
}) })

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

@ -10,8 +10,8 @@
element-loading-spinner="el-icon-loading" element-loading-background="#F9F9F9" element-loading-size="24"> element-loading-spinner="el-icon-loading" element-loading-background="#F9F9F9" element-loading-size="24">
</div> </div>
<el-scrollbar class="chat-list-items" v-else> <el-scrollbar class="chat-list-items" v-else>
<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.delete&&chat.showName.includes(searchText)" :chat="chat" :index="index" <chat-item v-show="!chat.delete && chat.showName.includes(searchText)" :chat="chat" :index="index"
@click.native="onActiveItem(index)" @delete="onDelItem(index)" @top="onTop(index)" @click.native="onActiveItem(index)" @delete="onDelItem(index)" @top="onTop(index)"
:active="chat === chatStore.activeChat"></chat-item> :active="chat === chatStore.activeChat"></chat-item>
</div> </div>

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

@ -10,17 +10,17 @@
<add-friend :dialogVisible="showAddFriend" @close="onCloseAddFriend"></add-friend> <add-friend :dialogVisible="showAddFriend" @close="onCloseAddFriend"></add-friend>
</div> </div>
<el-scrollbar class="friend-list-items"> <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.includes(searchText)" :index="index" <friend-item v-show="friend.nickName.includes(searchText)" :index="index"
:active="friend === $store.state.friendStore.activeFriend" @chat="onSendMessage(friend)" :active="friend === $store.state.friendStore.activeFriend" @chat="onSendMessage(friend)"
@delete="onDelItem(friend,index)" @click.native="onActiveItem(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="friend-box"> <el-container class="friend-box">
<div class="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="friend-detail"> <div class="friend-detail">
@ -33,7 +33,7 @@
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="昵称">{{ userInfo.nickName }} <el-descriptions-item label="昵称">{{ userInfo.nickName }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="性别">{{ userInfo.sex==0?"男":"女" }}</el-descriptions-item> <el-descriptions-item label="性别">{{ userInfo.sex == 0 ? "男" : "女" }}</el-descriptions-item>
<el-descriptions-item label="签名">{{ userInfo.signature }}</el-descriptions-item> <el-descriptions-item label="签名">{{ userInfo.signature }}</el-descriptions-item>
</el-descriptions> </el-descriptions>
</div> </div>
@ -43,7 +43,7 @@
<el-button v-show="!isFriend" icon="el-icon-plus" type="primary" <el-button v-show="!isFriend" icon="el-icon-plus" type="primary"
@click="onAddFriend(userInfo)">加为好友</el-button> @click="onAddFriend(userInfo)">加为好友</el-button>
<el-button v-show="isFriend" icon="el-icon-delete" type="danger" <el-button v-show="isFriend" icon="el-icon-delete" type="danger"
@click="onDelItem(userInfo,activeIdx)">删除好友</el-button> @click="onDelItem(userInfo, activeIdx)">删除好友</el-button>
</div> </div>
</div> </div>
</div> </div>
@ -55,11 +55,11 @@
</template> </template>
<script> <script>
import FriendItem from "../components/friend/FriendItem.vue"; import FriendItem from "../components/friend/FriendItem.vue";
import AddFriend from "../components/friend/AddFriend.vue"; import AddFriend from "../components/friend/AddFriend.vue";
import HeadImage from "../components/common/HeadImage.vue"; import HeadImage from "../components/common/HeadImage.vue";
export default { export default {
name: "friend", name: "friend",
components: { components: {
FriendItem, FriendItem,
@ -173,11 +173,11 @@
return this.friendStore.friends.find((f) => f.id == this.userInfo.id); return this.friendStore.friends.find((f) => f.id == this.userInfo.id);
} }
} }
} }
</script> </script>
<style lang="scss"> <style lang="scss">
.friend-page { .friend-page {
.friend-list-box { .friend-list-box {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -238,5 +238,5 @@
padding: 20px; padding: 20px;
} }
} }
} }
</style> </style>

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

@ -8,9 +8,9 @@
<el-button plain class="add-btn" icon="el-icon-plus" title="创建群聊" @click="onCreateGroup()"></el-button> <el-button plain class="add-btn" icon="el-icon-plus" title="创建群聊" @click="onCreateGroup()"></el-button>
</div> </div>
<el-scrollbar class="group-list-items"> <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.quit&&group.showGroupName.includes(searchText)" :group="group" <group-item v-show="!group.quit && group.showGroupName.includes(searchText)" :group="group"
:active="group === groupStore.activeGroup" @click.native="onActiveItem(group,index)"> :active="group === groupStore.activeGroup" @click.native="onActiveItem(group, index)">
</group-item> </group-item>
</div> </div>
</el-scrollbar> </el-scrollbar>
@ -23,17 +23,16 @@
<div v-show="activeGroup.id"> <div v-show="activeGroup.id">
<div class="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"
:showLoading="true" :maxSize="maxSize" @success="onUploadSuccess" :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>
</file-upload> </file-upload>
<head-image v-show="!isOwner" class="avatar" :size="160" :url="activeGroup.headImage" <head-image v-show="!isOwner" class="avatar" :size="160" :url="activeGroup.headImage"
:name="activeGroup.showGroupName" radius="10%"> :name="activeGroup.showGroupName" radius="10%">
</head-image> </head-image>
<el-button class="send-btn" icon="el-icon-position" type="primary" <el-button class="send-btn" icon="el-icon-position" type="primary" @click="onSendMessage()">发消息
@click="onSendMessage()">发消息
</el-button> </el-button>
</div> </div>
<el-form class="group-form" label-width="130px" :model="activeGroup" :rules="rules" size="small" <el-form class="group-form" label-width="130px" :model="activeGroup" :rules="rules" size="small"
@ -53,8 +52,8 @@
:placeholder="$store.state.userStore.userInfo.nickName"></el-input> :placeholder="$store.state.userStore.userInfo.nickName"></el-input>
</el-form-item> </el-form-item>
<el-form-item label="群公告"> <el-form-item label="群公告">
<el-input v-model="activeGroup.notice" :disabled="!isOwner" type="textarea" :rows="3" <el-input v-model="activeGroup.notice" :disabled="!isOwner" type="textarea" :rows="3" maxlength="1024"
maxlength="1024" placeholder="群主未设置"></el-input> placeholder="群主未设置"></el-input>
</el-form-item> </el-form-item>
<div> <div>
<el-button type="warning" v-show="isOwner" @click="onInviteMember()">邀请</el-button> <el-button type="warning" v-show="isOwner" @click="onInviteMember()">邀请</el-button>
@ -68,16 +67,15 @@
<div class="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="group-member" :member="member" <group-member v-show="!member.quit" class="group-member" :member="member"
:showDel="isOwner && member.userId!=activeGroup.ownerId" @del="onKick"></group-member> :showDel="isOwner && member.userId != activeGroup.ownerId" @del="onKick"></group-member>
</div> </div>
<div class="group-invite"> <div class="group-invite">
<div class="invite-member-btn" title="邀请好友进群聊" @click="onInviteMember()"> <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"
:members="groupMembers" @reload="loadGroupMembers" @reload="loadGroupMembers" @close="onCloseAddGroupMember"></add-group-member>
@close="onCloseAddGroupMember"></add-group-member>
</div> </div>
</div> </div>
</div> </div>

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

@ -1,14 +1,12 @@
<template> <template>
<div class="home-page"> <div class="home-page">
<div class="app-container" :class="{fullscreen: isFullscreen}"> <div class="app-container" :class="{ fullscreen: isFullscreen }">
<div class="navi-bar"> <div class="navi-bar">
<div class="navi-bar-box"> <div class="navi-bar-box">
<div class="top"> <div class="top">
<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" :size="38"
:size="38" :url="$store.state.userStore.userInfo.headImageThumb" @click.native="showSettingDialog = true">
:url="$store.state.userStore.userInfo.headImageThumb"
@click.native="showSettingDialog = true">
</head-image> </head-image>
</div> </div>
@ -509,6 +507,4 @@ export default {
text-align: center; text-align: center;
} }
} }
</style> </style>

13
im-web/src/view/Login.vue

@ -4,7 +4,7 @@
<el-form class="login-form" :model="loginForm" status-icon :rules="rules" ref="loginForm" label-width="60px" <el-form class="login-form" :model="loginForm" status-icon :rules="rules" ref="loginForm" label-width="60px"
@keyup.enter.native="submitForm('loginForm')"> @keyup.enter.native="submitForm('loginForm')">
<div class="login-brand"> <div class="login-brand">
<img class="logo" src="../../public/logo.png"/> <img class="logo" src="../../public/logo.png" />
<div>登陆盒子IM</div> <div>登陆盒子IM</div>
</div> </div>
<el-form-item label="终端" prop="userName" v-show="false"> <el-form-item label="终端" prop="userName" v-show="false">
@ -13,7 +13,6 @@
<el-form-item label="用户名" prop="userName"> <el-form-item label="用户名" prop="userName">
<el-input type="userName" v-model="loginForm.userName" autocomplete="off" <el-input type="userName" v-model="loginForm.userName" autocomplete="off"
placeholder="用户名"></el-input> placeholder="用户名"></el-input>
</el-form-item> </el-form-item>
<el-form-item label="密码" prop="password"> <el-form-item label="密码" prop="password">
<el-input type="password" v-model="loginForm.password" autocomplete="off" <el-input type="password" v-model="loginForm.password" autocomplete="off"
@ -34,8 +33,8 @@
</template> </template>
<script> <script>
import Icp from '../components/common/Icp.vue' import Icp from '../components/common/Icp.vue'
export default { export default {
name: "login", name: "login",
components: { components: {
Icp Icp
@ -116,11 +115,11 @@
// cookie便 // cookie便
this.loginForm.password = this.getCookie("password"); this.loginForm.password = this.getCookie("password");
} }
} }
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.login-view { .login-view {
width: 100%; width: 100%;
height: 100%; height: 100%;
background: #E8F2FF; background: #E8F2FF;
@ -174,5 +173,5 @@
} }
} }
} }
} }
</style> </style>

31
im-web/src/view/Register.vue

@ -1,22 +1,27 @@
<template> <template>
<el-container class="register-view"> <el-container class="register-view">
<div> <div>
<el-form :model="registerForm" status-icon :rules="rules" ref="registerForm" label-width="80px" class="web-ruleForm"> <el-form :model="registerForm" status-icon :rules="rules" ref="registerForm" label-width="80px"
class="web-ruleForm">
<div class="register-brand"> <div class="register-brand">
<img class="logo" src="../../public/logo.png"/> <img class="logo" src="../../public/logo.png" />
<div>欢迎成为盒子IM的用户</div> <div>欢迎成为盒子IM的用户</div>
</div> </div>
<el-form-item label="用户名" prop="userName"> <el-form-item label="用户名" prop="userName">
<el-input type="userName" v-model="registerForm.userName" autocomplete="off" placeholder="用户名(登录使用)"></el-input> <el-input type="userName" v-model="registerForm.userName" autocomplete="off"
placeholder="用户名(登录使用)"></el-input>
</el-form-item> </el-form-item>
<el-form-item label="昵称" prop="nickName"> <el-form-item label="昵称" prop="nickName">
<el-input type="nickName" v-model="registerForm.nickName" autocomplete="off" placeholder="昵称"></el-input> <el-input type="nickName" v-model="registerForm.nickName" autocomplete="off"
placeholder="昵称"></el-input>
</el-form-item> </el-form-item>
<el-form-item label="密码" prop="password"> <el-form-item label="密码" prop="password">
<el-input type="password" v-model="registerForm.password" autocomplete="off" placeholder="密码"></el-input> <el-input type="password" v-model="registerForm.password" autocomplete="off"
placeholder="密码"></el-input>
</el-form-item> </el-form-item>
<el-form-item label="确认密码" prop="confirmPassword"> <el-form-item label="确认密码" prop="confirmPassword">
<el-input type="password" v-model="registerForm.confirmPassword" autocomplete="off" placeholder="确认密码"></el-input> <el-input type="password" v-model="registerForm.confirmPassword" autocomplete="off"
placeholder="确认密码"></el-input>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button type="primary" @click="submitForm('registerForm')">注册</el-button> <el-button type="primary" @click="submitForm('registerForm')">注册</el-button>
@ -32,8 +37,8 @@
</template> </template>
<script> <script>
import Icp from '../components/common/Icp.vue' import Icp from '../components/common/Icp.vue'
export default { export default {
name: "login", name: "login",
components: { components: {
Icp Icp
@ -115,11 +120,11 @@
this.$refs[formName].resetFields(); this.$refs[formName].resetFields();
} }
} }
} }
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.register-view { .register-view {
position: fixed; position: fixed;
display: flex; display: flex;
justify-content: space-around; justify-content: space-around;
@ -166,9 +171,5 @@
padding-left: 20px; padding-left: 20px;
} }
} }
} }
</style> </style>

BIN
截图/交流群2.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 KiB

Loading…
Cancel
Save