Browse Source

!130 优化

Merge pull request !130 from blue/v_3.0.0
master
blue 1 year ago
committed by Gitee
parent
commit
677ab0fee5
No known key found for this signature in database GPG Key ID: 173E9B9CA92EEF8F
  1. 6
      im-platform/src/main/java/com/bx/implatform/vo/GroupVO.java
  2. 5
      im-platform/src/main/java/com/bx/implatform/vo/UserVO.java
  3. 18
      im-uniapp/App.vue
  4. 8
      im-uniapp/common/recorder-app.js
  5. 52
      im-uniapp/common/recorder-h5.js
  6. 12
      im-uniapp/components/chat-group-readed/chat-group-readed.vue
  7. 16
      im-uniapp/components/chat-item/chat-item.vue
  8. 25
      im-uniapp/components/chat-message-item/chat-message-item.vue
  9. 23
      im-uniapp/components/chat-record/chat-record.vue
  10. 272
      im-uniapp/pages/chat/chat-box.vue
  11. 27
      im-uniapp/pages/chat/chat.vue
  12. 35
      im-uniapp/store/chatStore.js
  13. 75
      im-web/src/components/chat/ChatBox.vue
  14. 2
      im-web/src/components/chat/ChatInput.vue
  15. 35
      im-web/src/store/chatStore.js
  16. 5
      im-web/src/view/Home.vue

6
im-platform/src/main/java/com/bx/implatform/vo/GroupVO.java

@ -50,5 +50,11 @@ public class GroupVO {
@Schema(description = "是否已退出")
private Boolean quit;
@Schema(description = "账号是否被封禁")
private Boolean isBanned;
@Schema(description = "被封禁原因")
private String reason;
}

5
im-platform/src/main/java/com/bx/implatform/vo/UserVO.java

@ -43,4 +43,9 @@ public class UserVO {
@Schema(description = "是否在线")
private Boolean online;
@Schema(description = "账号是否被封禁")
private Boolean isBanned;
@Schema(description = "被封禁原因")
private String reason;
}

18
im-uniapp/App.vue

