✨ feat(im): 拆出私聊 / 群聊已读两个全局开关,关闭后禁用接口与所有 UI 入口(含群回执)
ImProperties.message 新增 privateReadEnabled / groupReadEnabled,前端 config.ts 同步镜像。关闭后: - 后端:read 系列接口(read / getMaxReadMessageId / getGroupReadUserIds)抛业务异常;sendGroupMessage 强制 NO_RECEIPT 忽略 receipt=true;pull 群消息跳过 Redis 已读游标读取与 readCount 补齐 - 前端:气泡已读标签 / 群回执 popover / 「发送回执消息」下拉入口 / admin 列表「状态」「回执」列与详情对应字段按开关隐藏;自动上报 / 冷启动同步对方已读位置 / WS READ & RECEIPT handler 全部按开关短路兜底,避免打到禁用接口 - 单测:补 @Spy ImProperties 修复原本就在的 NPE,加 disabled 分支断言im
parent
46b06b0444
commit
2935d7d112
|
|
@ -19,7 +19,11 @@ import {
|
||||||
isFriendChatTip,
|
isFriendChatTip,
|
||||||
isFriendNotification
|
isFriendNotification
|
||||||
} from '../../utils/constants'
|
} 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 { useUserStore } from '@/store/modules/user'
|
||||||
import type { Message } from '../types'
|
import type { Message } from '../types'
|
||||||
|
|
||||||
|
|
@ -220,8 +224,12 @@ export const useMessagePuller = () => {
|
||||||
|
|
||||||
// 重连 / 冷启动后补齐当前激活私聊会话的「对方已读位置」
|
// 重连 / 冷启动后补齐当前激活私聊会话的「对方已读位置」
|
||||||
// 离线期间错过的 RECEIPT 推送会被这里补回;其他私聊会话等用户点开时由 Index.vue 的 watch 触发
|
// 离线期间错过的 RECEIPT 推送会被这里补回;其他私聊会话等用户点开时由 Index.vue 的 watch 触发
|
||||||
|
// 私聊已读关闭时跳过,避免打到已禁用接口触发错误日志
|
||||||
const active = conversationStore.activeConversation
|
const active = conversationStore.activeConversation
|
||||||
if (active && active.type === ImConversationType.PRIVATE) {
|
if (
|
||||||
|
MESSAGE_PRIVATE_READ_ENABLED
|
||||||
|
&& active && active.type === ImConversationType.PRIVATE
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
const maxReadId = await apiGetPrivateMaxReadMessageId(active.targetId)
|
const maxReadId = await apiGetPrivateMaxReadMessageId(active.targetId)
|
||||||
if (maxReadId) {
|
if (maxReadId) {
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ import {
|
||||||
type TextMessage
|
type TextMessage
|
||||||
} from '../../utils/message'
|
} from '../../utils/message'
|
||||||
import { ImMessageType, ImMessageStatus, ImConversationType } from '../../utils/constants'
|
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 type { Conversation, Message } from '../types'
|
||||||
import { useUserStore } from '@/store/modules/user'
|
import { useUserStore } from '@/store/modules/user'
|
||||||
|
|
||||||
|
|
@ -229,8 +230,12 @@ export const useMessageSender = () => {
|
||||||
if (!maxMessageId) {
|
if (!maxMessageId) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// 接口调用:私聊 / 群聊接口签名一致,按会话类型分发;失败仅记录日志,不回退本地已读状态
|
// 接口调用:按会话类型分发,并按对应已读开关控制;失败仅记录日志,不回退本地已读状态
|
||||||
const isPrivate = conversation.type === ImConversationType.PRIVATE
|
const isPrivate = conversation.type === ImConversationType.PRIVATE
|
||||||
|
const readEnabled = isPrivate ? MESSAGE_PRIVATE_READ_ENABLED : MESSAGE_GROUP_READ_ENABLED
|
||||||
|
if (!readEnabled) {
|
||||||
|
return
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
await (isPrivate
|
await (isPrivate
|
||||||
? apiReadPrivateMessages(conversation.targetId, maxMessageId)
|
? apiReadPrivateMessages(conversation.targetId, maxMessageId)
|
||||||
|
|
@ -255,6 +260,10 @@ export const useMessageSender = () => {
|
||||||
if (!peerId) {
|
if (!peerId) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// 私聊已读关闭:跳过对方已读位置同步,避免无谓接口调用
|
||||||
|
if (!MESSAGE_PRIVATE_READ_ENABLED) {
|
||||||
|
return
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
// 拉取对方已读到的最大消息 id
|
// 拉取对方已读到的最大消息 id
|
||||||
const maxReadId = await apiGetPrivateMaxReadMessageId(peerId)
|
const maxReadId = await apiGetPrivateMaxReadMessageId(peerId)
|
||||||
|
|
|
||||||
|
|
@ -108,9 +108,9 @@
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 群聊:发送按钮 + ▼ 下拉菜单(点主按钮普通发送 / 点 ▼ 选「发送回执消息」),对齐微信 PC -->
|
<!-- 群聊 + 群已读开启:发送按钮 + ▼ 下拉菜单(点主按钮普通发送 / 点 ▼ 选「发送回执消息」),对齐微信 PC -->
|
||||||
<el-dropdown
|
<el-dropdown
|
||||||
v-if="isGroup"
|
v-if="isGroup && MESSAGE_GROUP_READ_ENABLED"
|
||||||
split-button
|
split-button
|
||||||
type="primary"
|
type="primary"
|
||||||
:disabled="!canSend"
|
:disabled="!canSend"
|
||||||
|
|
@ -124,7 +124,7 @@
|
||||||
</el-dropdown-menu>
|
</el-dropdown-menu>
|
||||||
</template>
|
</template>
|
||||||
</el-dropdown>
|
</el-dropdown>
|
||||||
<!-- 私聊:普通发送按钮(私聊没有群回执概念) -->
|
<!-- 私聊或群已读关闭:普通发送按钮(无群回执入口) -->
|
||||||
<el-button v-else type="primary" :disabled="!canSend" @click="handleSend()">
|
<el-button v-else type="primary" :disabled="!canSend" @click="handleSend()">
|
||||||
发 送
|
发 送
|
||||||
</el-button>
|
</el-button>
|
||||||
|
|
@ -175,6 +175,7 @@ import { useMediaUploader } from '@/views/im/home/composables/useMediaUploader'
|
||||||
import { useMuteOverlay } from '@/views/im/home/composables/useMuteOverlay'
|
import { useMuteOverlay } from '@/views/im/home/composables/useMuteOverlay'
|
||||||
import { getConversationKey } from '@/views/im/utils/conversation'
|
import { getConversationKey } from '@/views/im/utils/conversation'
|
||||||
import { ImConversationType, ImMessageType } from '@/views/im/utils/constants'
|
import { ImConversationType, ImMessageType } from '@/views/im/utils/constants'
|
||||||
|
import { MESSAGE_GROUP_READ_ENABLED } from '@/views/im/utils/config'
|
||||||
import {
|
import {
|
||||||
serializeMessage,
|
serializeMessage,
|
||||||
type FaceMessage,
|
type FaceMessage,
|
||||||
|
|
|
||||||
|
|
@ -168,7 +168,12 @@ import {
|
||||||
isMediaMessageType,
|
isMediaMessageType,
|
||||||
isNormalMessage
|
isNormalMessage
|
||||||
} from '@/views/im/utils/constants'
|
} 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 { pinGroupMessage as apiPinGroupMessage, cancelMuteMember } from '@/api/im/group'
|
||||||
import { removeGroupMember } from '@/api/im/group/member'
|
import { removeGroupMember } from '@/api/im/group/member'
|
||||||
import {
|
import {
|
||||||
|
|
@ -399,8 +404,11 @@ const senderDisplayName = computed(() => {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
/** 私聊「已读 / 未读」态(仅对自己发送的私聊消息展示) */
|
/** 私聊「已读 / 未读」态(仅对自己发送的私聊消息展示;私聊已读全局关闭时不再展示) */
|
||||||
const privateReadLabel = computed(() => {
|
const privateReadLabel = computed(() => {
|
||||||
|
if (!MESSAGE_PRIVATE_READ_ENABLED) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
if (!props.message.selfSend) {
|
if (!props.message.selfSend) {
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
|
|
@ -416,8 +424,11 @@ const privateReadLabel = computed(() => {
|
||||||
return ''
|
return ''
|
||||||
})
|
})
|
||||||
|
|
||||||
/**是否需要显示群回执 popover:自己发的群消息且后端开启了回执(NO_RECEIPT 表示发送时未要求回执,不渲染) */
|
/**是否需要显示群回执 popover:自己发的群消息且后端开启了回执(NO_RECEIPT 表示发送时未要求回执,不渲染;群已读全局关闭时统一不展示) */
|
||||||
const showGroupReadStatus = computed(() => {
|
const showGroupReadStatus = computed(() => {
|
||||||
|
if (!MESSAGE_GROUP_READ_ENABLED) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if (!props.message.selfSend) {
|
if (!props.message.selfSend) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import {
|
||||||
isNormalMessage
|
isNormalMessage
|
||||||
} from '../../utils/constants'
|
} from '../../utils/constants'
|
||||||
import { playAudioTip } from '../../utils/message'
|
import { playAudioTip } from '../../utils/message'
|
||||||
|
import { MESSAGE_PRIVATE_READ_ENABLED, MESSAGE_GROUP_READ_ENABLED } from '../../utils/config'
|
||||||
import { useConversationStore } from './conversationStore'
|
import { useConversationStore } from './conversationStore'
|
||||||
import { useFriendStore, type FriendNotificationPayload } from './friendStore'
|
import { useFriendStore, type FriendNotificationPayload } from './friendStore'
|
||||||
import { getFriendDisplayName } from '../../utils/user'
|
import { getFriendDisplayName } from '../../utils/user'
|
||||||
|
|
@ -356,12 +357,14 @@ export const useImWebSocketStore = defineStore('imWebSocketStore', {
|
||||||
conversationStore.activeConversation?.type === ImConversationType.PRIVATE &&
|
conversationStore.activeConversation?.type === ImConversationType.PRIVATE &&
|
||||||
conversationStore.activeConversation?.targetId === peerId
|
conversationStore.activeConversation?.targetId === peerId
|
||||||
if (isActive) {
|
if (isActive) {
|
||||||
// 聊天窗口打开 = 实际看到了:本端清未读 + 上报后端,让对方 UI 立刻切到"已读"
|
// 聊天窗口打开 = 实际看到了:本端清未读;私聊已读开启时再上报后端,让对方 UI 立刻切到"已读"
|
||||||
// 已读位置直接用刚到的消息 id(这条就是当前会话最大 id)
|
// 已读位置直接用刚到的消息 id(这条就是当前会话最大 id)
|
||||||
conversationStore.markActiveAsRead()
|
conversationStore.markActiveAsRead()
|
||||||
apiReadPrivateMessages(peerId, websocketMessage.id).catch((e) => {
|
if (MESSAGE_PRIVATE_READ_ENABLED) {
|
||||||
console.warn('[IM WS] 自动已读上报失败', e)
|
apiReadPrivateMessages(peerId, websocketMessage.id).catch((e) => {
|
||||||
})
|
console.warn('[IM WS] 自动已读上报失败', e)
|
||||||
|
})
|
||||||
|
}
|
||||||
} else if (!conversation?.silent && isNormalMessage(websocketMessage.type)) {
|
} else if (!conversation?.silent && isNormalMessage(websocketMessage.type)) {
|
||||||
// 非当前会话且未免打扰:响一下提示音(带节流,详见 playAudioTip);FRIEND_* 等系统事件不响
|
// 非当前会话且未免打扰:响一下提示音(带节流,详见 playAudioTip);FRIEND_* 等系统事件不响
|
||||||
playAudioTip()
|
playAudioTip()
|
||||||
|
|
@ -369,8 +372,11 @@ export const useImWebSocketStore = defineStore('imWebSocketStore', {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/** 私聊 READ 事件:自己的其它终端在对方会话里标为已读,本端同步清零未读 */
|
/** 私聊 READ 事件:自己的其它终端在对方会话里标为已读,本端同步清零未读;私聊已读关闭时兜底忽略 */
|
||||||
handlePrivateRead(websocketMessage: ImPrivateMessageDTO) {
|
handlePrivateRead(websocketMessage: ImPrivateMessageDTO) {
|
||||||
|
if (!MESSAGE_PRIVATE_READ_ENABLED) {
|
||||||
|
return
|
||||||
|
}
|
||||||
const conversationStore = useConversationStore()
|
const conversationStore = useConversationStore()
|
||||||
const conversation = conversationStore.getConversation(
|
const conversation = conversationStore.getConversation(
|
||||||
ImConversationType.PRIVATE,
|
ImConversationType.PRIVATE,
|
||||||
|
|
@ -385,9 +391,12 @@ export const useImWebSocketStore = defineStore('imWebSocketStore', {
|
||||||
/**
|
/**
|
||||||
* 私聊 RECEIPT 事件:对方读了我的消息,把和对方会话里自己发的消息标为已读
|
* 私聊 RECEIPT 事件:对方读了我的消息,把和对方会话里自己发的消息标为已读
|
||||||
* 后端将 maxReadId 编码在 DTO 的 id 字段(见 ImPrivateMessageDTO.ofReceipt),
|
* 后端将 maxReadId 编码在 DTO 的 id 字段(见 ImPrivateMessageDTO.ofReceipt),
|
||||||
* 这里据此卡边界,避免把"回执在路上时刚发的消息"误标为已读。
|
* 这里据此卡边界,避免把"回执在路上时刚发的消息"误标为已读;私聊已读关闭时兜底忽略
|
||||||
*/
|
*/
|
||||||
handlePrivateReceipt(websocketMessage: ImPrivateMessageDTO) {
|
handlePrivateReceipt(websocketMessage: ImPrivateMessageDTO) {
|
||||||
|
if (!MESSAGE_PRIVATE_READ_ENABLED) {
|
||||||
|
return
|
||||||
|
}
|
||||||
if (!websocketMessage.id) {
|
if (!websocketMessage.id) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -463,11 +472,13 @@ export const useImWebSocketStore = defineStore('imWebSocketStore', {
|
||||||
conversationStore.activeConversation?.type === ImConversationType.GROUP &&
|
conversationStore.activeConversation?.type === ImConversationType.GROUP &&
|
||||||
conversationStore.activeConversation?.targetId === websocketMessage.groupId
|
conversationStore.activeConversation?.targetId === websocketMessage.groupId
|
||||||
if (isActive) {
|
if (isActive) {
|
||||||
// 群已读上报需要带 messageId(群消息以"读到第几条"的游标为准,区别于私聊只标 receiverId)
|
// 群已读上报需要带 messageId(群消息以"读到第几条"的游标为准,区别于私聊只标 receiverId);群已读关闭时仅本地清零
|
||||||
conversationStore.markActiveAsRead()
|
conversationStore.markActiveAsRead()
|
||||||
apiReadGroupMessages(websocketMessage.groupId, websocketMessage.id).catch((e) => {
|
if (MESSAGE_GROUP_READ_ENABLED) {
|
||||||
console.warn('[IM WS] 自动已读上报失败', e)
|
apiReadGroupMessages(websocketMessage.groupId, websocketMessage.id).catch((e) => {
|
||||||
})
|
console.warn('[IM WS] 自动已读上报失败', e)
|
||||||
|
})
|
||||||
|
}
|
||||||
} else if (!conversation?.silent && isNormalMessage(websocketMessage.type)) {
|
} else if (!conversation?.silent && isNormalMessage(websocketMessage.type)) {
|
||||||
// GROUP_* 群广播事件等系统消息不响提示音
|
// GROUP_* 群广播事件等系统消息不响提示音
|
||||||
playAudioTip()
|
playAudioTip()
|
||||||
|
|
@ -477,8 +488,11 @@ export const useImWebSocketStore = defineStore('imWebSocketStore', {
|
||||||
|
|
||||||
// ==================== 群聊已读 / 回执 ====================
|
// ==================== 群聊已读 / 回执 ====================
|
||||||
|
|
||||||
/** 群聊 READ:自己其它终端在某群里标为已读,本端同步清零该群未读 */
|
/** 群聊 READ:自己其它终端在某群里标为已读,本端同步清零该群未读;群已读关闭时兜底忽略 */
|
||||||
handleGroupRead(websocketMessage: ImGroupMessageDTO) {
|
handleGroupRead(websocketMessage: ImGroupMessageDTO) {
|
||||||
|
if (!MESSAGE_GROUP_READ_ENABLED) {
|
||||||
|
return
|
||||||
|
}
|
||||||
const conversationStore = useConversationStore()
|
const conversationStore = useConversationStore()
|
||||||
const conversation = conversationStore.getConversation(
|
const conversation = conversationStore.getConversation(
|
||||||
ImConversationType.GROUP,
|
ImConversationType.GROUP,
|
||||||
|
|
@ -490,8 +504,11 @@ export const useImWebSocketStore = defineStore('imWebSocketStore', {
|
||||||
conversationStore.saveConversations()
|
conversationStore.saveConversations()
|
||||||
},
|
},
|
||||||
|
|
||||||
/** 群聊 RECEIPT:更新某条群消息的 readCount / receiptStatus */
|
/** 群聊 RECEIPT:更新某条群消息的 readCount / receiptStatus;群已读关闭时兜底忽略 */
|
||||||
handleGroupReceipt(websocketMessage: ImGroupMessageDTO) {
|
handleGroupReceipt(websocketMessage: ImGroupMessageDTO) {
|
||||||
|
if (!MESSAGE_GROUP_READ_ENABLED) {
|
||||||
|
return
|
||||||
|
}
|
||||||
const conversationStore = useConversationStore()
|
const conversationStore = useConversationStore()
|
||||||
conversationStore.applyReadReceipt({
|
conversationStore.applyReadReceipt({
|
||||||
conversationType: ImConversationType.GROUP,
|
conversationType: ImConversationType.GROUP,
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
<el-descriptions-item label="类型">
|
<el-descriptions-item label="类型">
|
||||||
<dict-tag :type="DICT_TYPE.IM_MESSAGE_TYPE" :value="detail.type" />
|
<dict-tag :type="DICT_TYPE.IM_MESSAGE_TYPE" :value="detail.type" />
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
<el-descriptions-item label="状态">
|
<el-descriptions-item v-if="MESSAGE_GROUP_READ_ENABLED" label="状态">
|
||||||
<dict-tag :type="DICT_TYPE.IM_GROUP_MESSAGE_STATUS" :value="detail.status" />
|
<dict-tag :type="DICT_TYPE.IM_GROUP_MESSAGE_STATUS" :value="detail.status" />
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
<el-descriptions-item label="@用户" :span="2">
|
<el-descriptions-item label="@用户" :span="2">
|
||||||
|
|
@ -50,6 +50,7 @@ import { formatDate } from '@/utils/formatTime'
|
||||||
import { DICT_TYPE } from '@/utils/dict'
|
import { DICT_TYPE } from '@/utils/dict'
|
||||||
import { IM_AT_ALL_NICKNAME, IM_AT_ALL_USER_ID } from '@/views/im/utils/constants'
|
import { IM_AT_ALL_NICKNAME, IM_AT_ALL_USER_ID } from '@/views/im/utils/constants'
|
||||||
import { formatJson } from '@/views/im/utils/message'
|
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 * as ManagerGroupMessageApi from '@/api/im/manager/message/group'
|
||||||
import MessageContentPreview from '../MessageContentPreview.vue'
|
import MessageContentPreview from '../MessageContentPreview.vue'
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -112,12 +112,24 @@
|
||||||
<span v-else>-</span>
|
<span v-else>-</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="回执" align="center" prop="receiptStatus" width="110">
|
<el-table-column
|
||||||
|
v-if="MESSAGE_GROUP_READ_ENABLED"
|
||||||
|
label="回执"
|
||||||
|
align="center"
|
||||||
|
prop="receiptStatus"
|
||||||
|
width="110"
|
||||||
|
>
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<dict-tag :type="DICT_TYPE.IM_GROUP_MESSAGE_RECEIPT_STATUS" :value="row.receiptStatus" />
|
<dict-tag :type="DICT_TYPE.IM_GROUP_MESSAGE_RECEIPT_STATUS" :value="row.receiptStatus" />
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="状态" align="center" prop="status" width="100">
|
<el-table-column
|
||||||
|
v-if="MESSAGE_GROUP_READ_ENABLED"
|
||||||
|
label="状态"
|
||||||
|
align="center"
|
||||||
|
prop="status"
|
||||||
|
width="100"
|
||||||
|
>
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<dict-tag :type="DICT_TYPE.IM_GROUP_MESSAGE_STATUS" :value="row.status" />
|
<dict-tag :type="DICT_TYPE.IM_GROUP_MESSAGE_STATUS" :value="row.status" />
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -152,6 +164,7 @@
|
||||||
import { dateFormatter } from '@/utils/formatTime'
|
import { dateFormatter } from '@/utils/formatTime'
|
||||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||||
import { IM_AT_ALL_NICKNAME, IM_AT_ALL_USER_ID } from '@/views/im/utils/constants'
|
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 * as ManagerGroupMessageApi from '@/api/im/manager/message/group'
|
||||||
import UserSelectV2 from '@/views/system/user/components/UserSelectV2.vue'
|
import UserSelectV2 from '@/views/system/user/components/UserSelectV2.vue'
|
||||||
import GroupSelect from '@/views/im/manager/group/components/GroupSelect.vue'
|
import GroupSelect from '@/views/im/manager/group/components/GroupSelect.vue'
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
<el-descriptions-item label="类型">
|
<el-descriptions-item label="类型">
|
||||||
<dict-tag :type="DICT_TYPE.IM_MESSAGE_TYPE" :value="detail.type" />
|
<dict-tag :type="DICT_TYPE.IM_MESSAGE_TYPE" :value="detail.type" />
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
<el-descriptions-item label="状态">
|
<el-descriptions-item v-if="MESSAGE_PRIVATE_READ_ENABLED" label="状态">
|
||||||
<dict-tag :type="DICT_TYPE.IM_PRIVATE_MESSAGE_STATUS" :value="detail.status" />
|
<dict-tag :type="DICT_TYPE.IM_PRIVATE_MESSAGE_STATUS" :value="detail.status" />
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
<el-descriptions-item label="发送时间" :span="2">
|
<el-descriptions-item label="发送时间" :span="2">
|
||||||
|
|
@ -38,6 +38,7 @@
|
||||||
import { formatDate } from '@/utils/formatTime'
|
import { formatDate } from '@/utils/formatTime'
|
||||||
import { DICT_TYPE } from '@/utils/dict'
|
import { DICT_TYPE } from '@/utils/dict'
|
||||||
import { formatJson } from '@/views/im/utils/message'
|
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 * as ManagerPrivateMessageApi from '@/api/im/manager/message/private'
|
||||||
import MessageContentPreview from '../MessageContentPreview.vue'
|
import MessageContentPreview from '../MessageContentPreview.vue'
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -94,7 +94,13 @@
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="状态" align="center" prop="status" width="100">
|
<el-table-column
|
||||||
|
v-if="MESSAGE_PRIVATE_READ_ENABLED"
|
||||||
|
label="状态"
|
||||||
|
align="center"
|
||||||
|
prop="status"
|
||||||
|
width="100"
|
||||||
|
>
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<dict-tag :type="DICT_TYPE.IM_PRIVATE_MESSAGE_STATUS" :value="row.status" />
|
<dict-tag :type="DICT_TYPE.IM_PRIVATE_MESSAGE_STATUS" :value="row.status" />
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -134,6 +140,7 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { dateFormatter } from '@/utils/formatTime'
|
import { dateFormatter } from '@/utils/formatTime'
|
||||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||||
|
import { MESSAGE_PRIVATE_READ_ENABLED } from '@/views/im/utils/config'
|
||||||
import * as ManagerPrivateMessageApi from '@/api/im/manager/message/private'
|
import * as ManagerPrivateMessageApi from '@/api/im/manager/message/private'
|
||||||
import UserSelectV2 from '@/views/system/user/components/UserSelectV2.vue'
|
import UserSelectV2 from '@/views/system/user/components/UserSelectV2.vue'
|
||||||
import MessageContentPreview from '../MessageContentPreview.vue'
|
import MessageContentPreview from '../MessageContentPreview.vue'
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,22 @@ export const GROUP_ADMIN_MAX_COUNT = 3
|
||||||
/** 单群置顶消息条数上限(对齐 yudao.im.group.pin-max-count) */
|
/** 单群置顶消息条数上限(对齐 yudao.im.group.pin-max-count) */
|
||||||
export const GROUP_PIN_MAX_COUNT = 5
|
export const GROUP_PIN_MAX_COUNT = 5
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否启用私聊已读功能(对齐 yudao.im.message.private-read-enabled)
|
||||||
|
*
|
||||||
|
* 关闭后:进入私聊会话不再上报已读位置;气泡的「已读 / 未读」标签隐藏;
|
||||||
|
* 管理后台私聊消息列表的「状态」列与详情中的状态字段隐藏
|
||||||
|
*/
|
||||||
|
export const MESSAGE_PRIVATE_READ_ENABLED = true
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否启用群聊已读功能(含群消息回执,对齐 yudao.im.message.group-read-enabled)
|
||||||
|
*
|
||||||
|
* 关闭后:进入群会话不再上报已读位置;输入框的「发送回执消息」入口隐藏;
|
||||||
|
* 群消息气泡上的「N 人已读」popover 隐藏;管理后台群消息列表的「状态」「回执」列与详情中对应字段隐藏
|
||||||
|
*/
|
||||||
|
export const MESSAGE_GROUP_READ_ENABLED = true
|
||||||
|
|
||||||
/** 消息撤回时间限制(分钟,对齐 yudao.im.message.recall-timeout-minutes) */
|
/** 消息撤回时间限制(分钟,对齐 yudao.im.message.recall-timeout-minutes) */
|
||||||
export const MESSAGE_RECALL_TIMEOUT_MINUTES = 5
|
export const MESSAGE_RECALL_TIMEOUT_MINUTES = 5
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue