From c5b082ca804501c2c3b9578fc7d00fb8b88cf70b Mon Sep 17 00:00:00 2001 From: YunaiV Date: Fri, 8 May 2026 17:42:13 +0800 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor(im):=20=E4=B8=9A?= =?UTF-8?q?=E5=8A=A1=E7=AD=96=E7=95=A5=E6=95=B0=E5=80=BC=E4=BB=8E=20ImComm?= =?UTF-8?q?onConstants=20=E4=B8=8A=E7=A7=BB=E5=88=B0=20ImProperties?= =?UTF-8?q?=EF=BC=88=E6=8C=89=20group=20/=20message=20=E5=AD=90=E6=A8=A1?= =?UTF-8?q?=E5=9D=97=E5=88=86=E7=BB=84=EF=BC=89=EF=BC=8C=E5=B8=B8=E9=87=8F?= =?UTF-8?q?=E7=B1=BB=E4=BB=85=E4=BF=9D=E7=95=99=20AT=5FUSER=5FID=5FALL=20?= =?UTF-8?q?=E5=8D=8F=E8=AE=AE=E5=A5=91=E7=BA=A6=E5=80=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ♻️ refactor(im): 抽出 utils/config.ts 集中数值常量,按业务域统一前缀(GROUP_ / MESSAGE_ / FRIEND_ / CONVERSATION_ / FORWARD_),constants.ts 只留协议枚举与契约值 --- .../components/group/GroupAdminSetDialog.vue | 2 +- .../im/home/composables/useMessagePuller.ts | 5 +- .../components/message/MessageBubble.vue | 5 +- .../components/message/MessageItem.vue | 9 +-- .../message/forward/MessageForwardDialog.vue | 6 +- src/views/im/home/store/conversationStore.ts | 10 +-- src/views/im/home/store/friendStore.ts | 7 +-- .../manager/message/MessageContentPreview.vue | 10 +-- src/views/im/utils/config.ts | 61 +++++++++++++++++++ src/views/im/utils/constants.ts | 27 -------- 10 files changed, 83 insertions(+), 59 deletions(-) create mode 100644 src/views/im/utils/config.ts diff --git a/src/views/im/home/components/group/GroupAdminSetDialog.vue b/src/views/im/home/components/group/GroupAdminSetDialog.vue index cd6c2cc42..2259d89de 100644 --- a/src/views/im/home/components/group/GroupAdminSetDialog.vue +++ b/src/views/im/home/components/group/GroupAdminSetDialog.vue @@ -34,7 +34,7 @@ import { ref } from 'vue' import { useMessage } from '@/hooks/web/useMessage' import { addGroupAdmin, removeGroupAdmin } from '@/api/im/group' -import { GROUP_ADMIN_MAX_COUNT } from '@/views/im/utils/constants' +import { GROUP_ADMIN_MAX_COUNT } from '@/views/im/utils/config' import GroupMemberPickerPanel from '../picker/GroupMemberPickerPanel.vue' import type { GroupMemberLite } from './GroupMember.vue' diff --git a/src/views/im/home/composables/useMessagePuller.ts b/src/views/im/home/composables/useMessagePuller.ts index 1a40ff6b4..a114e249b 100644 --- a/src/views/im/home/composables/useMessagePuller.ts +++ b/src/views/im/home/composables/useMessagePuller.ts @@ -16,11 +16,10 @@ import { import { ImConversationType, ImMessageType, - PRIVATE_MESSAGE_PULL_SIZE, - GROUP_MESSAGE_PULL_SIZE, isFriendChatTip, isFriendNotification } from '../../utils/constants' +import { MESSAGE_PRIVATE_PULL_SIZE, MESSAGE_GROUP_PULL_SIZE } from '../../utils/config' import { useUserStore } from '@/store/modules/user' import type { Message } from '../types' @@ -109,7 +108,7 @@ export const useMessagePuller = () => { // 私聊 / 群聊各自一套接口和分页大小,按 isPrivate 在循环内分支调度 let minId = startMinId || 0 const isPrivate = conversationType === ImConversationType.PRIVATE - const size = isPrivate ? PRIVATE_MESSAGE_PULL_SIZE : GROUP_MESSAGE_PULL_SIZE + const size = isPrivate ? MESSAGE_PRIVATE_PULL_SIZE : MESSAGE_GROUP_PULL_SIZE while (true) { const list = isPrivate ? await apiPullPrivateMessages({ minId, size }) diff --git a/src/views/im/home/pages/conversation/components/message/MessageBubble.vue b/src/views/im/home/pages/conversation/components/message/MessageBubble.vue index a148a2f89..985e95c89 100644 --- a/src/views/im/home/pages/conversation/components/message/MessageBubble.vue +++ b/src/views/im/home/pages/conversation/components/message/MessageBubble.vue @@ -164,7 +164,8 @@ import Icon from '@/components/Icon/src/Icon.vue' import { formatFileSize } from '@/utils/file' import { formatSeconds } from '@/utils/formatTime' -import { ImMessageType, MERGE_FORWARD_PREVIEW_LINES } from '@/views/im/utils/constants' +import { ImMessageType } from '@/views/im/utils/constants' +import { MESSAGE_MERGE_PREVIEW_LINES } from '@/views/im/utils/config' import { parseMessage, parseTextSegments, @@ -259,7 +260,7 @@ const mergePreviewLines = computed(() => { return [] } return mergePayload.value.messages - .slice(0, MERGE_FORWARD_PREVIEW_LINES) + .slice(0, MESSAGE_MERGE_PREVIEW_LINES) .map((item) => `${item.senderNickname}:${summarizeMessageContent(item)}`) }) 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 4b4044470..cac5b70ba 100644 --- a/src/views/im/home/pages/conversation/components/message/MessageItem.vue +++ b/src/views/im/home/pages/conversation/components/message/MessageItem.vue @@ -163,12 +163,12 @@ import { ImConversationType, ImFriendAddSource, ImGroupMemberRole, - TIME_TIP_GAP_MS, isFriendChatTip, isGroupNotification, isMediaMessageType, isNormalMessage } from '@/views/im/utils/constants' +import { MESSAGE_TIME_TIP_GAP_MS, MESSAGE_RECALL_WINDOW_MS } from '@/views/im/utils/config' import { pinGroupMessage as apiPinGroupMessage, cancelMuteMember } from '@/api/im/group' import { removeGroupMember } from '@/api/im/group/member' import { @@ -254,7 +254,7 @@ const shouldShowTimeTip = computed(() => { if (!props.prevMessage?.sendTime) { return true } - return props.message.sendTime - props.prevMessage.sendTime > TIME_TIP_GAP_MS + return props.message.sendTime - props.prevMessage.sendTime > MESSAGE_TIME_TIP_GAP_MS }) /** 仅 MessageItem 自身仍要用到的 type 判定(其它分支已下沉到 MessageBubble) */ @@ -477,9 +477,6 @@ const MENU_KEYS = { } as const type MenuKey = (typeof MENU_KEYS)[keyof typeof MENU_KEYS] -/** 撤回时间窗:自己发送的消息超过这个时长就不能再撤回,菜单回退为「删除」(对齐微信 2 分钟) */ -const RECALL_WINDOW_MS = 2 * 60 * 1000 - /** * 右键菜单项: * - 引用:已落库(id≠0)+ 未撤回的消息可引用,引用块写入 draftStore.reply @@ -589,7 +586,7 @@ async function handleContextMenu(e: MouseEvent) { props.message.selfSend && !!props.message.id && !isRecall.value && - Date.now() - props.message.sendTime <= RECALL_WINDOW_MS + Date.now() - props.message.sendTime <= MESSAGE_RECALL_WINDOW_MS if (canRecall) { items.push({ key: MENU_KEYS.RECALL, diff --git a/src/views/im/home/pages/conversation/components/message/forward/MessageForwardDialog.vue b/src/views/im/home/pages/conversation/components/message/forward/MessageForwardDialog.vue index b31b36c51..8f40837a7 100644 --- a/src/views/im/home/pages/conversation/components/message/forward/MessageForwardDialog.vue +++ b/src/views/im/home/pages/conversation/components/message/forward/MessageForwardDialog.vue @@ -162,9 +162,9 @@ import { ImConversationType, ImForwardMode, ImMessageType, - MERGE_FORWARD_PREVIEW_LINES, type ImForwardModeValue } from '@/views/im/utils/constants' +import { MESSAGE_MERGE_PREVIEW_LINES } from '@/views/im/utils/config' import { getConversationKey, summarizeMessageContent } from '@/views/im/utils/conversation' import { buildDefaultGroupName } from '@/views/im/utils/group' import { @@ -271,14 +271,14 @@ const mergePreview = computed(() => { return null } const lines = payload.messages - .slice(0, MERGE_FORWARD_PREVIEW_LINES) + .slice(0, MESSAGE_MERGE_PREVIEW_LINES) .map((item) => `${item.senderNickname}:${summarizeMessageContent(item)}`) return { title: payload.title, lines } }) /** 逐条模式预览:取前 N 条摘要 */ const singlePreviewLines = computed(() => - state.messages.slice(0, MERGE_FORWARD_PREVIEW_LINES).map((m) => summarizeMessageContent(m)) + state.messages.slice(0, MESSAGE_MERGE_PREVIEW_LINES).map((m) => summarizeMessageContent(m)) ) /** 待发送的逐条消息:剥离 quote 一次,发送多目标时复用 */ diff --git a/src/views/im/home/store/conversationStore.ts b/src/views/im/home/store/conversationStore.ts index 9f6f04ef1..b85deb04d 100644 --- a/src/views/im/home/store/conversationStore.ts +++ b/src/views/im/home/store/conversationStore.ts @@ -7,11 +7,11 @@ import { ImMessageType, ImMessageStatus, IM_AT_ALL_USER_ID, - RECENT_FORWARD_MAX, isGroupNotification, isMediaMessageType, isNormalMessage } from '../../utils/constants' +import { CONVERSATION_RECENT_FORWARD_MAX } from '../../utils/config' import { getCurrentUserId, imStorage, removeQuietly, StorageKeys } from '../../utils/storage' import { parseRecallMessageId, revokeBlobUrlsInContent } from '../../utils/message' import { resolveConversationLastContent } from '../../utils/conversation' @@ -126,7 +126,7 @@ export const useConversationStore = defineStore('imConversationStore', { privateMessageMaxId: 0, // 私聊最大消息 id,作为 pull 的游标 groupMessageMaxId: 0, // 群聊最大消息 id,作为 pull 的游标 loading: false, // 是否正在批量加载(例如离线消息拉取期间),避免频繁写存储 - recentForwardConversationKeys: [] as string[] // 最近转发会话 key 列表(按推送顺序倒序,最大 RECENT_FORWARD_MAX 个) + recentForwardConversationKeys: [] as string[] // 最近转发会话 key 列表(按推送顺序倒序,最大 CONVERSATION_RECENT_FORWARD_MAX 个) }), getters: { @@ -188,7 +188,7 @@ export const useConversationStore = defineStore('imConversationStore', { ]) // 缺数据时显式赋空,避免切账号后沿用上一个用户的内存列表 this.recentForwardConversationKeys = Array.isArray(recent) - ? recent.slice(0, RECENT_FORWARD_MAX) + ? recent.slice(0, CONVERSATION_RECENT_FORWARD_MAX) : [] if (!meta) { return @@ -812,7 +812,7 @@ export const useConversationStore = defineStore('imConversationStore', { // ==================== 最近转发 ==================== /** - * 推送一批会话 key 到最近转发列表:去重 + 推到队首 + 截断 RECENT_FORWARD_MAX + * 推送一批会话 key 到最近转发列表:去重 + 推到队首 + 截断 CONVERSATION_RECENT_FORWARD_MAX * * 调用点:RecommendCardDialog / MessageForwardDialog 提交后(含部分成功)把目标 keys 推进来 */ @@ -823,7 +823,7 @@ export const useConversationStore = defineStore('imConversationStore', { const merged = [...keys, ...this.recentForwardConversationKeys] this.recentForwardConversationKeys = Array.from(new Set(merged)).slice( 0, - RECENT_FORWARD_MAX + CONVERSATION_RECENT_FORWARD_MAX ) this.persistRecentForwardConversationKeys() }, diff --git a/src/views/im/home/store/friendStore.ts b/src/views/im/home/store/friendStore.ts index 8d7497893..c1d6d5b4c 100644 --- a/src/views/im/home/store/friendStore.ts +++ b/src/views/im/home/store/friendStore.ts @@ -21,11 +21,8 @@ import { type ImFriendRequestRespVO } from '@/api/im/friend/request' import { useConversationStore } from './conversationStore' -import { - FRIEND_REQUEST_PAGE_SIZE, - ImConversationType, - ImFriendRequestHandleResult -} from '../../utils/constants' +import { ImConversationType, ImFriendRequestHandleResult } from '../../utils/constants' +import { FRIEND_REQUEST_PAGE_SIZE } from '../../utils/config' import { getCurrentUserId, imStorage, setQuietly, StorageKeys } from '../../utils/storage' import { getFriendDisplayName } from '../../utils/user' import type { Friend, FriendLite, FriendRequest } from '../types' diff --git a/src/views/im/manager/message/MessageContentPreview.vue b/src/views/im/manager/message/MessageContentPreview.vue index 25ecc17fd..50a95c41d 100644 --- a/src/views/im/manager/message/MessageContentPreview.vue +++ b/src/views/im/manager/message/MessageContentPreview.vue @@ -128,12 +128,8 @@ import { computed } from 'vue' import Icon from '@/components/Icon/src/Icon.vue' import { formatFileSize } from '@/utils/file' import { formatSeconds } from '@/utils/formatTime' -import { - ImMessageType, - MERGE_FORWARD_PREVIEW_LINES, - isFriendChatTip, - isGroupNotification -} from '@/views/im/utils/constants' +import { ImMessageType, isFriendChatTip, isGroupNotification } from '@/views/im/utils/constants' +import { MESSAGE_MERGE_PREVIEW_LINES } from '@/views/im/utils/config' import CardLineLabel from '@/views/im/home/components/card/CardLineLabel.vue' import { parseMessage, @@ -207,7 +203,7 @@ const mergePreviewLines = computed(() => { return [] } return mergePayload.value.messages - .slice(0, MERGE_FORWARD_PREVIEW_LINES) + .slice(0, MESSAGE_MERGE_PREVIEW_LINES) .map((item) => `${item.senderNickname}:${summarizeMessageContent(item)}`) }) diff --git a/src/views/im/utils/config.ts b/src/views/im/utils/config.ts new file mode 100644 index 000000000..eb7ec4018 --- /dev/null +++ b/src/views/im/utils/config.ts @@ -0,0 +1,61 @@ +/** + * IM 前端运行参数 / UI 阈值 + * + * 与 constants.ts 的分工: + * - constants.ts:协议枚举(与后端 enum 一一对应)+ 协议契约值(如 IM_AT_ALL_USER_ID) + * - config.ts:可调节的策略数值,包括镜像后端 ImProperties 的默认值与纯前端 UI 阈值 + * + * 标注「后端镜像」的常量与 yudao.im.* 配置默认值一致;调整后端配置时需同步修改这里 + */ + +// ==================== 后端镜像(与 ImProperties 默认值对齐) ==================== + +/** 群最大成员人数(对齐 yudao.im.group.max-member) */ +export const GROUP_MAX_MEMBER = 500 + +/** 单群管理员人数上限(对齐 yudao.im.group.admin-max-count) */ +export const GROUP_ADMIN_MAX_COUNT = 3 + +/** 单群置顶消息条数上限(对齐 yudao.im.group.pin-max-count) */ +export const GROUP_PIN_MAX_COUNT = 5 + +/** 消息撤回时间限制(分钟,对齐 yudao.im.message.recall-timeout-minutes) */ +export const MESSAGE_RECALL_TIMEOUT_MINUTES = 5 + +/** 私聊离线消息最大拉取天数(对齐 yudao.im.message.private-pull-max-days) */ +export const MESSAGE_PRIVATE_PULL_MAX_DAYS = 30 + +/** 群聊离线消息最大拉取天数(对齐 yudao.im.message.group-pull-max-days) */ +export const MESSAGE_GROUP_PULL_MAX_DAYS = 30 + +// ==================== 前端独有:拉取 / 分页 ==================== + +/** 每次拉取私聊消息的最大条数(后端上限 1000,前端取保守值 100) */ +export const MESSAGE_PRIVATE_PULL_SIZE = 100 + +/** 每次拉取群聊消息的最大条数(后端上限 1000,前端取保守值 100) */ +export const MESSAGE_GROUP_PULL_SIZE = 100 + +/** 「我相关」好友申请列表的单次拉取条数(游标分页 page size) */ +export const FRIEND_REQUEST_PAGE_SIZE = 100 + +/** 「我相关」加群申请列表的单次拉取条数 */ +export const GROUP_REQUEST_PAGE_SIZE = 100 + +// ==================== 前端独有:UI 阈值 ==================== + +/** 消息之间渲染「时间分隔条」的阈值:10 分钟 */ +export const MESSAGE_TIME_TIP_GAP_MS = 10 * 60 * 1000 + +/** + * 撤回菜单可见的时间窗:自己发送的消息超过这个时长就不能再撤回,菜单回退为「删除」 + * + * 比后端 MESSAGE_RECALL_TIMEOUT_MINUTES(5 分钟)严格,对齐微信 PC 的 2 分钟 + */ +export const MESSAGE_RECALL_WINDOW_MS = 2 * 60 * 1000 + +/** 合并转发消息(MERGE 类型)气泡内预览的最大行数(对齐微信「聊天记录」气泡) */ +export const MESSAGE_MERGE_PREVIEW_LINES = 3 + +/** 最近转发会话 key 列表的最大保留数量(对齐微信 PC 横向头像区可见容量) */ +export const CONVERSATION_RECENT_FORWARD_MAX = 12 diff --git a/src/views/im/utils/constants.ts b/src/views/im/utils/constants.ts index 120373724..2023a15c4 100644 --- a/src/views/im/utils/constants.ts +++ b/src/views/im/utils/constants.ts @@ -202,27 +202,6 @@ export const ImFriendRequestHandleResult = { REFUSED: 2 // 拒绝 } as const -/** 群管理员人数上限(对齐后端 GROUP_ADMIN_MAX_COUNT) */ -export const GROUP_ADMIN_MAX_COUNT = 3 - -/** 群置顶消息条数上限(对齐后端 GROUP_PIN_MAX_COUNT) */ -export const GROUP_PIN_MAX_COUNT = 5 - -/** 每次拉取私聊消息的最大条数(后端上限 1000,前端取保守值 100) */ -export const PRIVATE_MESSAGE_PULL_SIZE = 100 - -/** 每次拉取群聊消息的最大条数(后端上限 1000,前端取保守值 100) */ -export const GROUP_MESSAGE_PULL_SIZE = 100 - -/** 「我相关」好友申请列表的单次拉取条数(游标分页 page size,前端控制) */ -export const FRIEND_REQUEST_PAGE_SIZE = 100 - -/** 「我相关」加群申请列表的单次拉取条数 */ -export const GROUP_REQUEST_PAGE_SIZE = 100 - -/** 消息之间渲染「时间分隔条」的阈值:10 分钟 */ -export const TIME_TIP_GAP_MS = 10 * 60 * 1000 - /** * @全体成员 的特殊 userId 标识:atUserIds 中包含 -1 表示 @ 全体成员 * @@ -234,12 +213,6 @@ export const IM_AT_ALL_USER_ID = -1 /** @全体成员 的展示名(对齐微信 PC) */ export const IM_AT_ALL_NICKNAME = '所有人' -/** 合并转发气泡内预览的最大行数(对齐微信「聊天记录」气泡) */ -export const MERGE_FORWARD_PREVIEW_LINES = 3 - -/** 最近转发会话 key 列表的最大保留数量(对齐微信 PC 横向头像区可见容量) */ -export const RECENT_FORWARD_MAX = 12 - /** 转发模式:SINGLE 逐条原样转 / MERGE 打包成 MergeMessage */ export const ImForwardMode = { SINGLE: 'single',