Browse Source

!161 移除default-passive-events插件(会导致h5键盘无法保持)

Merge pull request !161 from blue/v_3.0.0
master
blue 7 months ago
committed by Gitee
parent
commit
4a423d83c8
No known key found for this signature in database GPG Key ID: 173E9B9CA92EEF8F
  1. 22
      im-uniapp/App.vue
  2. 18
      im-uniapp/components/chat-message-item/chat-message-item.vue
  3. 203
      im-uniapp/components/nav-bar/nav-bar.vue
  4. 1
      im-uniapp/main.js
  5. 1
      im-uniapp/package.json
  6. 10
      im-uniapp/pages.json
  7. 284
      im-uniapp/pages/chat/chat-box.vue
  8. 10
      im-uniapp/pages/friend/friend.vue
  9. 26
      im-uniapp/store/chatStore.js
  10. 44
      im-uniapp/store/friendStore.js
  11. 30
      im-uniapp/store/groupStore.js
  12. 22
      im-web/src/components/chat/ChatBox.vue
  13. 3
      im-web/src/components/chat/ChatMessageItem.vue
  14. 34
      im-web/src/store/chatStore.js
  15. BIN
      截图/app/1.png
  16. BIN
      截图/app/2.png
  17. BIN
      截图/web/单人通话.jpg
  18. BIN
      截图/web/多人通话.jpg
  19. BIN
      截图/web/好友.jpg
  20. BIN
      截图/web/私聊.jpg
  21. BIN
      截图/web/群列表.jpg
  22. BIN
      截图/web/群聊.jpg
  23. BIN
      截图/消息推送集群化.jpg

22
im-uniapp/App.vue

