diff --git a/src/views/im/home/composables/useMessagePuller.ts b/src/views/im/home/composables/useMessagePuller.ts
index a114e249b..ca51b5c3c 100644
--- a/src/views/im/home/composables/useMessagePuller.ts
+++ b/src/views/im/home/composables/useMessagePuller.ts
@@ -19,7 +19,11 @@ import {
isFriendChatTip,
isFriendNotification
} from '../../utils/constants'
-import { MESSAGE_PRIVATE_PULL_SIZE, MESSAGE_GROUP_PULL_SIZE } from '../../utils/config'
+import {
+ MESSAGE_PRIVATE_PULL_SIZE,
+ MESSAGE_GROUP_PULL_SIZE,
+ MESSAGE_PRIVATE_READ_ENABLED
+} from '../../utils/config'
import { useUserStore } from '@/store/modules/user'
import type { Message } from '../types'
@@ -220,8 +224,12 @@ export const useMessagePuller = () => {
// 重连 / 冷启动后补齐当前激活私聊会话的「对方已读位置」
// 离线期间错过的 RECEIPT 推送会被这里补回;其他私聊会话等用户点开时由 Index.vue 的 watch 触发
+ // 私聊已读关闭时跳过,避免打到已禁用接口触发错误日志
const active = conversationStore.activeConversation
- if (active && active.type === ImConversationType.PRIVATE) {
+ if (
+ MESSAGE_PRIVATE_READ_ENABLED
+ && active && active.type === ImConversationType.PRIVATE
+ ) {
try {
const maxReadId = await apiGetPrivateMaxReadMessageId(active.targetId)
if (maxReadId) {
diff --git a/src/views/im/home/composables/useMessageSender.ts b/src/views/im/home/composables/useMessageSender.ts
index 3349d5472..74a6e9ffe 100644
--- a/src/views/im/home/composables/useMessageSender.ts
+++ b/src/views/im/home/composables/useMessageSender.ts
@@ -18,6 +18,7 @@ import {
type TextMessage
} from '../../utils/message'
import { ImMessageType, ImMessageStatus, ImConversationType } from '../../utils/constants'
+import { MESSAGE_PRIVATE_READ_ENABLED, MESSAGE_GROUP_READ_ENABLED } from '../../utils/config'
import type { Conversation, Message } from '../types'
import { useUserStore } from '@/store/modules/user'
@@ -229,8 +230,12 @@ export const useMessageSender = () => {
if (!maxMessageId) {
return
}
- // 接口调用:私聊 / 群聊接口签名一致,按会话类型分发;失败仅记录日志,不回退本地已读状态
+ // 接口调用:按会话类型分发,并按对应已读开关控制;失败仅记录日志,不回退本地已读状态
const isPrivate = conversation.type === ImConversationType.PRIVATE
+ const readEnabled = isPrivate ? MESSAGE_PRIVATE_READ_ENABLED : MESSAGE_GROUP_READ_ENABLED
+ if (!readEnabled) {
+ return
+ }
try {
await (isPrivate
? apiReadPrivateMessages(conversation.targetId, maxMessageId)
@@ -255,6 +260,10 @@ export const useMessageSender = () => {
if (!peerId) {
return
}
+ // 私聊已读关闭:跳过对方已读位置同步,避免无谓接口调用
+ if (!MESSAGE_PRIVATE_READ_ENABLED) {
+ return
+ }
try {
// 拉取对方已读到的最大消息 id
const maxReadId = await apiGetPrivateMaxReadMessageId(peerId)
diff --git a/src/views/im/home/pages/conversation/components/input/MessageInput.vue b/src/views/im/home/pages/conversation/components/input/MessageInput.vue
index 8cb1fa077..f023e153c 100644
--- a/src/views/im/home/pages/conversation/components/input/MessageInput.vue
+++ b/src/views/im/home/pages/conversation/components/input/MessageInput.vue
@@ -108,9 +108,9 @@
-
+
-
+
发 送
@@ -175,6 +175,7 @@ import { useMediaUploader } from '@/views/im/home/composables/useMediaUploader'
import { useMuteOverlay } from '@/views/im/home/composables/useMuteOverlay'
import { getConversationKey } from '@/views/im/utils/conversation'
import { ImConversationType, ImMessageType } from '@/views/im/utils/constants'
+import { MESSAGE_GROUP_READ_ENABLED } from '@/views/im/utils/config'
import {
serializeMessage,
type FaceMessage,
diff --git a/src/views/im/home/pages/conversation/components/message/MessageItem.vue b/src/views/im/home/pages/conversation/components/message/MessageItem.vue
index cac5b70ba..239afaaae 100644
--- a/src/views/im/home/pages/conversation/components/message/MessageItem.vue
+++ b/src/views/im/home/pages/conversation/components/message/MessageItem.vue
@@ -168,7 +168,12 @@ import {
isMediaMessageType,
isNormalMessage
} from '@/views/im/utils/constants'
-import { MESSAGE_TIME_TIP_GAP_MS, MESSAGE_RECALL_WINDOW_MS } from '@/views/im/utils/config'
+import {
+ MESSAGE_TIME_TIP_GAP_MS,
+ MESSAGE_RECALL_WINDOW_MS,
+ MESSAGE_PRIVATE_READ_ENABLED,
+ MESSAGE_GROUP_READ_ENABLED
+} from '@/views/im/utils/config'
import { pinGroupMessage as apiPinGroupMessage, cancelMuteMember } from '@/api/im/group'
import { removeGroupMember } from '@/api/im/group/member'
import {
@@ -399,8 +404,11 @@ const senderDisplayName = computed(() => {
)
})
-/** 私聊「已读 / 未读」态(仅对自己发送的私聊消息展示) */
+/** 私聊「已读 / 未读」态(仅对自己发送的私聊消息展示;私聊已读全局关闭时不再展示) */
const privateReadLabel = computed(() => {
+ if (!MESSAGE_PRIVATE_READ_ENABLED) {
+ return ''
+ }
if (!props.message.selfSend) {
return ''
}
@@ -416,8 +424,11 @@ const privateReadLabel = computed(() => {
return ''
})
-/**是否需要显示群回执 popover:自己发的群消息且后端开启了回执(NO_RECEIPT 表示发送时未要求回执,不渲染) */
+/**是否需要显示群回执 popover:自己发的群消息且后端开启了回执(NO_RECEIPT 表示发送时未要求回执,不渲染;群已读全局关闭时统一不展示) */
const showGroupReadStatus = computed(() => {
+ if (!MESSAGE_GROUP_READ_ENABLED) {
+ return false
+ }
if (!props.message.selfSend) {
return false
}
diff --git a/src/views/im/home/store/websocketStore.ts b/src/views/im/home/store/websocketStore.ts
index 88dfd9b30..534beb627 100644
--- a/src/views/im/home/store/websocketStore.ts
+++ b/src/views/im/home/store/websocketStore.ts
@@ -13,6 +13,7 @@ import {
isNormalMessage
} from '../../utils/constants'
import { playAudioTip } from '../../utils/message'
+import { MESSAGE_PRIVATE_READ_ENABLED, MESSAGE_GROUP_READ_ENABLED } from '../../utils/config'
import { useConversationStore } from './conversationStore'
import { useFriendStore, type FriendNotificationPayload } from './friendStore'
import { getFriendDisplayName } from '../../utils/user'
@@ -356,12 +357,14 @@ export const useImWebSocketStore = defineStore('imWebSocketStore', {
conversationStore.activeConversation?.type === ImConversationType.PRIVATE &&
conversationStore.activeConversation?.targetId === peerId
if (isActive) {
- // 聊天窗口打开 = 实际看到了:本端清未读 + 上报后端,让对方 UI 立刻切到"已读"
+ // 聊天窗口打开 = 实际看到了:本端清未读;私聊已读开启时再上报后端,让对方 UI 立刻切到"已读"
// 已读位置直接用刚到的消息 id(这条就是当前会话最大 id)
conversationStore.markActiveAsRead()
- apiReadPrivateMessages(peerId, websocketMessage.id).catch((e) => {
- console.warn('[IM WS] 自动已读上报失败', e)
- })
+ if (MESSAGE_PRIVATE_READ_ENABLED) {
+ apiReadPrivateMessages(peerId, websocketMessage.id).catch((e) => {
+ console.warn('[IM WS] 自动已读上报失败', e)
+ })
+ }
} else if (!conversation?.silent && isNormalMessage(websocketMessage.type)) {
// 非当前会话且未免打扰:响一下提示音(带节流,详见 playAudioTip);FRIEND_* 等系统事件不响
playAudioTip()
@@ -369,8 +372,11 @@ export const useImWebSocketStore = defineStore('imWebSocketStore', {
}
},
- /** 私聊 READ 事件:自己的其它终端在对方会话里标为已读,本端同步清零未读 */
+ /** 私聊 READ 事件:自己的其它终端在对方会话里标为已读,本端同步清零未读;私聊已读关闭时兜底忽略 */
handlePrivateRead(websocketMessage: ImPrivateMessageDTO) {
+ if (!MESSAGE_PRIVATE_READ_ENABLED) {
+ return
+ }
const conversationStore = useConversationStore()
const conversation = conversationStore.getConversation(
ImConversationType.PRIVATE,
@@ -385,9 +391,12 @@ export const useImWebSocketStore = defineStore('imWebSocketStore', {
/**
* 私聊 RECEIPT 事件:对方读了我的消息,把和对方会话里自己发的消息标为已读
* 后端将 maxReadId 编码在 DTO 的 id 字段(见 ImPrivateMessageDTO.ofReceipt),
- * 这里据此卡边界,避免把"回执在路上时刚发的消息"误标为已读。
+ * 这里据此卡边界,避免把"回执在路上时刚发的消息"误标为已读;私聊已读关闭时兜底忽略
*/
handlePrivateReceipt(websocketMessage: ImPrivateMessageDTO) {
+ if (!MESSAGE_PRIVATE_READ_ENABLED) {
+ return
+ }
if (!websocketMessage.id) {
return
}
@@ -463,11 +472,13 @@ export const useImWebSocketStore = defineStore('imWebSocketStore', {
conversationStore.activeConversation?.type === ImConversationType.GROUP &&
conversationStore.activeConversation?.targetId === websocketMessage.groupId
if (isActive) {
- // 群已读上报需要带 messageId(群消息以"读到第几条"的游标为准,区别于私聊只标 receiverId)
+ // 群已读上报需要带 messageId(群消息以"读到第几条"的游标为准,区别于私聊只标 receiverId);群已读关闭时仅本地清零
conversationStore.markActiveAsRead()
- apiReadGroupMessages(websocketMessage.groupId, websocketMessage.id).catch((e) => {
- console.warn('[IM WS] 自动已读上报失败', e)
- })
+ if (MESSAGE_GROUP_READ_ENABLED) {
+ apiReadGroupMessages(websocketMessage.groupId, websocketMessage.id).catch((e) => {
+ console.warn('[IM WS] 自动已读上报失败', e)
+ })
+ }
} else if (!conversation?.silent && isNormalMessage(websocketMessage.type)) {
// GROUP_* 群广播事件等系统消息不响提示音
playAudioTip()
@@ -477,8 +488,11 @@ export const useImWebSocketStore = defineStore('imWebSocketStore', {
// ==================== 群聊已读 / 回执 ====================
- /** 群聊 READ:自己其它终端在某群里标为已读,本端同步清零该群未读 */
+ /** 群聊 READ:自己其它终端在某群里标为已读,本端同步清零该群未读;群已读关闭时兜底忽略 */
handleGroupRead(websocketMessage: ImGroupMessageDTO) {
+ if (!MESSAGE_GROUP_READ_ENABLED) {
+ return
+ }
const conversationStore = useConversationStore()
const conversation = conversationStore.getConversation(
ImConversationType.GROUP,
@@ -490,8 +504,11 @@ export const useImWebSocketStore = defineStore('imWebSocketStore', {
conversationStore.saveConversations()
},
- /** 群聊 RECEIPT:更新某条群消息的 readCount / receiptStatus */
+ /** 群聊 RECEIPT:更新某条群消息的 readCount / receiptStatus;群已读关闭时兜底忽略 */
handleGroupReceipt(websocketMessage: ImGroupMessageDTO) {
+ if (!MESSAGE_GROUP_READ_ENABLED) {
+ return
+ }
const conversationStore = useConversationStore()
conversationStore.applyReadReceipt({
conversationType: ImConversationType.GROUP,
diff --git a/src/views/im/manager/message/group/GroupMessageDetail.vue b/src/views/im/manager/message/group/GroupMessageDetail.vue
index 4b1350305..f925d7565 100644
--- a/src/views/im/manager/message/group/GroupMessageDetail.vue
+++ b/src/views/im/manager/message/group/GroupMessageDetail.vue
@@ -12,7 +12,7 @@
-
+
@@ -50,6 +50,7 @@ import { formatDate } from '@/utils/formatTime'
import { DICT_TYPE } from '@/utils/dict'
import { IM_AT_ALL_NICKNAME, IM_AT_ALL_USER_ID } from '@/views/im/utils/constants'
import { formatJson } from '@/views/im/utils/message'
+import { MESSAGE_GROUP_READ_ENABLED } from '@/views/im/utils/config'
import * as ManagerGroupMessageApi from '@/api/im/manager/message/group'
import MessageContentPreview from '../MessageContentPreview.vue'
diff --git a/src/views/im/manager/message/group/index.vue b/src/views/im/manager/message/group/index.vue
index bb17b3a0f..069ef46e6 100644
--- a/src/views/im/manager/message/group/index.vue
+++ b/src/views/im/manager/message/group/index.vue
@@ -112,12 +112,24 @@
-
-
+
-
+
@@ -152,6 +164,7 @@
import { dateFormatter } from '@/utils/formatTime'
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { IM_AT_ALL_NICKNAME, IM_AT_ALL_USER_ID } from '@/views/im/utils/constants'
+import { MESSAGE_GROUP_READ_ENABLED } from '@/views/im/utils/config'
import * as ManagerGroupMessageApi from '@/api/im/manager/message/group'
import UserSelectV2 from '@/views/system/user/components/UserSelectV2.vue'
import GroupSelect from '@/views/im/manager/group/components/GroupSelect.vue'
diff --git a/src/views/im/manager/message/private/PrivateMessageDetail.vue b/src/views/im/manager/message/private/PrivateMessageDetail.vue
index c2683f9b2..8ce0926d2 100644
--- a/src/views/im/manager/message/private/PrivateMessageDetail.vue
+++ b/src/views/im/manager/message/private/PrivateMessageDetail.vue
@@ -14,7 +14,7 @@
-
+
@@ -38,6 +38,7 @@
import { formatDate } from '@/utils/formatTime'
import { DICT_TYPE } from '@/utils/dict'
import { formatJson } from '@/views/im/utils/message'
+import { MESSAGE_PRIVATE_READ_ENABLED } from '@/views/im/utils/config'
import * as ManagerPrivateMessageApi from '@/api/im/manager/message/private'
import MessageContentPreview from '../MessageContentPreview.vue'
diff --git a/src/views/im/manager/message/private/index.vue b/src/views/im/manager/message/private/index.vue
index 8660d40c4..3733139d2 100644
--- a/src/views/im/manager/message/private/index.vue
+++ b/src/views/im/manager/message/private/index.vue
@@ -94,7 +94,13 @@
/>
-
+
@@ -134,6 +140,7 @@