diff --git a/im-uniapp/App.vue b/im-uniapp/App.vue index 475ab99..51d11e6 100644 --- a/im-uniapp/App.vue +++ b/im-uniapp/App.vue @@ -378,6 +378,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); diff --git a/im-uniapp/common/recorder-app.js b/im-uniapp/common/recorder-app.js index 0c8ff83..f114bbb 100644 --- a/im-uniapp/common/recorder-app.js +++ b/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 diff --git a/im-uniapp/common/recorder-h5.js b/im-uniapp/common/recorder-h5.js index 1425dd3..ddb4112 100644 --- a/im-uniapp/common/recorder-h5.js +++ b/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 diff --git a/im-uniapp/components/chat-message-item/chat-message-item.vue b/im-uniapp/components/chat-message-item/chat-message-item.vue index 71ab643..1b4a3dc 100644 --- a/im-uniapp/components/chat-message-item/chat-message-item.vue +++ b/im-uniapp/components/chat-message-item/chat-message-item.vue @@ -17,7 +17,7 @@ - + @@ -65,12 +65,12 @@ - 已读 - 未读 - + {{ msgInfo.readedCount }}人已读 @@ -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; } } diff --git a/im-uniapp/components/chat-record/chat-record.vue b/im-uniapp/components/chat-record/chat-record.vue index bde3454..cb390d8 100644 --- a/im-uniapp/components/chat-record/chat-record.vue +++ b/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; } } diff --git a/im-uniapp/pages/chat/chat-box.vue b/im-uniapp/pages/chat/chat-box.vue index 309f5d8..d9d9878 100644 --- a/im-uniapp/pages/chat/chat-box.vue +++ b/im-uniapp/pages/chat/chat-box.vue @@ -6,10 +6,11 @@ - @@ -19,7 +20,7 @@ - + @@ -31,24 +32,26 @@ + :read-only="isReadOnly" @focus="onEditorFocus" @blur="onEditorBlur" @ready="onEditorReady" @input="onTextInput"> - - + + + + + 文件 + @@ -63,15 +66,6 @@ 拍摄 - - - - - - 文件 - - 语音消息 @@ -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: { @@ -265,7 +262,6 @@ export default { sendTextMessage() { this.editorCtx.getContents({ success: (e) => { - // 清空编辑框数据 this.editorCtx.clear(); this.atUserIds = []; @@ -275,7 +271,6 @@ export default { this.showBannedTip(); return; } - let sendText = this.isReceipt ? "【回执消息】" : ""; e.delta.ops.forEach((op) => { if (op.insert.image) { @@ -302,7 +297,6 @@ export default { } // 填充对方id this.fillTargetId(msgInfo, this.chat.targetId); - this.sendMessageRequest(msgInfo).then((m) => { m.selfSend = true; this.chatStore.insertMessage(m, this.chat); @@ -314,6 +308,7 @@ export default { }); } }) + }, createAtText() { let atText = ""; @@ -356,7 +351,6 @@ export default { this.$nextTick(() => { this.scrollMsgIdx = idx; }); - }, onShowEmoChatTab() { this.showRecord = false; @@ -538,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' }); @@ -575,7 +569,7 @@ export default { // 防止滚动条定格在顶部,不能一直往上滚 this.scrollToMsgIdx(this.showMinIdx); // #endif - // 多展示0条信息 + // 多展示20条信息 this.showMinIdx = this.showMinIdx > 20 ? this.showMinIdx - 20 : 0; }, onShowMore() { @@ -607,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}`, @@ -665,7 +668,7 @@ export default { }) }, rpxTopx(rpx) { - // px转换成rpx + // rpx转换成px let info = uni.getSystemInfoSync() let px = info.windowWidth * rpx / 750; return Math.floor(rpx); @@ -696,30 +699,104 @@ export default { }) } }, + reCalChatMainHeight() { + setTimeout(() => { + let h = this.windowHeight; + // 减去标题栏高度 + h -= 50; + // 减去键盘高度 + if (this.isShowKeyBoard || this.chatTabBox != 'none') { + console.log("减去键盘高度:", this.keyboardHeight) + h -= this.keyboardHeight; + this.scrollToBottom(); + } + // #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 + // app实现键盘监听 + uni.onKeyboardHeightChange(this.keyBoardListener); + // #endif + }, + unListenKeyboard() { // #ifdef H5 - // 由于H5无法触发TextArea的@keyboardheightchange事件,所以通过 - // 监听屏幕高度变化来实现键盘监听 - let initHeight = window.innerHeight; - window.addEventListener('resize', () => { - let keyboardHeight = initHeight - window.innerHeight; - this.isShowKeyBoard = keyboardHeight > 0; - if (this.isShowKeyBoard) { - this.keyboardHeight = keyboardHeight; - } - this.reCalChatMainHeight(); - }); + // 安卓h5实现键盘监听 + window.removeEventListener('resize', this.resizeListener); + window.removeEventListener('focusin', this.focusInListener); + window.removeEventListener('focusout', this.focusOutListener); // #endif // #ifndef H5 - uni.onKeyboardHeightChange((res) => { - this.isShowKeyBoard = res.height > 0; - if (this.isShowKeyBoard) { - this.keyboardHeight = res.height; // 获取并保存键盘高度 - } - this.reCalChatMainHeight(); - }); + uni.offKeyboardHeightChange(this.keyBoardListener); // #endif }, + 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(), @@ -736,29 +813,8 @@ export default { } this.chatStore.insertMessage(msgInfo, this.chat); }, - reCalChatMainHeight() { - const sysInfo = uni.getSystemInfoSync(); - let h = sysInfo.windowHeight; - // 减去标题栏高度 - h -= 50; - // #ifdef H5 - // h5的sysInfo.windowHeight默认就已经减去键盘高度了 - if (this.chatTabBox != 'none') { - h -= this.keyboardHeight; - } - // #endif - // #ifndef H5 - // 减去状态栏高度 - h -= sysInfo.statusBarHeight; - if (this.isShowKeyBoard || this.chatTabBox != 'none') { - h -= this.keyboardHeight; - } - // #endif - console.log("h:", h) - this.chatMainHeight = h; - }, generateId() { - // 生成临时id + // 生成临时id return String(new Date().getTime()) + String(Math.floor(Math.random() * 1000)); } }, @@ -858,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) { @@ -870,14 +939,12 @@ export default { } -