@ -492,6 +492,9 @@ uni-page-head {
}
// #endif
page {
background-color: $im-bg;
}
.tab-page {
position: relative;
@ -502,9 +505,15 @@ uni-page-head {
top: $im-nav-bar-height;
// #endif
// #ifndef H5
// #ifdef APP-PLUS
height: calc(100vh - var(--status-bar-height) - $im-nav-bar-height); // app
top: calc($im-nav-bar-height + var(--status-bar-height));
// #endif
// #ifdef MP-WEIXIN
height: calc(100vh - $im-nav-bar-height);
top: $im-nav-bar-height;
// #endif
color: $im-text-color;
background-color: $im-bg;
@ -515,13 +524,20 @@ uni-page-head {
.page {
position: relative;
// #ifdef H5
height: calc(100vh - $im-nav-bar-height); // app
height: calc(100vh - $im-nav-bar-height); // h5100vh
top: $im-nav-bar-height;
// #endif
// #ifndef H5
// #ifdef APP-PLUS
height: calc(100vh - var(--status-bar-height) - $im-nav-bar-height); // app
top: calc($im-nav-bar-height + var(--status-bar-height));
// #endif
// #ifdef MP-WEIXIN
height: calc(100vh - $im-nav-bar-height);
top: $im-nav-bar-height;
// #endif
color: $im-text-color;
background-color: $im-bg;
font-size: $im-font-size;

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

@ -56,21 +56,21 @@
<text v-if="audioPlayState == 'PLAYING'" class="iconfont icon-pause"></text>
</view>
</long-press-menu>
<long-press-menu v-if="isAction" :items="menuItems" @select="onSelectMenu">
<view class="chat-realtime message-text" @click="$emit('call')">
<text v-if="msgInfo.type == $enums.MESSAGE_TYPE.ACT_RT_VOICE"
class="iconfont icon-chat-voice"></text>
<text v-if="msgInfo.type == $enums.MESSAGE_TYPE.ACT_RT_VIDEO"
class="iconfont icon-chat-video"></text>
<text>{{ msgInfo.content }}</text>
</view>
</long-press-menu>
<view v-if="sending&&isTextMessage" class="sending">
<loading :size="40" icon-color="#656adf" :mask="false"></loading>
</view>
<view v-else-if="sendFail" @click="onSendFail"
class="send-fail iconfont icon-warning-circle-fill"></view>
</view>
<long-press-menu v-if="isAction" :items="menuItems" @select="onSelectMenu">
<view class="chat-realtime message-text" @click="$emit('call')">
<text v-if="msgInfo.type == $enums.MESSAGE_TYPE.ACT_RT_VOICE"
class="iconfont icon-chat-voice"></text>
<text v-if="msgInfo.type == $enums.MESSAGE_TYPE.ACT_RT_VIDEO"
class="iconfont icon-chat-video"></text>
<text>{{ msgInfo.content }}</text>
</view>
</long-press-menu>
<view class="message-status" v-if="!isAction && msgInfo.selfSend && !msgInfo.groupId">
<text class="chat-readed" v-if="msgInfo.status == $enums.MESSAGE_STATUS.READED">已读</text>
<text class="chat-unread" v-else>未读</text>

203
im-uniapp/components/nav-bar/nav-bar.vue

@ -1,119 +1,122 @@
<template>
<view class="im-nav-bar">
<!-- #ifndef H5 -->
<view style="height: var(--status-bar-height)"></view>
<!-- #endif -->
<view class="im-nav-bar-content">
<view class="back" @click="handleBackClick" v-if="back">
<uni-icons type="back" :size="iconFontSize"></uni-icons>
</view>
<view class="title" v-if="title">
<slot></slot>
</view>
<view class="btn">
<uni-icons class="btn-item" v-if="search" type="search" :size="iconFontSize"
@click="$emit('search')"></uni-icons>
<uni-icons class="btn-item" v-if="add" type="plusempty" :size="iconFontSize" @click="$emit('add')"></uni-icons>
<uni-icons class="btn-item" v-if="more" type="more-filled" :size="iconFontSize"
@click="$emit('more')"></uni-icons>
</view>
</view>
</view>
<view class="im-nav-bar">
<!-- #ifdef APP-PLUS -->
<view style="height: var(--status-bar-height)"></view>
<!-- #endif -->
<view class="im-nav-bar-content">
<!-- #ifndef MP-WEIXIN -->
<view class="back" @click="handleBackClick" v-if="back">
<uni-icons type="back" :size="iconFontSize"></uni-icons>
</view>
<!-- #endif -->
<view class="title" v-if="title">
<slot></slot>
</view>
<view class="btn">
<uni-icons class="btn-item" v-if="search" type="search" :size="iconFontSize"
@click="$emit('search')"></uni-icons>
<uni-icons class="btn-item" v-if="add" type="plusempty" :size="iconFontSize"
@click="$emit('add')"></uni-icons>
<uni-icons class="btn-item" v-if="more" type="more-filled" :size="iconFontSize"
@click="$emit('more')"></uni-icons>
</view>
</view>
</view>
</template>
<script>
export default {
name: "nav-bar",
props: {
back: {
type: Boolean,
default: false
},
title: {
type: Boolean,
default: true
},
search: {
type: Boolean,
default: false
},
add: {
type: Boolean,
default: false
},
more: {
type: Boolean,
default: false
},
iconFontSize: {
type: Number,
default: 24
}
},
data() {
return {}
},
computed: {
height() {
name: "nav-bar",
props: {
back: {
type: Boolean,
default: false
},
title: {
type: Boolean,
default: true
},
search: {
type: Boolean,
default: false
},
add: {
type: Boolean,
default: false
},
more: {
type: Boolean,
default: false
},
iconFontSize: {
type: Number,
default: 24
}
},
data() {
return {}
},
computed: {
height() {
}
},
methods: {
handleBackClick() {
uni.navigateBack({
delta: 1
})
}
}
}
},
methods: {
handleBackClick() {
uni.navigateBack({
delta: 1
})
}
}
}
</script>
<style scoped lang="scss">
.im-nav-bar {
background-color: #fff;
//background-color: $im-bg;
position: fixed;
top: 0;
width: 100%;
color: $im-text-color;
border-bottom: 1px solid $im-border-light;
font-size: $im-font-size-large;
z-index: 99;
background-color: #fff;
//background-color: $im-bg;
position: fixed;
top: 0;
width: 100%;
color: $im-text-color;
border-bottom: 1px solid $im-border-light;
font-size: $im-font-size-large;
z-index: 99;
.im-nav-bar-content {
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
height: $im-nav-bar-height;
.im-nav-bar-content {
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
height: $im-nav-bar-height;
.title {}
.title {}
.back {
position: absolute;
left: 0;
height: 100%;
display: flex;
align-items: center;
padding: 12px;
font-size: 22px;
box-sizing: border-box;
}
.back {
position: absolute;
left: 0;
height: 100%;
display: flex;
align-items: center;
padding: 12px;
font-size: 22px;
box-sizing: border-box;
}
.btn {
position: absolute;
right: 0;
height: 100%;
display: flex;
padding: 12px;
align-items: center;
box-sizing: border-box;
.btn {
position: absolute;
right: 0;
height: 100%;
display: flex;
padding: 12px;
align-items: center;
box-sizing: border-box;
.btn-item {
margin-left: 8px;
}
}
}
.btn-item {
margin-left: 8px;
}
}
}
}
</style>

1
im-uniapp/main.js

@ -23,7 +23,6 @@ import switchBar from '@/components/bar/switch-bar'
import * as recorder from './common/recorder-h5';
import ImageResize from "quill-image-resize-mp";
import Quill from "quill";
import 'default-passive-events';
// 以下组件用于兼容部分手机聊天边框无法输入的问题
window.Quill = Quill;
window.ImageResize = { default: ImageResize };

1
im-uniapp/package.json

@ -4,7 +4,6 @@
"scripts": {}
},
"dependencies": {
"default-passive-events": "^4.0.0",
"pinyin-pro": "^3.23.1",
"quill": "^1.3.7",
"quill-image-resize-mp": "^3.0.1",

10
im-uniapp/pages.json

@ -30,7 +30,9 @@
{
"path": "pages/chat/chat-box",
"style": {
"navigationStyle": "custom",
"mp-weixin": {
"disableScroll": true
},
"app-plus": {
// adjustPanadjustResize=webview+
"softinputMode": "adjustResize"
@ -67,7 +69,13 @@
],
"globalStyle": {
"navigationBarTitleText": "盒子IM",
"navigationBarTextStyle": "black",
/* #ifndef MP-WEIXIN */
"navigationStyle": "custom",
/* #endif */
/* #ifdef MP-WEIXIN */
"navigationStyle": "default",
/* #endif */
"navigationBarBackgroundColor": "#f7f7f7",
"backgroundColor": "#f7f7f7"
},

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

@ -50,52 +50,55 @@
</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">
<view class="tool-icon iconfont icon-picture"></view>
</image-upload>
<view class="tool-name">相册</view>
</view>
<view class="chat-tools-item">
<image-upload sourceType="camera" :onBefore="onUploadImageBefore" :onSuccess="onUploadImageSuccess"
:onError="onUploadImageFail">
<view class="tool-icon iconfont icon-camera"></view>
</image-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>
</view>
<view v-if="chat.type == 'GROUP' && memberSize<=500" class="chat-tools-item" @click="switchReceipt()">
<view class="tool-icon iconfont icon-receipt" :class="isReceipt ? 'active' : ''"></view>
<view class="tool-name">回执消息</view>
</view>
<!-- #ifndef MP-WEIXIN -->
<!-- 音视频不支持小程序 -->
<view v-if="chat.type == 'PRIVATE'" class="chat-tools-item" @click="onPriviteVideo()">
<view class="tool-icon iconfont icon-video"></view>
<view class="tool-name">视频通话</view>
</view>
<view v-if="chat.type == 'PRIVATE'" class="chat-tools-item" @click="onPriviteVoice()">
<view class="tool-icon iconfont icon-call"></view>
<view class="tool-name">语音通话</view>
</view>
<view v-if="chat.type == 'GROUP'" class="chat-tools-item" @click="onGroupVideo()">
<view class="tool-icon iconfont icon-call"></view>
<view class="tool-name">语音通话</view>
<scroll-view v-if="chatTabBox == 'tools'" class="chat-tools" :style="{height: keyboardHeight+'px'}">
<view class="chat-tools-list">
<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">
<view class="tool-icon iconfont icon-picture"></view>
</image-upload>
<view class="tool-name">相册</view>
</view>
<view class="chat-tools-item">
<image-upload sourceType="camera" :onBefore="onUploadImageBefore"
:onSuccess="onUploadImageSuccess" :onError="onUploadImageFail">
<view class="tool-icon iconfont icon-camera"></view>
</image-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>
</view>
<view v-if="chat.type == 'GROUP' && memberSize<=500" class="chat-tools-item"
@click="switchReceipt()">
<view class="tool-icon iconfont icon-receipt" :class="isReceipt ? 'active' : ''"></view>
<view class="tool-name">回执消息</view>
</view>
<!-- #ifndef MP-WEIXIN -->
<!-- 音视频不支持小程序 -->
<view v-if="chat.type == 'PRIVATE'" class="chat-tools-item" @click="onPriviteVideo()">
<view class="tool-icon iconfont icon-video"></view>
<view class="tool-name">视频通话</view>
</view>
<view v-if="chat.type == 'PRIVATE'" class="chat-tools-item" @click="onPriviteVoice()">
<view class="tool-icon iconfont icon-call"></view>
<view class="tool-name">语音通话</view>
</view>
<view v-if="chat.type == 'GROUP'" class="chat-tools-item" @click="onGroupVideo()">
<view class="tool-icon iconfont icon-call"></view>
<view class="tool-name">语音通话</view>
</view>
<!-- #endif -->
</view>
<!-- #endif -->
</view>
</scroll-view>
<scroll-view v-if="chatTabBox === 'emo'" class="chat-emotion" scroll-y="true"
:style="{height: keyboardHeight+'px'}">
<view class="emotion-item-list">
@ -133,6 +136,7 @@ export default {
showRecord: false,
chatMainHeight: 800, //
keyboardHeight: 290, //
screenHeight: 1000, //
windowHeight: 1000, //
initHeight: 1000, // h5
atUserIds: [],
@ -184,7 +188,7 @@ export default {
//
tmpMessage.id = m.id;
tmpMessage.status = m.status;
this.chatStore.insertMessage(tmpMessage, chat);
this.chatStore.updateMessage(tmpMessage, chat);
//
this.moveChatToTop();
//
@ -331,12 +335,12 @@ export default {
tmpMessage.id = m.id;
tmpMessage.status = m.status;
tmpMessage.content = m.content;
this.chatStore.insertMessage(tmpMessage, chat);
this.chatStore.updateMessage(tmpMessage, chat);
}).catch(() => {
//
tmpMessage = JSON.parse(JSON.stringify(tmpMessage));
tmpMessage.status = this.$enums.MESSAGE_STATUS.FAILED;
this.chatStore.insertMessage(tmpMessage, chat);
this.chatStore.updateMessage(tmpMessage, chat);
})
}
})
@ -393,10 +397,12 @@ export default {
this.switchChatTabBox('tools')
},
switchChatTabBox(chatTabBox) {
this.chatTabBox = chatTabBox;
this.reCalChatMainHeight();
if (chatTabBox != 'tools' && this.$refs.fileUpload) {
this.$refs.fileUpload.hide()
if (this.chatTabBox != chatTabBox) {
this.chatTabBox = chatTabBox;
if (chatTabBox != 'tools' && this.$refs.fileUpload) {
this.$refs.fileUpload.hide()
}
setTimeout(() => this.reCalChatMainHeight(), 30);
}
},
selectEmoji(emoText) {
@ -454,7 +460,7 @@ export default {
data.width = size.width;
data.height = size.height;
msgInfo.content = JSON.stringify(data)
this.chatStore.insertMessage(msgInfo, chat);
this.chatStore.updateMessage(msgInfo, chat);
this.scrollToBottom();
})
return true;
@ -467,13 +473,13 @@ export default {
msgInfo.id = m.id;
msgInfo.status = m.status;
this.isReceipt = false;
this.chatStore.insertMessage(msgInfo, file.chat);
this.chatStore.updateMessage(msgInfo, file.chat);
})
},
onUploadImageFail(file, err) {
let msgInfo = JSON.parse(JSON.stringify(file.msgInfo));
msgInfo.status = this.$enums.MESSAGE_STATUS.FAILED;
this.chatStore.insertMessage(msgInfo, file.chat);
this.chatStore.updateMessage(msgInfo, file.chat);
},
onUploadFileBefore(file) {
//
@ -520,13 +526,13 @@ export default {
msgInfo.id = m.id;
msgInfo.status = m.status;
this.isReceipt = false;
this.chatStore.insertMessage(msgInfo, file.chat);
this.chatStore.updateMessage(msgInfo, file.chat);
})
},
onUploadFileFail(file, res) {
let msgInfo = JSON.parse(JSON.stringify(file.msgInfo));
msgInfo.status = this.$enums.MESSAGE_STATUS.FAILED;
this.chatStore.insertMessage(msgInfo, file.chat);
this.chatStore.updateMessage(msgInfo, file.chat);
},
onResendMessage(msgInfo) {
if (msgInfo.type != this.$enums.MESSAGE_TYPE.TEXT) {
@ -551,12 +557,12 @@ export default {
tmpMessage.id = m.id;
tmpMessage.status = m.status;
tmpMessage.content = m.content;
this.chatStore.insertMessage(tmpMessage, chat);
this.chatStore.updateMessage(tmpMessage, chat);
}).catch(() => {
//
tmpMessage = JSON.parse(JSON.stringify(tmpMessage));
tmpMessage.status = this.$enums.MESSAGE_STATUS.FAILED;
this.chatStore.insertMessage(tmpMessage, chat);
this.chatStore.updateMessage(tmpMessage, chat);
})
},
onDeleteMessage(msgInfo) {
@ -670,9 +676,13 @@ export default {
if (this.scrollTop < 10) {
//
this.holdingScrollBar();
} else {
// ios
const delays = [100, 300, 1000];
delays.forEach(delay => setTimeout(() => this.scrollTop += 5, delay))
}
});
}, 50)
}, 10)
},
onShowMore() {
if (this.isGroup) {
@ -815,39 +825,42 @@ export default {
}
},
reCalChatMainHeight() {
setTimeout(() => {
let h = this.windowHeight;
//
h -= 50;
//
if (this.isShowKeyBoard || this.chatTabBox != 'none') {
h -= this.keyboardHeight;
this.scrollToBottom();
}
// #ifndef H5
// h5
h -= uni.getSystemInfoSync().statusBarHeight;
// #endif
this.chatMainHeight = h;
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);
})
let sysInfo = uni.getSystemInfoSync();
let h = this.windowHeight;
//
h -= 50;
//
if (this.isShowKeyBoard || this.chatTabBox != 'none') {
// ios app
// #ifdef APP-PLUS
if (sysInfo.platform == 'ios') {
h += this.screenHeight - this.windowHeight;
}
// #endif
}, 30)
h -= this.keyboardHeight;
this.scrollToBottom();
}
// APP
// #ifdef APP-PLUS
h -= sysInfo.statusBarHeight;
// #endif
this.chatMainHeight = h;
// #ifndef APP
// ios
if (uni.getSystemInfoSync().platform == 'ios') {
//
const delays = [50, 100, 500];
delays.forEach((delay) => {
setTimeout(() => {
uni.pageScrollTo({
scrollTop: 0,
duration: 10
});
}, delay);
})
}
// #endif
},
listenKeyBoard() {
// #ifdef H5
@ -857,12 +870,13 @@ export default {
return;
}
if (uni.getSystemInfoSync().platform == 'ios') {
// ios h5
window.addEventListener('focusin', this.focusInListener);
window.addEventListener('focusout', this.focusOutListener);
// ios13
if (window.visualViewport) {
window.visualViewport.addEventListener('resize', this.resizeListener);
} else {
// ios h5
window.addEventListener('focusin', this.focusInListener);
window.addEventListener('focusout', this.focusOutListener);
}
} else {
// h5
@ -876,9 +890,12 @@ export default {
},
unListenKeyboard() {
// #ifdef H5
window.removeEventListener('resize', this.resizeListener);
window.removeEventListener('focusin', this.focusInListener);
window.removeEventListener('focusout', this.focusOutListener);
window.removeEventListener('resize', this.resizeListener);
if (window.visualViewport) {
window.visualViewport.removeEventListener('resize', this.resizeListener);
}
// #endif
// #ifndef H5
uni.offKeyboardHeightChange(this.keyBoardListener);
@ -889,7 +906,7 @@ export default {
if (this.isShowKeyBoard) {
this.keyboardHeight = res.height; //
}
this.reCalChatMainHeight();
setTimeout(() => this.reCalChatMainHeight(), 30);
},
resizeListener() {
let keyboardHeight = this.initHeight - window.innerHeight;
@ -897,20 +914,22 @@ export default {
if (window.visualViewport && uni.getSystemInfoSync().platform == 'ios') {
keyboardHeight = this.initHeight - window.visualViewport.height;
}
console.log("resizeListener:", window.visualViewport.height)
this.isShowKeyBoard = keyboardHeight > 150;
if (this.isShowKeyBoard) {
let isShowKeyBoard = keyboardHeight > 150;
if (isShowKeyBoard) {
this.keyboardHeight = keyboardHeight;
}
this.reCalChatMainHeight();
if (this.isShowKeyBoard != isShowKeyBoard) {
this.isShowKeyBoard = isShowKeyBoard;
setTimeout(() => this.reCalChatMainHeight(), 30);
}
},
focusInListener() {
this.isShowKeyBoard = true;
this.reCalChatMainHeight();
setTimeout(() => this.reCalChatMainHeight(), 30);
},
focusOutListener() {
this.isShowKeyBoard = false;
this.reCalChatMainHeight();
setTimeout(() => this.reCalChatMainHeight(), 30);
},
showBannedTip() {
let msgInfo = {
@ -1082,7 +1101,11 @@ export default {
//
this.listenKeyBoard();
//
this.windowHeight = uni.getSystemInfoSync().windowHeight;
this.screenHeight = uni.getSystemInfoSync().screenHeight;
this.reCalChatMainHeight();
this.$nextTick(() => {
// windowHeight
this.windowHeight = uni.getSystemInfoSync().windowHeight;
this.reCalChatMainHeight();
this.scrollToBottom();
@ -1132,10 +1155,10 @@ export default {
}
.chat-main-box {
// #ifdef H5
// #ifndef APP-PLUS
top: $im-nav-bar-height;
// #endif
// #ifndef H5
// #ifdef APP-PLUS
top: calc($im-nav-bar-height + var(--status-bar-height));
// #endif
position: fixed;
@ -1206,7 +1229,7 @@ export default {
border-top: $im-border solid 1px;
background-color: $im-bg;
min-height: 80rpx;
margin-bottom: 14rpx;
padding-bottom: 14rpx;
.iconfont {
font-size: 60rpx;
@ -1250,44 +1273,45 @@ export default {
background-color: $im-bg;
.chat-tools {
display: flex;
flex-wrap: wrap;
align-items: top;
height: 310px;
padding: 40rpx;
box-sizing: border-box;
.chat-tools-item {
width: 25%;
padding: 16rpx;
box-sizing: border-box;
.chat-tools-list {
display: flex;
flex-direction: column;
align-items: center;
flex-wrap: wrap;
align-content: center;
.chat-tools-item {
width: 25%;
padding: 16rpx;
box-sizing: border-box;
display: flex;
flex-direction: column;
align-items: center;
.tool-icon {
padding: 26rpx;
font-size: 54rpx;
border-radius: 20%;
background-color: white;
color: $icon-color;
.tool-icon {
padding: 26rpx;
font-size: 54rpx;
border-radius: 20%;
background-color: white;
color: $icon-color;
&:active {
background-color: $im-bg-active;
&:active {
background-color: $im-bg-active;
}
}
}
.tool-name {
height: 60rpx;
line-height: 60rpx;
font-size: 28rpx;
.tool-name {
height: 60rpx;
line-height: 60rpx;
font-size: 28rpx;
}
}
}
}
.chat-emotion {
height: 310px;
padding: 20rpx;
padding: 40rpx;
box-sizing: border-box;
.emotion-item-list {

10
im-uniapp/pages/friend/friend.vue

@ -12,7 +12,7 @@
温馨提示您现在还没有任何好友快点击右上方'+'按钮添加好友吧~
</view>
<view class="friend-items" v-else>
<up-index-list :index-list="friendIdx" :sticky="false" :custom-nav-height="50">
<up-index-list :index-list="friendIdx" :sticky="false" :custom-nav-height="customNavHeight">
<template v-for="(friends, i) in friendGroups">
<up-index-item>
<up-index-anchor :text="friendIdx[i] == '*' ? '在线' : friendIdx[i]"></up-index-anchor>
@ -97,6 +97,14 @@ export default {
},
hasFriends() {
return this.friendStore.friends.some(f => !f.deleted);
},
customNavHeight() {
let h = 50;
// #ifdef APP-PLUS
h += uni.getSystemInfoSync().statusBarHeight;
// #endif
console.log("customNavHeight:",h)
return h;
}
}
}

26
im-uniapp/store/chatStore.js

@ -497,24 +497,28 @@ export default defineStore('chatStore', {
if (!chat) {
return null;
}
for (let idx = chat.messages.length - 1; idx >= 0; idx--) {
// 通过id判断
if (msgInfo.id && chat.messages[idx].id) {
if (msgInfo.id == chat.messages[idx].id) {
return chat.messages[idx];
// 通过id判断
if (msgInfo.id) {
for (let idx = chat.messages.length - 1; idx >= 0; idx--) {
let m = chat.messages[idx];
if (m.id && msgInfo.id == m.id) {
return m;
}
// 如果id比要查询的消息小,说明没有这条消息
if (msgInfo.id > chat.messages[idx].id) {
if (m.id && m.id < msgInfo.id) {
break;
}
}
// 正在发送中的消息可能没有id,只有tmpId
if (msgInfo.tmpId && chat.messages[idx].tmpId) {
if (msgInfo.tmpId == chat.messages[idx].tmpId) {
return chat.messages[idx];
}
// 正在发送中的消息可能没有id,只有tmpId
if (msgInfo.tmpId) {
for (let idx = chat.messages.length - 1; idx >= 0; idx--) {
let m = chat.messages[idx];
if (m.tmpId && msgInfo.tmpId == m.tmpId) {
return m;
}
// 如果id比要查询的消息小,说明没有这条消息
if (msgInfo.tmpId > chat.messages[idx].tmpId) {
if (m.tmpId && m.tmpId < msgInfo.tmpId) {
break;
}
}

44
im-uniapp/store/friendStore.js

@ -5,36 +5,27 @@ import { TERMINAL_TYPE } from '../common/enums.js'
export default defineStore('friendStore', {
state: () => {
return {
friends: [],
friendMap: new Map(),
timer: null
}
},
actions: {
setFriends(friends) {
friends.forEach((f) => {
f.online = false;
f.onlineWeb = false;
f.onlineApp = false;
})
this.friends = friends;
this.friendMap.clear();
friends.forEach(f => this.friendMap.set(f.id, f))
},
updateFriend(friend) {
let f = this.findFriend(friend.id);
let copy = JSON.parse(JSON.stringify(f));
Object.assign(f, friend);
f.online = copy.online;
f.onlineWeb = copy.onlineWeb;
f.onlineApp = copy.onlineApp;
friend.online = f.online;
friend.onlineWeb = f.onlineWeb;
friend.onlineApp = f.onlineApp;
this.friendMap.set(friend.id, friend)
},
removeFriend(id) {
this.friends.filter(f => f.id == id).forEach(f => f.deleted = true);
this.friendMap.get(id).deleted = true;
},
addFriend(friend) {
if (this.friends.some((f) => f.id == friend.id)) {
this.updateFriend(friend)
} else {
this.friends.unshift(friend);
}
this.friendMap.set(friend.id, friend)
},
setOnlineStatus(onlineTerminals) {
this.friends.forEach((f) => {
@ -68,12 +59,11 @@ export default defineStore('friendStore', {
}, 30000)
},
setDnd(id, isDnd) {
let friend = this.findFriend(id);
friend.isDnd = isDnd;
this.friendMap.get(id).isDnd = isDnd;
},
clear() {
this.friendMap.clear();
clearTimeout(this.timer);
this.friends = [];
this.timer = null;
},
loadFriend() {
@ -92,11 +82,15 @@ export default defineStore('friendStore', {
}
},
getters: {
isFriend: (state) => (userId) => {
return state.friends.filter((f) => !f.deleted).some((f) => f.id == userId);
friends: (state) => {
return Array.from(state.friendMap.values());
},
findFriend: (state) => (id) => {
return state.friends.find((f) => f.id == id);
findFriend: (state) => (userId) => {
return state.friendMap.get(userId);
},
isFriend: (state) => (userId) => {
let f = state.findFriend(userId)
return f && !f.deleted
}
}
})

30
im-uniapp/store/groupStore.js

@ -4,33 +4,28 @@ import http from '@/common/request';
export default defineStore('groupStore', {
state: () => {
return {
groups: []
groupMap: new Map()
}
},
actions: {
setGroups(groups) {
this.groups = groups;
this.groupMap.clear();
groups.forEach(g => this.groupMap.set(g.id, g))
},
addGroup(group) {
if (this.groups.some((g) => g.id == group.id)) {
this.updateGroup(group);
} else {
this.groups.unshift(group);
}
this.groupMap.set(group.id, group);
},
removeGroup(id) {
this.groups.filter(g => g.id == id).forEach(g => g.quit = true);
this.groupMap.get(id).quit = true;
},
updateGroup(group) {
let g = this.findGroup(group.id);
Object.assign(g, group);
this.groupMap.set(group.id, group);
},
setDnd(id, isDnd) {
let group = this.findGroup(id);
group.isDnd = isDnd;
this.groupMap.get(id).isDnd = isDnd;
},
clear() {
this.groups = [];
this.groupMap.clear();
},
loadGroup() {
return new Promise((resolve, reject) => {
@ -47,8 +42,15 @@ export default defineStore('groupStore', {
}
},
getters: {
groups: (state) => {
return Array.from(state.groupMap.values());
},
findGroup: (state) => (id) => {
return state.groups.find((g) => g.id == id);
return state.groupMap.get(id);
},
isGroup: (state) => (id) => {
let group = state.findGroup(id);
return group && !group.quit
}
}
})

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

@ -163,13 +163,13 @@ export default {
msgInfo.id = m.id;
msgInfo.status = m.status;
this.isReceipt = false;
this.chatStore.insertMessage(msgInfo, file.chat);
this.chatStore.updateMessage(msgInfo, file.chat);
})
},
onImageFail(e, file) {
let msgInfo = JSON.parse(JSON.stringify(file.msgInfo));
msgInfo.status = this.$enums.MESSAGE_STATUS.FAILED;
this.chatStore.insertMessage(msgInfo, file.chat);
this.chatStore.updateMessage(msgInfo, file.chat);
},
onImageBefore(file) {
//
@ -208,7 +208,7 @@ export default {
data.width = size.width;
data.height = size.height;
msgInfo.content = JSON.stringify(data)
this.chatStore.insertMessage(msgInfo, chat);
this.chatStore.updateMessage(msgInfo, chat);
this.scrollToBottom();
})
},
@ -226,13 +226,13 @@ export default {
msgInfo.status = m.status;
this.isReceipt = false;
this.refreshPlaceHolder();
this.chatStore.insertMessage(msgInfo, file.chat);
this.chatStore.updateMessage(msgInfo, file.chat);
})
},
onFileFail(e, file) {
let msgInfo = JSON.parse(JSON.stringify(file.msgInfo));
msgInfo.status = this.$enums.MESSAGE_STATUS.FAILED;
this.chatStore.insertMessage(msgInfo, file.chat);
this.chatStore.updateMessage(msgInfo, file.chat);
},
onFileBefore(file) {
//
@ -384,7 +384,7 @@ export default {
//
tmpMessage.id = m.id;
tmpMessage.status = m.status;
this.chatStore.insertMessage(tmpMessage, chat);
this.chatStore.updateMessage(tmpMessage, chat);
//
this.moveChatToTop();
//
@ -397,7 +397,7 @@ export default {
this.refreshPlaceHolder();
}).catch(() => {
tmpMessage.status = this.$enums.MESSAGE_STATUS.FAILED;
this.chatStore.insertMessage(tmpMessage, this.chat);
this.chatStore.updateMessage(tmpMessage, this.chat);
})
},
fillTargetId(msgInfo, targetId) {
@ -483,11 +483,11 @@ export default {
tmpMessage.id = m.id;
tmpMessage.status = m.status;
tmpMessage.content = m.content;
this.chatStore.insertMessage(tmpMessage, chat);
this.chatStore.updateMessage(tmpMessage, chat);
}).catch(() => {
//
tmpMessage.status = this.$enums.MESSAGE_STATUS.FAILED;
this.chatStore.insertMessage(tmpMessage, chat);
this.chatStore.updateMessage(tmpMessage, chat);
}).finally(() => {
this.isReceipt = false;
resolve();
@ -522,11 +522,11 @@ export default {
tmpMessage.id = m.id;
tmpMessage.status = m.status;
tmpMessage.content = m.content;
this.chatStore.insertMessage(tmpMessage, chat);
this.chatStore.updateMessage(tmpMessage, chat);
}).catch(() => {
//
tmpMessage.status = this.$enums.MESSAGE_STATUS.FAILED;
this.chatStore.insertMessage(tmpMessage, chat);
this.chatStore.updateMessage(tmpMessage, chat);
}).finally(() => {
this.scrollToBottom();
});

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

@ -23,10 +23,7 @@
<span class="message-text" v-if="isTextMessage" v-html="htmlText"></span>
<div class="message-image" v-else-if="msgInfo.type == $enums.MESSAGE_TYPE.IMAGE"
@click="showFullImageBox()">
<div v-loading="sending" element-loading-text="发送中.."
element-loading-background="rgba(0, 0, 0, 0.4)">
<img :style="imageStyle" :src="contentData.thumbUrl" loading="lazy" />
</div>
</div>
<div class="message-file" v-else-if="msgInfo.type == $enums.MESSAGE_TYPE.FILE">
<div class="chat-file-box" v-loading="sending">

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

@ -294,7 +294,7 @@ export default defineStore('chatStore', {
let chat = this.findChatByFriend(friend.id);
// 更新会话中的群名和头像
if (chat && (chat.headImage != friend.headImage ||
chat.showName != friend.nickName)) {
chat.showName != friend.nickName)) {
chat.headImage = friend.headImage;
chat.showName = friend.nickName;
chat.stored = false;
@ -305,7 +305,7 @@ export default defineStore('chatStore', {
let chat = this.findChatByFriend(user.id);
// 更新会话中的昵称和头像
if (chat && (chat.headImage != user.headImageThumb ||
chat.showName != user.nickName)) {
chat.showName != user.nickName)) {
chat.headImage = user.headImageThumb;
chat.showName = user.nickName;
chat.stored = false;
@ -315,7 +315,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;
@ -466,7 +466,8 @@ export default defineStore('chatStore', {
// 冷热消息合并
let chat = Object.assign({}, coldChat, hotChat);
if (hotChat && coldChat) {
chat.messages = coldChat.messages.concat(hotChat.messages)
chat.messages = coldChat.messages.concat(hotChat
.messages)
}
// 历史版本没有readedMessageIdx字段,做兼容一下
chat.readedMessageIdx = chat.readedMessageIdx || 0;
@ -522,24 +523,27 @@ export default defineStore('chatStore', {
if (!chat) {
return null;
}
for (let idx = chat.messages.length - 1; idx >= 0; idx--) {
// 通过id判断
if (msgInfo.id && chat.messages[idx].id) {
if (msgInfo.id == chat.messages[idx].id) {
return chat.messages[idx];
if (msgInfo.id) {
for (let idx = chat.messages.length - 1; idx >= 0; idx--) {
let m = chat.messages[idx];
if (m.id && msgInfo.id == m.id) {
return m;
}
// 如果id比要查询的消息小,说明没有这条消息
if (msgInfo.id > chat.messages[idx].id) {
if (m.id && m.id < msgInfo.id) {
break;
}
}
// 正在发送中的消息可能没有id,只有tmpId
if (msgInfo.tmpId && chat.messages[idx].tmpId) {
if (msgInfo.tmpId == chat.messages[idx].tmpId) {
return chat.messages[idx];
}
// 正在发送中的消息可能没有id,只有tmpId
if (msgInfo.tmpId) {
for (let idx = chat.messages.length - 1; idx >= 0; idx--) {
let m = chat.messages[idx];
if (m.tmpId && msgInfo.tmpId == m.tmpId) {
return m;
}
// 如果id比要查询的消息小,说明没有这条消息
if (msgInfo.tmpId > chat.messages[idx].tmpId) {
if (m.tmpId && m.tmpId < msgInfo.tmpId) {
break;
}
}

BIN
截图/app/1.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

BIN
截图/app/2.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

After

Width:  |  Height:  |  Size: 1.8 MiB

BIN
截图/web/单人通话.jpg

Binary file not shown.

Before

Width:  |  Height:  |  Size: 461 KiB

After

Width:  |  Height:  |  Size: 168 KiB

BIN
截图/web/多人通话.jpg

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

After

Width:  |  Height:  |  Size: 202 KiB

BIN
截图/web/好友.jpg

Binary file not shown.

Before

Width:  |  Height:  |  Size: 279 KiB

After

Width:  |  Height:  |  Size: 141 KiB

BIN
截图/web/私聊.jpg

Binary file not shown.

Before

Width:  |  Height:  |  Size: 460 KiB

After

Width:  |  Height:  |  Size: 144 KiB

BIN
截图/web/群列表.jpg

Binary file not shown.

Before

Width:  |  Height:  |  Size: 452 KiB

After

Width:  |  Height:  |  Size: 189 KiB

BIN
截图/web/群聊.jpg

Binary file not shown.

Before

Width:  |  Height:  |  Size: 205 KiB

After

Width:  |  Height:  |  Size: 187 KiB

BIN
截图/消息推送集群化.jpg

Binary file not shown.

Before

Width:  |  Height:  |  Size: 133 KiB

After

Width:  |  Height:  |  Size: 133 KiB

Loading…
Cancel
Save