diff --git a/README.md b/README.md
index 7df8fde..87f6af2 100644
--- a/README.md
+++ b/README.md
@@ -13,14 +13,13 @@
#### 近期更新
-发布2.0版本,本次更新主要是加入了uniapp版本:
+发布2.0版本,本次更新加入了uniapp版本:
- 支持移动端和web端同时在线,多端消息同步
- 目前仅兼容h5和微信小程序,后续会继续兼容更多终端类型
-- 页面风格优化:表情包更新、自动生成文字头像等
-
-感兴趣的小伙伴,可在下方扫码体验
-
+- 聊天窗口加入已读未读显示
+- 群聊加入@功能
+- 界面风格升级,表情包更新、生成文字头像等
#### 在线体验
diff --git a/im-ui/src/components/chat/ChatBox.vue b/im-ui/src/components/chat/ChatBox.vue
index 346d039..82744db 100644
--- a/im-ui/src/components/chat/ChatBox.vue
+++ b/im-ui/src/components/chat/ChatBox.vue
@@ -219,7 +219,6 @@
},
createSendText() {
let sendText = ""
- console.log(this.$refs.editBox.childNodes);
this.$refs.editBox.childNodes.forEach((node) => {
if (node.nodeName == "#text") {
sendText += node.textContent;
@@ -233,10 +232,8 @@
},
createAtUserIds() {
let ids = [];
- console.log(this.$refs.editBox.childNodes);
this.$refs.editBox.childNodes.forEach((node) => {
if (node.nodeName == "SPAN") {
- console.log(node);
ids.push(node.dataset.id);
}
})
diff --git a/im-ui/src/view/Login.vue b/im-ui/src/view/Login.vue
index 9ae9728..dc80110 100644
--- a/im-ui/src/view/Login.vue
+++ b/im-ui/src/view/Login.vue
@@ -17,6 +17,13 @@
修改拉取离线消息机制:用户登录后,自动从服务器同步最近1个月的消息
+
+
最近更新(2023-11-12):
+
+ - 群聊加入@功能
+ - 聊天输入框支持显示emoji表情
+
+
项目依旧完全开源,可内网部署。如果项目对您有帮助,请帮忙点个star:
diff --git a/im-uniapp/common/wssocket.js b/im-uniapp/common/wssocket.js
index 1f3a04a..deedfae 100644
--- a/im-uniapp/common/wssocket.js
+++ b/im-uniapp/common/wssocket.js
@@ -4,9 +4,14 @@ let messageCallBack = null;
let closeCallBack = null;
let isConnect = false; //连接标识 避免重复连接
let rec = null;
+let isInit = false;
let init = () => {
-
+ // 防止重复初始化
+ if (isInit) {
+ return;
+ }
+ isInit = true;
uni.onSocketOpen((res) => {
console.log("WebSocket连接已打开");
isConnect = true;
@@ -21,7 +26,7 @@ let init = () => {
data: JSON.stringify(loginInfo)
});
})
-
+
uni.onSocketMessage((res) => {
let sendInfo = JSON.parse(res.data)
if (sendInfo.cmd == 0) {
@@ -32,17 +37,17 @@ let init = () => {
heartCheck.reset();
} else {
// 其他消息转发出去
- console.log("接收到消息",sendInfo);
+ console.log("接收到消息", sendInfo);
messageCallBack && messageCallBack(sendInfo.cmd, sendInfo.data)
}
})
-
+
uni.onSocketClose((res) => {
console.log('WebSocket连接关闭')
isConnect = false; //断开后修改标识
closeCallBack && closeCallBack(res);
})
-
+
uni.onSocketError((e) => {
console.log(e)
isConnect = false; //连接断开修改标识
@@ -53,7 +58,7 @@ let init = () => {
})
};
-let connect = (url, token)=>{
+let connect = (url, token) => {
wsurl = url;
accessToken = token;
if (isConnect) {
@@ -67,23 +72,23 @@ let connect = (url, token)=>{
fail: (e) => {
console.log(e);
console.log("websocket连接失败,10s后重连");
- setTimeout(()=>{
+ setTimeout(() => {
connect();
- },10000)
+ }, 10000)
}
});
}
//定义重连函数
-let reconnect = (wsurl,accessToken) => {
+let reconnect = (wsurl, accessToken) => {
console.log("尝试重新连接");
- if (isConnect){
+ if (isConnect) {
//如果已经连上就不在重连了
- return;
+ return;
}
rec && clearTimeout(rec);
rec = setTimeout(function() { // 延迟15秒重连 避免过多次过频繁请求重连
- connect(wsurl,accessToken);
+ connect(wsurl, accessToken);
}, 15000);
};
@@ -98,8 +103,8 @@ let close = () => {
console.log("关闭websocket连接");
isConnect = false;
},
- fail:(e)=>{
- console.log("关闭websocket连接失败",e);
+ fail: (e) => {
+ console.log("关闭websocket连接失败", e);
}
});
};
diff --git a/im-uniapp/components/chat-at-box/chat-at-box.vue b/im-uniapp/components/chat-at-box/chat-at-box.vue
new file mode 100644
index 0000000..51ad5b2
--- /dev/null
+++ b/im-uniapp/components/chat-at-box/chat-at-box.vue
@@ -0,0 +1,176 @@
+
+
+
+
+ 选择要提醒的人
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ m.aliasName}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
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 c627bc6..8085022 100644
--- a/im-uniapp/components/chat-message-item/chat-message-item.vue
+++ b/im-uniapp/components/chat-message-item/chat-message-item.vue
@@ -7,7 +7,7 @@
-
+
{{showName}}
diff --git a/im-uniapp/components/head-image/head-image.vue b/im-uniapp/components/head-image/head-image.vue
index b3dd3d1..cdb3248 100644
--- a/im-uniapp/components/head-image/head-image.vue
+++ b/im-uniapp/components/head-image/head-image.vue
@@ -1,5 +1,5 @@
-
+
@@ -26,7 +26,7 @@
},
size: {
type: Number,
- default: 50
+ default: 20
},
url: {
type: String
@@ -77,6 +77,8 @@
position: relative;
overflow: hidden;
border-radius: 10%;
+ border: 1px solid #ccc;
+ vertical-align: bottom;
}
.avatar-text {
diff --git a/im-uniapp/pages/chat/chat-box.vue b/im-uniapp/pages/chat/chat-box.vue
index af562d5..a2a84a5 100644
--- a/im-uniapp/pages/chat/chat-box.vue
+++ b/im-uniapp/pages/chat/chat-box.vue
@@ -1,5 +1,5 @@
-
+
+ :scroll-into-view="'chat-item-'+scrollMsgIdx">
-
+
+
+ :
+
+
+
+
+
+
+
+
-
+
-
+
-
@@ -53,7 +64,6 @@
文件
-
语音输入
@@ -62,9 +72,7 @@
呼叫
-
-
-
+
@@ -92,6 +101,7 @@
chatTabBox: 'none',
showKeyBoard: false,
keyboardHeight: 322,
+ atUserIds: [],
showMinIdx: 0 // 下标小于showMinIdx的消息不显示,否则可能很卡
}
},
@@ -102,6 +112,18 @@
icon: "none"
})
},
+ openAtBox() {
+ this.$refs.atBox.init(this.atUserIds);
+ this.$refs.atBox.open();
+ },
+ onAtComplete(atUserIds) {
+ this.atUserIds = atUserIds;
+ },
+ onLongPressHead(msgInfo){
+ if(!msgInfo.selfSend && this.chat.type=="GROUP" && this.atUserIds.indexOf(msgInfo.sendId)<0){
+ this.atUserIds.push(msgInfo.sendId);
+ }
+ },
headImage(msgInfo) {
if (this.chat.type == 'GROUP') {
let member = this.groupMembers.find((m) => m.userId == msgInfo.sendId);
@@ -117,17 +139,19 @@
} else {
return msgInfo.selfSend ? this.mine.nickName : this.chat.showName
}
-
},
sendTextMessage() {
- if (!this.sendText.trim()) {
+ if (!this.sendText.trim() && this.atUserIds.length==0) {
return uni.showToast({
title: "不能发送空白信息",
icon: "none"
});
+
}
+ let atText = this.createAtText()
let msgInfo = {
- content: this.sendText,
+ content: this.sendText + atText,
+ atUserIds: this.atUserIds,
type: 0
}
// 填充对方id
@@ -148,8 +172,20 @@
}).finally(() => {
// 滚动到底部
this.scrollToBottom();
+ // 清空@用户列表
+ this.atUserIds = [];
});
},
+ createAtText() {
+ let atText = "";
+ this.atUserIds.forEach((id) => {
+ let member = this.groupMembers.find((m)=>m.userId==id);
+ if (member) {
+ atText += ` @${member.aliasName}`;
+ }
+ })
+ return atText;
+ },
fillTargetId(msgInfo, targetId) {
if (this.chat.type == "GROUP") {
msgInfo.groupId = targetId;
@@ -175,7 +211,7 @@
return;
}
this.$nextTick(() => {
- console.log("scrollToMsgIdx",this.scrollMsgIdx)
+ console.log("scrollToMsgIdx", this.scrollMsgIdx)
this.scrollMsgIdx = idx;
});
@@ -450,6 +486,20 @@
},
unreadCount() {
return this.chat.unreadCount;
+ },
+ atUserItems(){
+ let atUsers = [];
+ this.atUserIds.forEach((id)=>{
+ if(id==-1){
+ atUsers.push({id:-1,aliasName:"全体成员"})
+ return;
+ }
+ let member = this.groupMembers.find((m)=>m.userId==id);
+ if(member){
+ atUsers.push(member);
+ }
+ })
+ return atUsers;
}
},
watch: {
@@ -543,6 +593,34 @@
}
}
+ .chat-at-bar {
+ display: flex;
+ align-items: center;
+ padding: 0 10rpx;
+ border: #dddddd solid 1px;
+
+ .icon-at {
+ font-size: 35rpx;
+ color: darkblue;
+ font-weight: 600;
+ }
+
+ .chat-at-scroll-box {
+ flex: 1;
+ width: 80%;
+ .chat-at-items {
+ display: flex;
+ align-items: center;
+ height: 70rpx;
+
+ .chat-at-item {
+ padding: 0 3rpx;
+ }
+ }
+ }
+
+ }
+
.send-bar {
display: flex;
align-items: center;
@@ -552,7 +630,7 @@
background-color: white;
.iconfont {
- font-size: 70rpx;
+ font-size: 60rpx;
margin: 3rpx;
}
@@ -571,7 +649,6 @@
.send-text-area {
width: 100%;
}
-
}
.btn-send {
@@ -586,7 +663,6 @@
background-color: whitesmoke;
.chat-tools {
-
display: flex;
flex-wrap: wrap;
justify-content: space-between;
@@ -609,7 +685,6 @@
height: 60rpx;
line-height: 60rpx;
font-size: 25rpx;
-
}
}
}
diff --git a/im-uniapp/pages/group/group-invite.vue b/im-uniapp/pages/group/group-invite.vue
index b2e5af7..93596d8 100644
--- a/im-uniapp/pages/group/group-invite.vue
+++ b/im-uniapp/pages/group/group-invite.vue
@@ -17,9 +17,7 @@
-
-
-
+
diff --git a/im-uniapp/static/icon/iconfont.css b/im-uniapp/static/icon/iconfont.css
index e41ef1b..b437e6f 100644
--- a/im-uniapp/static/icon/iconfont.css
+++ b/im-uniapp/static/icon/iconfont.css
@@ -1,6 +1,6 @@
@font-face {
font-family: "iconfont"; /* Project id 4272106 */
- src: url('iconfont.ttf?t=1697348383625') format('truetype');
+ src: url('iconfont.ttf?t=1699795609670') format('truetype');
}
.iconfont {
@@ -11,6 +11,10 @@
-moz-osx-font-smoothing: grayscale;
}
+.icon-at:before {
+ content: "\e7de";
+}
+
.icon-man:before {
content: "\e615";
}
diff --git a/im-uniapp/static/icon/iconfont.ttf b/im-uniapp/static/icon/iconfont.ttf
index 289cebb..de77a92 100644
Binary files a/im-uniapp/static/icon/iconfont.ttf and b/im-uniapp/static/icon/iconfont.ttf differ
diff --git a/im-uniapp/store/chatStore.js b/im-uniapp/store/chatStore.js
index 85a7b99..67a2bf3 100644
--- a/im-uniapp/store/chatStore.js
+++ b/im-uniapp/store/chatStore.js
@@ -158,6 +158,7 @@ export default {
chat.atAll = true;
}
}
+
// 记录消息的最大id
if (msgInfo.id && type == "PRIVATE" && msgInfo.id > state.privateMsgMaxId) {
state.privateMsgMaxId = msgInfo.id;