@ -9,6 +9,7 @@ import UNI_APP from '@/.env.js'
export default {
data() {
return {
isInit: false, //
isExit: false, // 退
audioTip: null,
reconnecting: false //
@ -21,6 +22,7 @@ export default {
this.loadStore().then(() => {
// websocket
this.initWebSocket();
this.isInit = true;
}).catch((e) => {
console.log(e);
this.exit();
@ -169,7 +171,7 @@ export default {
//
this.chatStore.openChat(chatInfo);
//
this.chatStore.insertMessage(msg);
this.chatStore.insertMessage(msg, chatInfo);
//
this.playAudioTip();
@ -192,6 +194,10 @@ export default {
}
//
if (msg.type == enums.MESSAGE_TYPE.RECEIPT) {
let chatInfo = {
type: 'GROUP',
targetId: msg.groupId
}
//
let msgInfo = {
id: msg.id,
@ -199,7 +205,7 @@ export default {
readedCount: msg.readedCount,
receiptOk: msg.receiptOk
};
this.chatStore.updateMessage(msgInfo)
this.chatStore.updateMessage(msgInfo,chatInfo)
return;
}
//
@ -259,7 +265,7 @@ export default {
//
this.chatStore.openChat(chatInfo);
//
this.chatStore.insertMessage(msg);
this.chatStore.insertMessage(msg, chatInfo);
//
this.playAudioTip();
},
@ -374,6 +380,12 @@ export default {
//
let loginInfo = uni.getStorageSync("loginInfo")
this.refreshToken(loginInfo).then(() => {
// #ifdef H5
//
uni.switchTab({
url: "/pages/chat/chat"
})
// #endif
//
this.init();
this.closeSplashscreen(0);

8
im-uniapp/common/recorder-app.js

@ -3,6 +3,9 @@ import UNI_APP from '@/.env.js';
const rc = uni.getRecorderManager();
// 录音开始时间
let startTime = null;
let checkIsEnable = ()=>{
return true;
}
let start = () => {
return new Promise((resolve, reject) => {
@ -20,10 +23,6 @@ let start = () => {
})
}
let pause = () => {
rc.stop();
}
let close = () => {
rc.stop();
}
@ -61,6 +60,7 @@ let upload = () => {
}
export {
checkIsEnable,
start,
close,
upload

52
im-uniapp/common/recorder-h5.js

@ -4,26 +4,39 @@ let rc = null;
let duration = 0;
let chunks = [];
let stream = null;
let startTime = null;
let checkIsEnable = () => {
if (origin.indexOf('https') === -1 && origin.indexOf('localhost') === -1 &&
origin.indexOf('127.0.0.1') === -1) {
uni.showToast({
title: '请在https环境中使用录音功能',
icon: 'error'
})
return false;
}
if (!navigator.mediaDevices || !window.MediaRecorder) {
uni.showToast({
title: '当前浏览器不支持录音',
icon: 'error'
})
return false;
}
return true;
}
let start = () => {
return navigator.mediaDevices.getUserMedia({ audio: true }).then(audioStream => {
const startTime = new Date().getTime();
console.log("start record")
startTime = new Date().getTime();
chunks = [];
stream = audioStream;
rc = new MediaRecorder(stream)
rc.ondataavailable = (e) => {
console.log("ondataavailable")
chunks.push(e.data)
}
rc.onstop = () => {
duration = (new Date().getTime() - startTime) / 1000;
console.log("时长:", duration)
}
rc.start()
})
}
let close = () => {
console.log("stream:", stream)
stream.getTracks().forEach((track) => {
track.stop()
})
@ -33,11 +46,23 @@ let close = () => {
let upload = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
rc.ondataavailable = (e) => {
console.log("ondataavailable:",e.data)
console.log("size:",e.data.size)
console.log("type:",e.data.type)
chunks.push(e.data)
}
rc.onstop = () => {
if(!chunks[0].size){
chunks = [];
return;
}
duration = (new Date().getTime() - startTime) / 1000;
console.log("时长:", duration)
console.log("上传,chunks:", chunks.length)
const newbolb = new Blob(chunks, { 'type': 'audio/mpeg' });
const name = new Date().getDate() + '.mp3';
const file = new File([newbolb], name)
console.log("upload")
uni.uploadFile({
url: UNI_APP.BASE_URL + '/file/upload',
header: {
@ -62,11 +87,12 @@ let upload = () => {
reject(e);
}
})
}, 100)
}
})
}
export {
checkIsEnable,
start,
close,
upload

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

@ -79,12 +79,18 @@ export default {
})
this.items[0] = `已读(${this.readedMembers.length})`;
this.items[1] = `未读(${this.unreadMembers.length})`;
//
this.chatStore.updateMessage({
let chatInfo = {
type: 'GROUP',
targetId: this.msgInfo.groupId
}
let msgInfo = {
id: this.msgInfo.id,
groupId: this.msgInfo.groupId,
readedCount: this.readedMembers.length
})
}
//
this.chatStore.updateMessage(msgInfo, chatInfo)
})
},
onClickItem(e) {

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

@ -42,8 +42,15 @@ export default {
}
},
methods: {
showChatBox() {
//
if(!getApp().$vm.isInit || this.chatStore.isLoading()){
uni.showToast({
title: "正在初始化页面,请稍后...",
icon: 'none'
})
return;
}
uni.navigateTo({
url: "/pages/chat/chat-box?chatIdx=" + this.index
})
@ -155,7 +162,8 @@ export default {
font-size: $im-font-size-smaller;
color: $im-text-color-lighter;
padding-top: 8rpx;
align-items: center;
.chat-at-text {
color: $im-color-danger;
}
@ -170,10 +178,6 @@ export default {
overflow: hidden;
text-overflow: ellipsis;
img {
width: 40rpx !important;
height: 40rpx !important;
}
}
}

25
im-uniapp/components/chat-message-item/chat-message-item.vue

@ -17,7 +17,7 @@
<view class="chat-msg-bottom">
<view v-if="msgInfo.type == $enums.MESSAGE_TYPE.TEXT">
<long-press-menu :items="menuItems" @select="onSelectMenu">
<rich-text class="chat-msg-text" :nodes="$emo.transform(msgInfo.content, 'emoji-normal')"></rich-text>
<rich-text class="chat-msg-text" :nodes="$emo.transform(msgInfo.content,'emoji-normal')"></rich-text>
</long-press-menu>
</view>
<view class="chat-msg-image" v-if="msgInfo.type == $enums.MESSAGE_TYPE.IMAGE">
@ -65,12 +65,12 @@
</view>
</long-press-menu>
<view class="chat-msg-status" v-if="!isAction">
<text class="chat-readed" v-show="msgInfo.selfSend && !msgInfo.groupId
<text class="chat-readed" v-if="msgInfo.selfSend && !msgInfo.groupId
&& msgInfo.status == $enums.MESSAGE_STATUS.READED">已读</text>
<text class="chat-unread" v-show="msgInfo.selfSend && !msgInfo.groupId
<text class="chat-unread" v-if="msgInfo.selfSend && !msgInfo.groupId
&& msgInfo.status != $enums.MESSAGE_STATUS.READED">未读</text>
</view>
<view class="chat-receipt" v-show="msgInfo.receipt" @click="onShowReadedBox">
<view class="chat-receipt" v-if="msgInfo.receipt" @click="onShowReadedBox">
<text v-if="msgInfo.receiptOk" class="tool-icon iconfont icon-ok"></text>
<text v-else>{{ msgInfo.readedCount }}人已读</text>
</view>
@ -125,14 +125,16 @@ export default {
this.innerAudioContext = uni.createInnerAudioContext();
let url = JSON.parse(this.msgInfo.content).url;
this.innerAudioContext.src = url;
console.log(url);
this.innerAudioContext.onEnded((e) => {
console.log('停止')
this.audioPlayState = "STOP"
this.emit();
})
this.innerAudioContext.onError((e) => {
this.audioPlayState = "STOP"
console.log("播放音频出错");
console.log(e)
this.emit();
});
}
if (this.audioPlayState == 'STOP') {
@ -145,6 +147,7 @@ export default {
this.innerAudioContext.play();
this.audioPlayState = "PLAYING"
}
this.emit();
},
onSelectMenu(item) {
this.$emit(item.key.toLowerCase(), this.msgInfo);
@ -158,6 +161,16 @@ export default {
},
onShowReadedBox() {
this.$refs.chatGroupReaded.open();
},
emit(){
this.$emit("audioStateChange",this.audioPlayState,this.msgInfo);
},
stopPlayAudio(){
if (this.innerAudioContext) {
this.innerAudioContext.stop();
this.innerAudioContext = null;
this.audioPlayState = "STOP"
}
}
},
computed: {
@ -262,6 +275,7 @@ export default {
.chat-msg-bottom {
display: inline-block;
padding-right: 80rpx;
margin-top: 5rpx;
.chat-msg-text {
position: relative;
@ -289,7 +303,6 @@ export default {
border-color: $im-bg transparent transparent;
overflow: hidden;
border-width: 18rpx;
//box-shadow: $im-box-shadow-dark;
}
}

23
im-uniapp/components/chat-record/chat-record.vue

@ -50,12 +50,14 @@ export default {
/* 使@touchend
一直处于录音状态这里允许用户再次点击发送语音并结束录音 */
if (this.recording) {
this.onEndRecord();
return;
}
console.log("开始录音")
this.moveToCancel = false;
this.initRecordBar();
if(!this.$rc.checkIsEnable()){
return;
}
this.$rc.start().then(() => {
this.recording = true;
console.log("开始录音成功")
@ -70,9 +72,12 @@ export default {
});
},
onEndRecord() {
if(!this.recording){
return;
}
this.recording = false;
//
this.StopTimer();
this.stopTimer();
//
this.$rc.close();
//
@ -80,8 +85,8 @@ export default {
console.log("录音取消")
return;
}
// 1
if (this.druation == 0) {
// 1
if (this.druation <= 1) {
uni.showToast({
title: "说话时间太短",
icon: 'none'
@ -95,13 +100,11 @@ export default {
title: e,
icon: 'none'
})
}).finally(() => {
this.$rc.close();
})
},
startTimer() {
this.druation = 0;
this.StopTimer();
this.stopTimer();
this.rcTimer = setInterval(() => {
this.druation++;
// 60s,
@ -110,7 +113,7 @@ export default {
}
}, 1000)
},
StopTimer() {
stopTimer() {
this.rcTimer && clearInterval(this.rcTimer);
this.rcTimer = null;
},
@ -134,6 +137,10 @@ export default {
}
return `录音时长:${this.druation}s`;
}
},
unmounted() {
this.stopTimer();
this.recording = false;
}
}

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

@ -6,10 +6,11 @@
<scroll-view class="scroll-box" scroll-y="true" upper-threshold="200" @scrolltoupper="onScrollToTop"
:scroll-into-view="'chat-item-' + scrollMsgIdx">
<view v-if="chat" v-for="(msgInfo, idx) in chat.messages" :key="idx">
<chat-message-item v-if="idx >= showMinIdx" :headImage="headImage(msgInfo)"
@call="onRtCall(msgInfo)" :showName="showName(msgInfo)" @recall="onRecallMessage"
@copy="onCopyMessage" @delete="onDeleteMessage" @longPressHead="onLongPressHead(msgInfo)"
@download="onDownloadFile" :id="'chat-item-' + idx" :msgInfo="msgInfo"
<chat-message-item :ref="'message'+msgInfo.id" v-if="idx >= showMinIdx"
:headImage="headImage(msgInfo)" @call="onRtCall(msgInfo)" :showName="showName(msgInfo)"
@recall="onRecallMessage" @delete="onDeleteMessage" @copy="onCopyMessage"
@longPressHead="onLongPressHead(msgInfo)" @download="onDownloadFile"
@audioStateChange="onAudioStateChange" :id="'chat-item-' + idx" :msgInfo="msgInfo"
:groupMembers="groupMembers">
</chat-message-item>
</view>
@ -19,7 +20,7 @@
<view class="iconfont icon-at">:&nbsp;</view>
<scroll-view v-if="atUserIds.length > 0" class="chat-at-scroll-box" scroll-x="true" scroll-left="120">
<view class="chat-at-items">
<view v-for="m in atUserItems" class="chat-at-item">
<view v-for="m in atUserItems" class="chat-at-item" :key="m.userId">
<head-image :name="m.showNickName" :url="m.headImage" size="minier"></head-image>
</view>
</view>
@ -31,24 +32,26 @@
<chat-record v-if="showRecord" class="chat-record" @send="onSendRecord"></chat-record>
<view v-else class="send-text">
<editor id="editor" class="send-text-area" :placeholder="isReceipt ? '[回执消息]' : ''"
:read-only="isReadOnly" @focus="onEditorFocus" @blur="onEditorBlur" @ready="onEditorReady"
@input="onTextInput">
:read-only="isReadOnly" @focus="onEditorFocus" @blur="onEditorBlur" @ready="onEditorReady" @input="onTextInput">
</editor>
<!-- <textarea class="send-text-area" v-model="sendText" auto-height :show-confirm-bar="false"
:placeholder="isReceipt ? '[回执消息]' : ''" :adjust-position="false" @confirm="sendTextMessage()"
@keyboardheightchange="onKeyboardheightchange" @input="onTextInput" confirm-type="send"
confirm-hold :hold-keyboard="true"></textarea> -->
</view>
<view v-if="chat && chat.type == 'GROUP'" class="iconfont icon-at" @click="openAtBox()"></view>
<view class="iconfont icon-icon_emoji" @click="onShowEmoChatTab()"></view>
<view v-if="isEmpty" class="iconfont icon-add" @click="onShowToolsChatTab()">
</view>
<button v-if="!isEmpty || atUserIds.length > 0" class="btn-send" type="primary"
<button v-if="!isEmpty || atUserIds.length" class="btn-send" type="primary"
@touchend.prevent="sendTextMessage()" size="mini">发送</button>
</view>
</view>
<view class="chat-tab-bar">
<view v-if="chatTabBox == 'tools'" class="chat-tools" :style="{height: keyboardHeight+'px'}">
<view class="chat-tools-item">
<file-upload ref="fileUpload" :onBefore="onUploadFileBefore" :onSuccess="onUploadFileSuccess"
:onError="onUploadFileFail">
<view class="tool-icon iconfont icon-folder"></view>
</file-upload>
<view class="tool-name">文件</view>
</view>
<view class="chat-tools-item">
<image-upload :maxCount="9" sourceType="album" :onBefore="onUploadImageBefore"
:onSuccess="onUploadImageSuccess" :onError="onUploadImageFail">
@ -63,15 +66,6 @@
</image-upload>
<view class="tool-name">拍摄</view>
</view>
<view class="chat-tools-item">
<file-upload ref="fileUpload" :onBefore="onUploadFileBefore" :onSuccess="onUploadFileSuccess"
:onError="onUploadFileFail">
<view class="tool-icon iconfont icon-folder"></view>
</file-upload>
<view class="tool-name">文件</view>
</view>
<view class="chat-tools-item" @click="onRecorderInput()">
<view class="tool-icon iconfont icon-microphone"></view>
<view class="tool-name">语音消息</view>
@ -131,8 +125,10 @@ export default {
scrollMsgIdx: 0, //
chatTabBox: 'none',
showRecord: false,
keyboardHeight: 300,
chatMainHeight: 0, //
keyboardHeight: 290, //
windowHeight: 1000, //
initHeight: 1000, // h5
atUserIds: [],
needScrollToBottom: false, //
showMinIdx: 0, // showMinIdx
@ -142,7 +138,8 @@ export default {
editorCtx: null, //
isEmpty: true, //
isFocus: false, //
isReadOnly: false //
isReadOnly: false, //
playingAudio: null //
}
},
methods: {
@ -155,6 +152,11 @@ export default {
this.switchChatTabBox('none');
},
onSendRecord(data) {
//
if (this.isBanned) {
this.showBannedTip();
return;
}
let msgInfo = {
content: JSON.stringify(data),
type: this.$enums.MESSAGE_TYPE.AUDIO,
@ -164,7 +166,7 @@ export default {
this.fillTargetId(msgInfo, this.chat.targetId);
this.sendMessageRequest(msgInfo).then((m) => {
m.selfSend = true;
this.chatStore.insertMessage(m);
this.chatStore.insertMessage(m, this.chat);
//
this.moveChatToTop();
//
@ -260,6 +262,15 @@ export default {
sendTextMessage() {
this.editorCtx.getContents({
success: (e) => {
//
this.editorCtx.clear();
this.atUserIds = [];
this.isReceipt = false;
//
if (this.isBanned) {
this.showBannedTip();
return;
}
let sendText = this.isReceipt ? "【回执消息】" : "";
e.delta.ops.forEach((op) => {
if (op.insert.image) {
@ -294,13 +305,10 @@ export default {
}).finally(() => {
//
this.scrollToBottom();
//
this.atUserIds = [];
this.isReceipt = false;
this.editorCtx.clear();
});
}
})
},
createAtText() {
let atText = "";
@ -343,7 +351,6 @@ export default {
this.$nextTick(() => {
this.scrollMsgIdx = idx;
});
},
onShowEmoChatTab() {
this.showRecord = false;
@ -379,6 +386,11 @@ export default {
})
},
onUploadImageBefore(file) {
//
if (this.isBanned) {
this.showBannedTip();
return;
}
let data = {
originUrl: file.path,
thumbUrl: file.path
@ -399,7 +411,7 @@ export default {
// id
this.fillTargetId(msgInfo, this.chat.targetId);
//
this.chatStore.insertMessage(msgInfo);
this.chatStore.insertMessage(msgInfo, this.chat);
//
this.moveChatToTop();
// file
@ -416,15 +428,20 @@ export default {
msgInfo.loadStatus = 'ok';
msgInfo.id = m.id;
this.isReceipt = false;
this.chatStore.insertMessage(msgInfo);
this.chatStore.insertMessage(msgInfo, this.chat);
})
},
onUploadImageFail(file, err) {
let msgInfo = JSON.parse(JSON.stringify(file.msgInfo));
msgInfo.loadStatus = 'fail';
this.chatStore.insertMessage(msgInfo);
this.chatStore.insertMessage(msgInfo, this.chat);
},
onUploadFileBefore(file) {
//
if (this.isBanned) {
this.showBannedTip();
return;
}
let data = {
name: file.name,
size: file.size,
@ -445,7 +462,7 @@ export default {
// id
this.fillTargetId(msgInfo, this.chat.targetId);
//
this.chatStore.insertMessage(msgInfo);
this.chatStore.insertMessage(msgInfo, this.chat);
//
this.moveChatToTop();
// file
@ -467,13 +484,13 @@ export default {
msgInfo.loadStatus = 'ok';
msgInfo.id = m.id;
this.isReceipt = false;
this.chatStore.insertMessage(msgInfo);
this.chatStore.insertMessage(msgInfo, this.chat);
})
},
onUploadFileFail(file, res) {
let msgInfo = JSON.parse(JSON.stringify(file.msgInfo));
msgInfo.loadStatus = 'fail';
this.chatStore.insertMessage(msgInfo);
this.chatStore.insertMessage(msgInfo, this.chat);
},
onDeleteMessage(msgInfo) {
uni.showModal({
@ -481,7 +498,7 @@ export default {
content: '确认删除消息?',
success: (res) => {
if (!res.cancel) {
this.chatStore.deleteMessage(msgInfo);
this.chatStore.deleteMessage(msgInfo, this.chat);
uni.showToast({
title: "删除成功",
icon: "none"
@ -505,7 +522,7 @@ export default {
msgInfo.type = this.$enums.MESSAGE_TYPE.RECALL;
msgInfo.content = '你撤回了一条消息';
msgInfo.status = this.$enums.MESSAGE_STATUS.RECALL;
this.chatStore.insertMessage(msgInfo);
this.chatStore.insertMessage(msgInfo, this.chat);
})
}
}
@ -515,7 +532,7 @@ export default {
uni.setClipboardData({
data: msgInfo.content,
success: () => {
uni.showToast({ title: '已复制', icon: 'none' });
uni.showToast({ title: '复制成功' });
},
fail: () => {
uni.showToast({ title: '复制失败', icon: 'none' });
@ -552,7 +569,7 @@ export default {
//
this.scrollToMsgIdx(this.showMinIdx);
// #endif
// 0
// 20
this.showMinIdx = this.showMinIdx > 20 ? this.showMinIdx - 20 : 0;
},
onShowMore() {
@ -584,6 +601,15 @@ export default {
onEditorBlur(e) {
this.isFocus = false;
},
onAudioStateChange(state, msgInfo) {
const playingAudio = this.$refs['message' + msgInfo.id][0]
if (state == 'PLAYING' && playingAudio != this.playingAudio) {
//
this.playingAudio && this.playingAudio.stopPlayAudio();
//
this.playingAudio = playingAudio;
}
},
loadReaded(fid) {
this.$http({
url: `/message/private/maxReadedId?friendId=${fid}`,
@ -642,7 +668,7 @@ export default {
})
},
rpxTopx(rpx) {
// pxrpx
// rpxpx
let info = uni.getSystemInfoSync()
let px = info.windowWidth * rpx / 750;
return Math.floor(rpx);
@ -673,53 +699,122 @@ export default {
})
}
},
listenKeyBoard() {
// #ifdef H5
// H5TextArea@keyboardheightchange
//
let initHeight = window.innerHeight;
window.addEventListener('resize', () => {
let keyboardHeight = initHeight - window.innerHeight;
this.isShowKeyBoard = keyboardHeight > 0;
if (this.isShowKeyBoard) {
this.keyboardHeight = keyboardHeight;
reCalChatMainHeight() {
setTimeout(() => {
let h = this.windowHeight;
//
h -= 50;
//
if (this.isShowKeyBoard || this.chatTabBox != 'none') {
console.log("减去键盘高度:", this.keyboardHeight)
h -= this.keyboardHeight;
this.scrollToBottom();
}
this.reCalChatMainHeight();
});
// #ifndef H5
// h5
h -= uni.getSystemInfoSync().statusBarHeight;
// #endif
this.chatMainHeight = h;
console.log("窗口高度:", this.chatMainHeight)
if (this.isShowKeyBoard || this.chatTabBox != 'none') {
this.scrollToBottom();
}
// ios
// #ifdef H5
if (uni.getSystemInfoSync().platform == 'ios') {
//
const delays = [50, 100, 500];
delays.forEach((delay) => {
setTimeout(() => {
uni.pageScrollTo({
scrollTop: 0,
duration: 10
});
}, delay);
})
}
// #endif
}, 30)
},
listenKeyBoard() {
// #ifdef H5
const userAgent = navigator.userAgent;
const regex = /(macintosh|windows)/i;
if (regex.test(userAgent)) {
//
console.log("userAgent:", userAgent)
return;
}
if (uni.getSystemInfoSync().platform == 'ios') {
// ios h5
window.addEventListener('focusin', this.focusInListener);
window.addEventListener('focusout', this.focusOutListener);
} else {
// h5
let initHeight = window.innerHeight;
window.addEventListener('resize', this.resizeListener);
}
// #endif
// #ifndef H5
uni.onKeyboardHeightChange((res) => {
this.isShowKeyBoard = res.height > 0;
if (this.isShowKeyBoard) {
this.keyboardHeight = res.height; //
}
this.reCalChatMainHeight();
});
// app
uni.onKeyboardHeightChange(this.keyBoardListener);
// #endif
},
reCalChatMainHeight() {
const sysInfo = uni.getSystemInfoSync();
let h = sysInfo.windowHeight;
//
h -= 50;
unListenKeyboard() {
// #ifdef H5
// h5sysInfo.windowHeight
if (this.chatTabBox != 'none') {
h -= this.keyboardHeight;
}
// h5
window.removeEventListener('resize', this.resizeListener);
window.removeEventListener('focusin', this.focusInListener);
window.removeEventListener('focusout', this.focusOutListener);
// #endif
// #ifndef H5
//
h -= sysInfo.statusBarHeight;
if (this.isShowKeyBoard || this.chatTabBox != 'none') {
h -= this.keyboardHeight;
}
uni.offKeyboardHeightChange(this.keyBoardListener);
// #endif
console.log("h:", h)
this.chatMainHeight = h;
},
keyBoardListener(res) {
this.isShowKeyBoard = res.height > 0;
if (this.isShowKeyBoard) {
this.keyboardHeight = res.height; //
}
this.reCalChatMainHeight();
},
resizeListener() {
console.log("resize")
let keyboardHeight = this.initHeight - window.innerHeight;
this.isShowKeyBoard = keyboardHeight > 150;
if (this.isShowKeyBoard) {
this.keyboardHeight = keyboardHeight;
}
this.reCalChatMainHeight();
},
focusInListener() {
console.log("focusInListener")
this.isShowKeyBoard = true;
this.reCalChatMainHeight();
},
focusOutListener() {
console.log("focusOutListener")
this.isShowKeyBoard = false;
this.reCalChatMainHeight();
},
showBannedTip() {
let msgInfo = {
tmpId: this.generateId(),
sendId: this.mine.id,
sendTime: new Date().getTime(),
type: this.$enums.MESSAGE_TYPE.TIP_TEXT
}
if (this.chat.type == "PRIVATE") {
msgInfo.recvId = this.mine.id
msgInfo.content = "该用户已被管理员封禁,原因:" + this.friend.reason
} else {
msgInfo.groupId = this.group.id;
msgInfo.content = "本群聊已被管理员封禁,原因:" + this.group.reason
}
this.chatStore.insertMessage(msgInfo, this.chat);
},
generateId() {
// id
// id
return String(new Date().getTime()) + String(Math.floor(Math.random() * 1000));
}
},
@ -753,6 +848,10 @@ export default {
}
return this.chat.unreadCount;
},
isBanned() {
return (this.chat.type == "PRIVATE" && this.friend.isBanned) ||
(this.chat.type == "GROUP" && this.group.isBanned)
},
atUserItems() {
let atUsers = [];
this.atUserIds.forEach((id) => {
@ -815,7 +914,20 @@ export default {
//
this.listenKeyBoard();
//
this.$nextTick(()=>this.reCalChatMainHeight())
this.$nextTick(() => {
this.windowHeight = uni.getSystemInfoSync().windowHeight;
this.reCalChatMainHeight()
// ios h5:
// #ifdef H5
this.initHeight = window.innerHeight;
document.body.addEventListener('touchmove', function(e) {
e.preventDefault();
}, { passive: false });
// #endif
});
},
onUnload() {
this.unListenKeyboard();
},
onShow() {
if (this.needScrollToBottom) {
@ -827,14 +939,12 @@ export default {
}
</script>
<style lang="scss" scoped>
<style lang="scss">
.chat-box {
$icon-color: rgba(0, 0, 0, 0.88);
position: relative;
background-color: #fafafa;
.header {
display: flex;
justify-content: center;
@ -869,7 +979,7 @@ export default {
width: 100%;
display: flex;
flex-direction: column;
z-index: 9;
z-index: 2;
.chat-msg {
flex: 1;

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

@ -1,14 +1,20 @@
<template>
<view class="tab-page">
<nav-bar search @search="showSearch = !showSearch">消息</nav-bar>
<nav-bar search @search="onSearch()">消息</nav-bar>
<view v-if="loading" class="chat-loading">
<loading :size="50" :mask="false">
<view>消息接收中...</view>
</loading>
</view>
<view v-if="initializing" class="chat-loading">
<loading :size="50" :mask="false">
<view>正在初始化...</view>
</loading>
</view>
<view class="nav-bar" v-if="showSearch">
<view class="nav-search">
<uni-search-bar radius="100" v-model="searchText" cancelButton="none" placeholder="搜索"></uni-search-bar>
<uni-search-bar focus="true" radius="100" v-model="searchText" cancelButton="none"
placeholder="搜索"></uni-search-bar>
</view>
</view>
<view class="chat-tip" v-if="!loading && chatStore.chats.length == 0">
@ -77,6 +83,10 @@ export default {
}
return !this.searchText || chat.showName.includes(this.searchText)
},
onSearch() {
this.showSearch = !this.showSearch;
this.searchText = "";
},
refreshUnreadBadge() {
if (this.unreadCount > 0) {
uni.setTabBarBadge({
@ -86,7 +96,7 @@ export default {
} else {
uni.removeTabBarBadge({
index: 0,
complete: () => { }
complete: () => {}
})
}
}
@ -102,7 +112,10 @@ export default {
return count;
},
loading() {
return this.chatStore.loadingGroupMsg || this.chatStore.loadingPrivateMsg
return this.chatStore.isLoading();
},
initializing(){
return !getApp().$vm.isInit;
}
},
watch: {
@ -116,7 +129,7 @@ export default {
}
</script>
<style scoped lang="scss">
<style lang="scss">
.tab-page {
position: relative;
display: flex;
@ -136,9 +149,7 @@ export default {
width: 100%;
height: 120rpx;
background: white;
position: fixed;
top: 0;
z-index: 999;
color: $im-text-color-lighter;
.loading-box {

35
im-uniapp/store/chatStore.js

@ -141,9 +141,9 @@ export default defineStore('chatStore', {
this.saveToStorage();
}
},
insertMessage(msgInfo) {
insertMessage(msgInfo, chatInfo) {
// 获取对方id或群id
let type = msgInfo.groupId ? 'GROUP' : 'PRIVATE';
let type = chatInfo.type;
// 记录消息的最大id
if (msgInfo.id && type == "PRIVATE" && msgInfo.id > this.privateMsgMaxId) {
this.privateMsgMaxId = msgInfo.id;
@ -152,7 +152,7 @@ export default defineStore('chatStore', {
this.groupMsgMaxId = msgInfo.id;
}
// 如果是已存在消息,则覆盖旧的消息数据
let chat = this.findChat(msgInfo);
let chat = this.findChat(chatInfo);
let message = this.findMessage(chat, msgInfo);
if (message) {
Object.assign(message, msgInfo);
@ -228,9 +228,9 @@ export default defineStore('chatStore', {
chat.stored = false;
this.saveToStorage();
},
updateMessage(msgInfo) {
updateMessage(msgInfo, chatInfo) {
// 获取对方id或群id
let chat = this.findChat(msgInfo);
let chat = this.findChat(chatInfo);
let message = this.findMessage(chat, msgInfo);
if (message) {
// 属性拷贝
@ -239,9 +239,9 @@ export default defineStore('chatStore', {
this.saveToStorage();
}
},
deleteMessage(msgInfo) {
deleteMessage(msgInfo, chatInfo) {
// 获取对方id或群id
let chat = this.findChat(msgInfo);
let chat = this.findChat(chatInfo);
for (let idx in chat.messages) {
// 已经发送成功的,根据id删除
if (chat.messages[idx].id && chat.messages[idx].id == msgInfo.id) {
@ -261,7 +261,7 @@ export default defineStore('chatStore', {
updateChatFromFriend(friend) {
let chat = this.findChatByFriend(friend.id)
if (chat && (chat.headImage != friend.headImageThumb ||
chat.showName != friend.nickName)) {
chat.showName != friend.nickName)) {
// 更新会话中的群名和头像
chat.headImage = friend.headImageThumb;
chat.showName = friend.nickName;
@ -272,7 +272,7 @@ export default defineStore('chatStore', {
updateChatFromGroup(group) {
let chat = this.findChatByGroup(group.id);
if (chat && (chat.headImage != group.headImageThumb ||
chat.showName != group.showGroupName)) {
chat.showName != group.showGroupName)) {
// 更新会话中的群名称和头像
chat.headImage = group.headImageThumb;
chat.showName = group.showGroupName;
@ -390,21 +390,10 @@ export default defineStore('chatStore', {
}
}
},
findChat: (state) => (msgInfo) => {
findChat: (state) => (chat) => {
let chats = state.curChats;
// 获取对方id或群id
let type = msgInfo.groupId ? 'GROUP' : 'PRIVATE';
let targetId = msgInfo.groupId ? msgInfo.groupId : msgInfo.selfSend ? msgInfo.recvId : msgInfo
.sendId;
let chat = null;
for (let idx in chats) {
if (chats[idx].type == type &&
chats[idx].targetId === targetId) {
chat = chats[idx];
break;
}
}
return chat;
let idx = state.findChatIdx(chat);
return chats[idx];
},
findChatByFriend: (state) => (fid) => {
return state.curChats.find(chat => chat.type == 'PRIVATE' &&

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

@ -161,15 +161,20 @@ export default {
msgInfo.loadStatus = 'ok';
msgInfo.id = m.id;
this.isReceipt = false;
this.$store.commit("insertMessage", msgInfo);
this.$store.commit("insertMessage", [msgInfo, this.chat]);
})
},
onImageFail(e, file) {
let msgInfo = JSON.parse(JSON.stringify(file.msgInfo));
msgInfo.loadStatus = 'fail';
this.$store.commit("insertMessage", msgInfo);
this.$store.commit("insertMessage", [msgInfo, this.chat]);
},
onImageBefore(file) {
//
if (this.isBanned) {
this.showBannedTip();
return;
}
let url = URL.createObjectURL(file);
let data = {
originUrl: url,
@ -191,7 +196,7 @@ export default {
// id
this.fillTargetId(msgInfo, this.chat.targetId);
//
this.$store.commit("insertMessage", msgInfo);
this.$store.commit("insertMessage", [msgInfo, this.chat]);
//
this.moveChatToTop();
//
@ -213,15 +218,20 @@ export default {
msgInfo.id = m.id;
this.isReceipt = false;
this.refreshPlaceHolder();
this.$store.commit("insertMessage", msgInfo);
this.$store.commit("insertMessage", [msgInfo, this.chat]);
})
},
onFileFail(e, file) {
let msgInfo = JSON.parse(JSON.stringify(file.msgInfo));
msgInfo.loadStatus = 'fail';
this.$store.commit("insertMessage", msgInfo);
this.$store.commit("insertMessage", [msgInfo, this.chat]);
},
onFileBefore(file) {
//
if (this.isBanned) {
this.showBannedTip();
return;
}
let url = URL.createObjectURL(file);
let data = {
name: file.name,
@ -243,7 +253,7 @@ export default {
// id
this.fillTargetId(msgInfo, this.chat.targetId);
//
this.$store.commit("insertMessage", msgInfo);
this.$store.commit("insertMessage", [msgInfo, this.chat]);
//
this.moveChatToTop();
//
@ -285,6 +295,12 @@ export default {
this.showRecord = false;
},
showPrivateVideo(mode) {
//
if (this.isBanned) {
this.showBannedTip();
return;
}
let rtcInfo = {
mode: mode,
isHost: true,
@ -294,6 +310,11 @@ export default {
this.$eventBus.$emit("openPrivateVideo", rtcInfo);
},
onGroupVideo() {
//
if (this.isBanned) {
this.showBannedTip();
return;
}
//
let ids = [this.mine.id];
let maxChannel = this.$store.state.configStore.webrtc.maxChannel;
@ -329,6 +350,11 @@ export default {
this.showHistory = false;
},
onSendRecord(data) {
//
if (this.isBanned) {
this.showBannedTip();
return;
}
let msgInfo = {
content: JSON.stringify(data),
type: 3,
@ -338,7 +364,7 @@ export default {
this.fillTargetId(msgInfo, this.chat.targetId);
this.sendMessageRequest(msgInfo).then((m) => {
m.selfSend = true;
this.$store.commit("insertMessage", m);
this.$store.commit("insertMessage", [m, this.chat]);
//
this.moveChatToTop();
//
@ -364,6 +390,11 @@ export default {
async sendMessage(fullList) {
this.resetEditor();
this.readedMessage();
//
if (this.isBanned) {
this.showBannedTip();
return;
}
let sendText = this.isReceipt ? "【回执消息】" : "";
let promiseList = [];
for (let i = 0; i < fullList.length; i++) {
@ -421,7 +452,7 @@ export default {
this.lockMessage = true;
this.sendMessageRequest(msgInfo).then((m) => {
m.selfSend = true;
this.$store.commit("insertMessage", m);
this.$store.commit("insertMessage", [m, this.chat]);
//
this.moveChatToTop();
}).finally(() => {
@ -465,7 +496,7 @@ export default {
msgInfo.type = 10;
msgInfo.content = '你撤回了一条消息';
msgInfo.status = this.$enums.MESSAGE_STATUS.RECALL;
this.$store.commit("insertMessage", msgInfo);
this.$store.commit("insertMessage", [msgInfo, this.chat]);
})
});
},
@ -482,7 +513,7 @@ export default {
this.$http({
url: url,
method: 'put'
}).then(() => { })
}).then(() => {})
},
loadReaded(fId) {
this.$http({
@ -588,6 +619,22 @@ export default {
})
}
},
showBannedTip() {
let msgInfo = {
tmpId: this.generateId(),
sendId: this.mine.id,
sendTime: new Date().getTime(),
type: this.$enums.MESSAGE_TYPE.TIP_TEXT
}
if (this.chat.type == "PRIVATE") {
msgInfo.recvId = this.mine.id
msgInfo.content = "该用户已被管理员封禁,原因:" + this.friend.reason
} else {
msgInfo.groupId = this.group.id;
msgInfo.content = "本群聊已被管理员封禁,原因:" + this.group.reason
}
this.$store.commit("insertMessage", [msgInfo, this.chat]);
},
generateId() {
// id
return String(new Date().getTime()) + String(Math.floor(Math.random() * 1000));
@ -616,13 +663,17 @@ export default {
return 0;
}
return this.chat.messages.length;
},
isBanned() {
return (this.chat.type == "PRIVATE" && this.friend.isBanned) ||
(this.chat.type == "GROUP" && this.group.isBanned)
}
},
watch: {
chat: {
handler(newChat, oldChat) {
if (newChat.targetId > 0 && (!oldChat || newChat.type != oldChat.type ||
newChat.targetId != oldChat.targetId)) {
newChat.targetId != oldChat.targetId)) {
if (this.chat.type == "GROUP") {
this.loadGroup(this.chat.targetId);
} else {
@ -831,4 +882,4 @@ export default {
}
}
</style>
</style>

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

@ -443,7 +443,7 @@ export default {
if (node.dataset.id) {
tempText += node.innerHTML;
atUserIds.push(node.dataset.id)
} else {
} else if(node.outerHtml) {
tempText += node.outerHtml;
}
}

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

@ -137,8 +137,8 @@ export default {
this.commit("saveToStorage");
}
},
insertMessage(state, msgInfo) {
let type = msgInfo.groupId ? 'GROUP' : 'PRIVATE';
insertMessage(state, [msgInfo, chatInfo]) {
let type = chatInfo.type;
// 记录消息的最大id
if (msgInfo.id && type == "PRIVATE" && msgInfo.id > state.privateMsgMaxId) {
state.privateMsgMaxId = msgInfo.id;
@ -147,7 +147,7 @@ export default {
state.groupMsgMaxId = msgInfo.id;
}
// 如果是已存在消息,则覆盖旧的消息数据
let chat = this.getters.findChat(msgInfo);
let chat = this.getters.findChat(chatInfo);
let message = this.getters.findMessage(chat, msgInfo);
if (message) {
Object.assign(message, msgInfo);
@ -178,7 +178,8 @@ export default {
chat.lastSendTime = msgInfo.sendTime;
chat.sendNickName = msgInfo.sendNickName;
// 未读加1
if (!msgInfo.selfSend && msgInfo.status != MESSAGE_STATUS.READED && msgInfo.type != MESSAGE_TYPE.TIP_TEXT) {
if (!msgInfo.selfSend && msgInfo.status != MESSAGE_STATUS.READED &&
msgInfo.type != MESSAGE_TYPE.TIP_TEXT) {
chat.unreadCount++;
}
// 是否有人@我
@ -216,9 +217,9 @@ export default {
chat.stored = false;
this.commit("saveToStorage");
},
updateMessage(state, msgInfo) {
updateMessage(state, [msgInfo, chatInfo]) {
// 获取对方id或群id
let chat = this.getters.findChat(msgInfo);
let chat = this.getters.findChat(chatInfo);
let message = this.getters.findMessage(chat, msgInfo);
if (message) {
// 属性拷贝
@ -227,8 +228,8 @@ export default {
this.commit("saveToStorage");
}
},
deleteMessage(state, msgInfo) {
let chat = this.getters.findChat(msgInfo);
deleteMessage(state, [msgInfo, chatInfo]) {
let chat = this.getters.findChat(chatInfo);
for (let idx in chat.messages) {
// 已经发送成功的,根据id删除
if (chat.messages[idx].id && chat.messages[idx].id == msgInfo.id) {
@ -289,7 +290,7 @@ export default {
});
// 将消息一次性装载回来
state.chats = cacheChats;
// 清空缓存,不再使用
// 清空缓存
cacheChats = null;
this.commit("saveToStorage");
},
@ -384,20 +385,10 @@ export default {
}
}
},
findChat: (state, getters) => (msgInfo) => {
findChat: (state, getters) => (chat) => {
let chats = getters.findChats();
// 获取对方id或群id
let type = msgInfo.groupId ? 'GROUP' : 'PRIVATE';
let targetId = msgInfo.groupId ? msgInfo.groupId : msgInfo.selfSend ? msgInfo.recvId : msgInfo.sendId;
let chat = null;
for (let idx in chats) {
if (chats[idx].type == type &&
chats[idx].targetId === targetId) {
chat = chats[idx];
break;
}
}
return chat;
let idx = getters.findChatIdx(chat);
return chats[idx];
},
findChatByFriend: (state, getters) => (fid) => {
let chats = getters.findChats();

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

@ -201,7 +201,7 @@ export default {
//
this.$store.commit("openChat", chatInfo);
//
this.$store.commit("insertMessage", msg);
this.$store.commit("insertMessage", [msg, chatInfo]);
//
if (!msg.selfSend && this.$msgType.isNormal(msg.type) &&
msg.status != this.$enums.MESSAGE_STATUS.READED) {
@ -251,7 +251,6 @@ export default {
})
},
insertGroupMessage(group, msg) {
let chatInfo = {
type: 'GROUP',
targetId: group.id,
@ -261,7 +260,7 @@ export default {
//
this.$store.commit("openChat", chatInfo);
//
this.$store.commit("insertMessage", msg);
this.$store.commit("insertMessage", [msg, chatInfo]);
//
if (!msg.selfSend && msg.type <= this.$enums.MESSAGE_TYPE.VIDEO &&
msg.status != this.$enums.MESSAGE_STATUS.READED) {

Loading…
Cancel
Save