♻️ refactor(im): 业务策略数值从 ImCommonConstants 上移到 ImProperties(按 group / message 子模块分组),常量类仅保留 AT_USER_ID_ALL 协议契约值

♻️ refactor(im): 抽出 utils/config.ts 集中数值常量,按业务域统一前缀(GROUP_ / MESSAGE_ / FRIEND_ / CONVERSATION_ / FORWARD_),constants.ts 只留协议枚举与契约值
im
YunaiV 2026-05-08 17:42:13 +08:00
parent dfd5b39a17
commit c5b082ca80
10 changed files with 83 additions and 59 deletions

View File

@ -34,7 +34,7 @@ import { ref } from 'vue'
import { useMessage } from '@/hooks/web/useMessage' import { useMessage } from '@/hooks/web/useMessage'
import { addGroupAdmin, removeGroupAdmin } from '@/api/im/group' 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 GroupMemberPickerPanel from '../picker/GroupMemberPickerPanel.vue'
import type { GroupMemberLite } from './GroupMember.vue' import type { GroupMemberLite } from './GroupMember.vue'

View File

@ -16,11 +16,10 @@ import {
import { import {
ImConversationType, ImConversationType,
ImMessageType, ImMessageType,
PRIVATE_MESSAGE_PULL_SIZE,
GROUP_MESSAGE_PULL_SIZE,
isFriendChatTip, isFriendChatTip,
isFriendNotification isFriendNotification
} from '../../utils/constants' } from '../../utils/constants'
import { MESSAGE_PRIVATE_PULL_SIZE, MESSAGE_GROUP_PULL_SIZE } 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'
@ -109,7 +108,7 @@ export const useMessagePuller = () => {
// 私聊 / 群聊各自一套接口和分页大小,按 isPrivate 在循环内分支调度 // 私聊 / 群聊各自一套接口和分页大小,按 isPrivate 在循环内分支调度
let minId = startMinId || 0 let minId = startMinId || 0
const isPrivate = conversationType === ImConversationType.PRIVATE 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) { while (true) {
const list = isPrivate const list = isPrivate
? await apiPullPrivateMessages({ minId, size }) ? await apiPullPrivateMessages({ minId, size })

View File

@ -164,7 +164,8 @@ import Icon from '@/components/Icon/src/Icon.vue'
import { formatFileSize } from '@/utils/file' import { formatFileSize } from '@/utils/file'
import { formatSeconds } from '@/utils/formatTime' 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 { import {
parseMessage, parseMessage,
parseTextSegments, parseTextSegments,
@ -259,7 +260,7 @@ const mergePreviewLines = computed(() => {
return [] return []
} }
return mergePayload.value.messages return mergePayload.value.messages
.slice(0, MERGE_FORWARD_PREVIEW_LINES) .slice(0, MESSAGE_MERGE_PREVIEW_LINES)
.map((item) => `${item.senderNickname}${summarizeMessageContent(item)}`) .map((item) => `${item.senderNickname}${summarizeMessageContent(item)}`)
}) })

View File

@ -163,12 +163,12 @@ import {
ImConversationType, ImConversationType,
ImFriendAddSource, ImFriendAddSource,
ImGroupMemberRole, ImGroupMemberRole,
TIME_TIP_GAP_MS,
isFriendChatTip, isFriendChatTip,
isGroupNotification, isGroupNotification,
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 { 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 {
@ -254,7 +254,7 @@ const shouldShowTimeTip = computed(() => {
if (!props.prevMessage?.sendTime) { if (!props.prevMessage?.sendTime) {
return true 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 */ /** 仅 MessageItem 自身仍要用到的 type 判定(其它分支已下沉到 MessageBubble */
@ -477,9 +477,6 @@ const MENU_KEYS = {
} as const } as const
type MenuKey = (typeof MENU_KEYS)[keyof typeof MENU_KEYS] type MenuKey = (typeof MENU_KEYS)[keyof typeof MENU_KEYS]
/** 撤回时间窗:自己发送的消息超过这个时长就不能再撤回,菜单回退为「删除」(对齐微信 2 分钟) */
const RECALL_WINDOW_MS = 2 * 60 * 1000
/** /**
* 右键菜单项 * 右键菜单项
* - 引用已落库id0+ 未撤回的消息可引用引用块写入 draftStore.reply * - 引用已落库id0+ 未撤回的消息可引用引用块写入 draftStore.reply
@ -589,7 +586,7 @@ async function handleContextMenu(e: MouseEvent) {
props.message.selfSend && props.message.selfSend &&
!!props.message.id && !!props.message.id &&
!isRecall.value && !isRecall.value &&
Date.now() - props.message.sendTime <= RECALL_WINDOW_MS Date.now() - props.message.sendTime <= MESSAGE_RECALL_WINDOW_MS
if (canRecall) { if (canRecall) {
items.push({ items.push({
key: MENU_KEYS.RECALL, key: MENU_KEYS.RECALL,

View File

@ -162,9 +162,9 @@ import {
ImConversationType, ImConversationType,
ImForwardMode, ImForwardMode,
ImMessageType, ImMessageType,
MERGE_FORWARD_PREVIEW_LINES,
type ImForwardModeValue type ImForwardModeValue
} from '@/views/im/utils/constants' } from '@/views/im/utils/constants'
import { MESSAGE_MERGE_PREVIEW_LINES } from '@/views/im/utils/config'
import { getConversationKey, summarizeMessageContent } from '@/views/im/utils/conversation' import { getConversationKey, summarizeMessageContent } from '@/views/im/utils/conversation'
import { buildDefaultGroupName } from '@/views/im/utils/group' import { buildDefaultGroupName } from '@/views/im/utils/group'
import { import {
@ -271,14 +271,14 @@ const mergePreview = computed(() => {
return null return null
} }
const lines = payload.messages const lines = payload.messages
.slice(0, MERGE_FORWARD_PREVIEW_LINES) .slice(0, MESSAGE_MERGE_PREVIEW_LINES)
.map((item) => `${item.senderNickname}${summarizeMessageContent(item)}`) .map((item) => `${item.senderNickname}${summarizeMessageContent(item)}`)
return { title: payload.title, lines } return { title: payload.title, lines }
}) })
/** 逐条模式预览:取前 N 条摘要 */ /** 逐条模式预览:取前 N 条摘要 */
const singlePreviewLines = computed(() => 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 一次,发送多目标时复用 */ /** 待发送的逐条消息:剥离 quote 一次,发送多目标时复用 */

View File

@ -7,11 +7,11 @@ import {
ImMessageType, ImMessageType,
ImMessageStatus, ImMessageStatus,
IM_AT_ALL_USER_ID, IM_AT_ALL_USER_ID,
RECENT_FORWARD_MAX,
isGroupNotification, isGroupNotification,
isMediaMessageType, isMediaMessageType,
isNormalMessage isNormalMessage
} from '../../utils/constants' } from '../../utils/constants'
import { CONVERSATION_RECENT_FORWARD_MAX } from '../../utils/config'
import { getCurrentUserId, imStorage, removeQuietly, StorageKeys } from '../../utils/storage' import { getCurrentUserId, imStorage, removeQuietly, StorageKeys } from '../../utils/storage'
import { parseRecallMessageId, revokeBlobUrlsInContent } from '../../utils/message' import { parseRecallMessageId, revokeBlobUrlsInContent } from '../../utils/message'
import { resolveConversationLastContent } from '../../utils/conversation' import { resolveConversationLastContent } from '../../utils/conversation'
@ -126,7 +126,7 @@ export const useConversationStore = defineStore('imConversationStore', {
privateMessageMaxId: 0, // 私聊最大消息 id作为 pull 的游标 privateMessageMaxId: 0, // 私聊最大消息 id作为 pull 的游标
groupMessageMaxId: 0, // 群聊最大消息 id作为 pull 的游标 groupMessageMaxId: 0, // 群聊最大消息 id作为 pull 的游标
loading: false, // 是否正在批量加载(例如离线消息拉取期间),避免频繁写存储 loading: false, // 是否正在批量加载(例如离线消息拉取期间),避免频繁写存储
recentForwardConversationKeys: [] as string[] // 最近转发会话 key 列表(按推送顺序倒序,最大 RECENT_FORWARD_MAX 个) recentForwardConversationKeys: [] as string[] // 最近转发会话 key 列表(按推送顺序倒序,最大 CONVERSATION_RECENT_FORWARD_MAX 个)
}), }),
getters: { getters: {
@ -188,7 +188,7 @@ export const useConversationStore = defineStore('imConversationStore', {
]) ])
// 缺数据时显式赋空,避免切账号后沿用上一个用户的内存列表 // 缺数据时显式赋空,避免切账号后沿用上一个用户的内存列表
this.recentForwardConversationKeys = Array.isArray(recent) this.recentForwardConversationKeys = Array.isArray(recent)
? recent.slice(0, RECENT_FORWARD_MAX) ? recent.slice(0, CONVERSATION_RECENT_FORWARD_MAX)
: [] : []
if (!meta) { if (!meta) {
return return
@ -812,7 +812,7 @@ export const useConversationStore = defineStore('imConversationStore', {
// ==================== 最近转发 ==================== // ==================== 最近转发 ====================
/** /**
* key + + RECENT_FORWARD_MAX * key + + CONVERSATION_RECENT_FORWARD_MAX
* *
* RecommendCardDialog / MessageForwardDialog keys * RecommendCardDialog / MessageForwardDialog keys
*/ */
@ -823,7 +823,7 @@ export const useConversationStore = defineStore('imConversationStore', {
const merged = [...keys, ...this.recentForwardConversationKeys] const merged = [...keys, ...this.recentForwardConversationKeys]
this.recentForwardConversationKeys = Array.from(new Set(merged)).slice( this.recentForwardConversationKeys = Array.from(new Set(merged)).slice(
0, 0,
RECENT_FORWARD_MAX CONVERSATION_RECENT_FORWARD_MAX
) )
this.persistRecentForwardConversationKeys() this.persistRecentForwardConversationKeys()
}, },

View File

@ -21,11 +21,8 @@ import {
type ImFriendRequestRespVO type ImFriendRequestRespVO
} from '@/api/im/friend/request' } from '@/api/im/friend/request'
import { useConversationStore } from './conversationStore' import { useConversationStore } from './conversationStore'
import { import { ImConversationType, ImFriendRequestHandleResult } from '../../utils/constants'
FRIEND_REQUEST_PAGE_SIZE, import { FRIEND_REQUEST_PAGE_SIZE } from '../../utils/config'
ImConversationType,
ImFriendRequestHandleResult
} from '../../utils/constants'
import { getCurrentUserId, imStorage, setQuietly, StorageKeys } from '../../utils/storage' import { getCurrentUserId, imStorage, setQuietly, StorageKeys } from '../../utils/storage'
import { getFriendDisplayName } from '../../utils/user' import { getFriendDisplayName } from '../../utils/user'
import type { Friend, FriendLite, FriendRequest } from '../types' import type { Friend, FriendLite, FriendRequest } from '../types'

View File

@ -128,12 +128,8 @@ import { computed } from 'vue'
import Icon from '@/components/Icon/src/Icon.vue' import Icon from '@/components/Icon/src/Icon.vue'
import { formatFileSize } from '@/utils/file' import { formatFileSize } from '@/utils/file'
import { formatSeconds } from '@/utils/formatTime' import { formatSeconds } from '@/utils/formatTime'
import { import { ImMessageType, isFriendChatTip, isGroupNotification } from '@/views/im/utils/constants'
ImMessageType, import { MESSAGE_MERGE_PREVIEW_LINES } from '@/views/im/utils/config'
MERGE_FORWARD_PREVIEW_LINES,
isFriendChatTip,
isGroupNotification
} from '@/views/im/utils/constants'
import CardLineLabel from '@/views/im/home/components/card/CardLineLabel.vue' import CardLineLabel from '@/views/im/home/components/card/CardLineLabel.vue'
import { import {
parseMessage, parseMessage,
@ -207,7 +203,7 @@ const mergePreviewLines = computed(() => {
return [] return []
} }
return mergePayload.value.messages return mergePayload.value.messages
.slice(0, MERGE_FORWARD_PREVIEW_LINES) .slice(0, MESSAGE_MERGE_PREVIEW_LINES)
.map((item) => `${item.senderNickname}${summarizeMessageContent(item)}`) .map((item) => `${item.senderNickname}${summarizeMessageContent(item)}`)
}) })

View File

@ -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_MINUTES5 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

View File

@ -202,27 +202,6 @@ export const ImFriendRequestHandleResult = {
REFUSED: 2 // 拒绝 REFUSED: 2 // 拒绝
} as const } 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 @ * @ userId atUserIds -1 @
* *
@ -234,12 +213,6 @@ export const IM_AT_ALL_USER_ID = -1
/** @全体成员 的展示名(对齐微信 PC */ /** @全体成员 的展示名(对齐微信 PC */
export const IM_AT_ALL_NICKNAME = '所有人' export const IM_AT_ALL_NICKNAME = '所有人'
/** 合并转发气泡内预览的最大行数(对齐微信「聊天记录」气泡) */
export const MERGE_FORWARD_PREVIEW_LINES = 3
/** 最近转发会话 key 列表的最大保留数量(对齐微信 PC 横向头像区可见容量) */
export const RECENT_FORWARD_MAX = 12
/** 转发模式SINGLE 逐条原样转 / MERGE 打包成 MergeMessage */ /** 转发模式SINGLE 逐条原样转 / MERGE 打包成 MergeMessage */
export const ImForwardMode = { export const ImForwardMode = {
SINGLE: 'single', SINGLE: 'single',