feat(im): 重构普通消息类型,和 openim 的消息编号对齐
parent
055d4bab27
commit
a9f54fdee1
|
|
@ -127,7 +127,7 @@ const lastSenderDisplayName = computed(() => {
|
|||
)
|
||||
})
|
||||
|
||||
/** 群聊 + 有最后发送者 + 最后一条是普通消息时,显示发送者前缀(TIP_TEXT / RECALL / 草稿态不带前缀) */
|
||||
/** 群聊 + 有最后发送者 + 最后一条是普通消息时,显示发送者前缀(FRIEND_* / GROUP_* / RECALL / 草稿态不带前缀) */
|
||||
const showSendName = computed(() => {
|
||||
if (draft.value) {
|
||||
return false
|
||||
|
|
|
|||
|
|
@ -143,16 +143,16 @@
|
|||
v-for="message in currentList"
|
||||
:key="message.id || message.clientMessageId"
|
||||
>
|
||||
<!-- TIP_TEXT 系统提示("你们已成为好友"等):居中灰色,不挂头像 / sender,
|
||||
<!-- 好友会话事件(FRIEND_ADD / FRIEND_DELETE):居中灰色,不挂头像 / sender,
|
||||
跟主聊天面板里 MessageItem 的渲染语义对齐 -->
|
||||
<div
|
||||
v-if="message.type === ImMessageType.TIP_TEXT"
|
||||
v-if="isFriendChatTip(message.type)"
|
||||
class="px-4 py-3 text-12px text-center italic text-[var(--el-text-color-secondary)] border-b border-[var(--el-border-color-lighter)]"
|
||||
>
|
||||
{{ resolveTipText(message.content) }}
|
||||
{{ resolveFriendNotificationText(message) }}
|
||||
</div>
|
||||
|
||||
<!-- 群广播事件文案:跟 TIP_TEXT 同样的居中灰色样式 -->
|
||||
<!-- 群广播事件文案:跟好友事件同灰色样式 -->
|
||||
<div
|
||||
v-else-if="isGroupNotification(message.type)"
|
||||
class="px-4 py-3 text-12px text-center italic text-[var(--el-text-color-secondary)] border-b border-[var(--el-border-color-lighter)]"
|
||||
|
|
@ -312,14 +312,19 @@ import {
|
|||
getMemberDisplayName,
|
||||
getSenderDisplayName,
|
||||
getSenderRealNickname,
|
||||
resolveFriendNotificationText,
|
||||
resolveGroupNotificationText
|
||||
} from '@/views/im/utils/user'
|
||||
import { buildRecallTip } from '@/views/im/utils/conversation'
|
||||
import { useMessagePuller } from '@/views/im/home/composables/useMessagePuller'
|
||||
import { ImConversationType, ImMessageType, isGroupNotification } from '@/views/im/utils/constants'
|
||||
import {
|
||||
ImConversationType,
|
||||
ImMessageType,
|
||||
isFriendChatTip,
|
||||
isGroupNotification
|
||||
} from '@/views/im/utils/constants'
|
||||
import {
|
||||
parseMessage,
|
||||
resolveTipText,
|
||||
getFileIconInfo,
|
||||
type TextMessage,
|
||||
type ImageMessage,
|
||||
|
|
@ -664,11 +669,12 @@ function audioOf(message: Message): AudioMessage | null {
|
|||
|
||||
/** 关键字命中文本:文本类返回原文、文件返回文件名(利于按文件名搜)、其他返回占位词 */
|
||||
function textSnippetOf(message: Message): string {
|
||||
if (isFriendChatTip(message.type)) {
|
||||
return resolveFriendNotificationText(message)
|
||||
}
|
||||
switch (message.type) {
|
||||
case ImMessageType.TEXT:
|
||||
return parseMessage<TextMessage>(message.content)?.content ?? ''
|
||||
case ImMessageType.TIP_TEXT:
|
||||
return resolveTipText(message.content)
|
||||
case ImMessageType.IMAGE:
|
||||
return '[图片]'
|
||||
case ImMessageType.FILE:
|
||||
|
|
|
|||
|
|
@ -7,15 +7,15 @@
|
|||
{{ formatTipTime(message.sendTime) }}
|
||||
</div>
|
||||
|
||||
<!-- 系统提示文案(TIP_TEXT=21) -->
|
||||
<!-- 好友会话事件(FRIEND_ADD / FRIEND_DELETE):跟群广播事件同灰色样式,文案固定 -->
|
||||
<div
|
||||
v-if="isTipText"
|
||||
v-if="isFriendChatTipMessage"
|
||||
class="flex items-center justify-center px-4 py-2 text-12px text-[var(--el-text-color-secondary)]"
|
||||
>
|
||||
{{ tipText }}
|
||||
{{ friendChatTipText }}
|
||||
</div>
|
||||
|
||||
<!-- 群广播事件:跟 TIP_TEXT 同样的居中灰色样式,文案按 type 拼装 -->
|
||||
<!-- 群广播事件:跟好友事件同灰色样式,文案按 type 拼装 -->
|
||||
<div
|
||||
v-else-if="isGroupNotificationMessage"
|
||||
class="flex items-center justify-center px-4 py-2 text-12px text-[var(--el-text-color-secondary)]"
|
||||
|
|
@ -227,6 +227,7 @@ import {
|
|||
ImConversationType,
|
||||
ImGroupMemberRole,
|
||||
TIME_TIP_GAP_MS,
|
||||
isFriendChatTip,
|
||||
isGroupNotification,
|
||||
isNormalMessage
|
||||
} from '@/views/im/utils/constants'
|
||||
|
|
@ -236,7 +237,6 @@ import {
|
|||
buildQuoteFromMessage,
|
||||
getQuoteFromMessage,
|
||||
parseMessage,
|
||||
resolveTipText,
|
||||
getFileIconInfo,
|
||||
type TextMessage,
|
||||
type ImageMessage,
|
||||
|
|
@ -256,10 +256,12 @@ import {
|
|||
getMemberDisplayName,
|
||||
getSenderDisplayName,
|
||||
getSenderRealNickname,
|
||||
resolveFriendNotificationText,
|
||||
resolveGroupNotificationText
|
||||
} from '@/views/im/utils/user'
|
||||
import { useImUiStore } from '../../../../store/uiStore'
|
||||
import { useMessageSender } from '../../../../composables/useMessageSender'
|
||||
import { useMuteOverlay } from '../../../../composables/useMuteOverlay'
|
||||
import type { Message } from '../../../../types'
|
||||
import MessageReadStatus from './MessageReadStatus.vue'
|
||||
import ReplyPreview from './ReplyPreview.vue'
|
||||
|
|
@ -292,6 +294,7 @@ const friendStore = useFriendStore()
|
|||
const draftStore = useDraftStore()
|
||||
const uiStore = useImUiStore()
|
||||
const { recall, sendRaw } = useMessageSender()
|
||||
const muteOverlay = useMuteOverlay()
|
||||
// 仅用 confirm,避免 message 跟 props.message 同名冲突(vue/no-dupe-keys)
|
||||
const { confirm: confirmDialog, success: successMessage } = useMessage()
|
||||
|
||||
|
|
@ -300,8 +303,8 @@ const { confirm: confirmDialog, success: successMessage } = useMessage()
|
|||
/** 是否已撤回:pull / WS 两路都会调 recallMessage 把原消息更新为 type=RECALL,渲染只需识别 type */
|
||||
const isRecall = computed(() => props.message.type === ImMessageType.RECALL)
|
||||
|
||||
/** 系统提示文案 */
|
||||
const isTipText = computed(() => props.message.type === ImMessageType.TIP_TEXT)
|
||||
/** 是否会话内好友事件气泡(FRIEND_ADD / FRIEND_DELETE) */
|
||||
const isFriendChatTipMessage = computed(() => isFriendChatTip(props.message.type))
|
||||
|
||||
/** 是否在当前消息上方渲染时间分隔条:列表第一条 / 距上一条超过阈值;缺 sendTime 不渲染 */
|
||||
const shouldShowTimeTip = computed(() => {
|
||||
|
|
@ -384,8 +387,8 @@ function formatTipTime(timestamp: number): string {
|
|||
/** 文本内容 */
|
||||
const textContent = computed(() => parseMessage<TextMessage>(props.message.content)?.content ?? '')
|
||||
|
||||
/** TIP_TEXT 文案:与 conversationStore.resolveLastContent / MessageHistory.renderContent 共用 helper,避免兼容性逻辑分裂 */
|
||||
const tipText = computed(() => resolveTipText(props.message.content))
|
||||
/** 好友会话事件文案:FRIEND_ADD / FRIEND_DELETE 渲染成灰色提示,文案固定 */
|
||||
const friendChatTipText = computed(() => resolveFriendNotificationText(props.message))
|
||||
|
||||
/** 群广播事件 */
|
||||
const isGroupNotificationMessage = computed(() => isGroupNotification(props.message.type))
|
||||
|
|
@ -561,10 +564,10 @@ const RECALL_WINDOW_MS = 2 * 60 * 1000
|
|||
* - 引用:已落库(id≠0)+ 未撤回的消息可引用,引用块写入 draftStore.reply
|
||||
* - 撤回 / 删除:互斥;自己发送 + 已落库 + 未撤回 + 2 分钟内显示「撤回」(推服务器),其它显示「删除」(仅本地清)
|
||||
*
|
||||
* TIP_TEXT 态不弹菜单
|
||||
* 好友事件气泡态不弹菜单
|
||||
*/
|
||||
async function handleContextMenu(e: MouseEvent) {
|
||||
if (isTipText.value) {
|
||||
if (isFriendChatTipMessage.value) {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -761,6 +764,10 @@ async function handleResend() {
|
|||
if (!conversation) {
|
||||
return
|
||||
}
|
||||
// 禁言 / 封禁时拦截:避免重试绕过 MessageInput 的 muteOverlay 又走一次 sendRaw 让后端拒
|
||||
if (muteOverlay.value) {
|
||||
return
|
||||
}
|
||||
conversationStore.removeMessage(conversation.type, conversation.targetId, {
|
||||
id: props.message.id,
|
||||
clientMessageId: props.message.clientMessageId
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import {
|
|||
ImWebSocketMessageType,
|
||||
ImMessageType,
|
||||
ImConversationType,
|
||||
isFriendChatTip,
|
||||
isFriendNotification,
|
||||
isNormalMessage
|
||||
} from '../../utils/constants'
|
||||
|
|
@ -77,9 +78,9 @@ const convertGroupMessage = (
|
|||
* 2. 帧分发:dispatchFrame → dispatchPrivateFrame / dispatchGroupFrame,按 ImMessageType 再分流
|
||||
* 3. 缓冲:初始化加载期(conversationStore.loading=true)暂存消息,等 pull 完成后由 useMessagePuller 调 flushBuffer 回放
|
||||
* 4. 事件处理(按类型分发到对应 handle*,联动 conversation / friend / group store):
|
||||
* - 普通消息(TEXT / IMAGE / FILE / VOICE / VIDEO / TIP_TEXT):入库 + 当前会话自动已读 / 提示音
|
||||
* - 普通消息(TEXT / IMAGE / FILE / VOICE / VIDEO):入库 + 当前会话自动已读 / 提示音
|
||||
* - 已读 / 回执(READ / RECEIPT):多端已读同步、对方读后回执
|
||||
* - 好友变更(FRIEND_ADD / DELETE / UPDATE):同步 friendStore + 级联刷新私聊会话
|
||||
* - 好友变更(FRIEND_*):同步 friendStore + 级联刷新私聊会话;FRIEND_ADD / FRIEND_DELETE 额外插入会话气泡
|
||||
* - 群个人信号(GROUP_MEMBER_SETTING_UPDATE):同步 groupStore + 级联刷新群聊会话
|
||||
* - 群广播事件(GROUP_*):走 handleGroupMessage + applyGroupNotification 旁路(含 DISSOLVE / QUIT / KICK 自判清群)
|
||||
*/
|
||||
|
|
@ -224,8 +225,13 @@ export const useImWebSocketStore = defineStore('imWebSocketStore', {
|
|||
default:
|
||||
if (isFriendNotification(websocketMessage.type)) {
|
||||
this.handleFriendNotification(websocketMessage)
|
||||
// FRIEND_ADD / FRIEND_DELETE 同时作为会话事件气泡插入消息列表(becomeFriends 入库
|
||||
// 帧 + silent / delete 单边推送帧统一走入库去重路径,前端按 type 渲染灰色提示)
|
||||
if (isFriendChatTip(websocketMessage.type)) {
|
||||
this.handlePrivateMessage(websocketMessage)
|
||||
}
|
||||
} else {
|
||||
// TEXT / IMAGE / FILE / VOICE / VIDEO / TIP_TEXT 等普通消息
|
||||
// TEXT / IMAGE / FILE / VOICE / VIDEO 等普通消息
|
||||
this.handlePrivateMessage(websocketMessage)
|
||||
}
|
||||
}
|
||||
|
|
@ -253,7 +259,7 @@ export const useImWebSocketStore = defineStore('imWebSocketStore', {
|
|||
this.handleGroupMemberSettingUpdate(websocketMessage)
|
||||
break
|
||||
default:
|
||||
// TEXT / IMAGE / FILE / VOICE / VIDEO / TIP_TEXT + GROUP_* 群广播事件
|
||||
// TEXT / IMAGE / FILE / VOICE / VIDEO + GROUP_* 群广播事件
|
||||
this.handleGroupMessage(websocketMessage)
|
||||
}
|
||||
} catch (e) {
|
||||
|
|
@ -263,7 +269,7 @@ export const useImWebSocketStore = defineStore('imWebSocketStore', {
|
|||
},
|
||||
|
||||
/**
|
||||
* 私聊普通消息(TEXT / IMAGE / FILE / VOICE / VIDEO / TIP_TEXT)入库 + 自动已读
|
||||
* 私聊普通消息(TEXT / IMAGE / FILE / VOICE / VIDEO)入库 + 自动已读
|
||||
*
|
||||
* 流程:
|
||||
* 1. 离线加载期缓冲(避开与 pull 回填的竞态)
|
||||
|
|
@ -334,7 +340,7 @@ export const useImWebSocketStore = defineStore('imWebSocketStore', {
|
|||
console.warn('[IM WS] 自动已读上报失败', e)
|
||||
})
|
||||
} else if (!conversation?.silent && isNormalMessage(websocketMessage.type)) {
|
||||
// 非当前会话且未免打扰:响一下提示音(带节流,详见 playAudioTip);TIP_TEXT 等系统提示不响
|
||||
// 非当前会话且未免打扰:响一下提示音(带节流,详见 playAudioTip);FRIEND_* 等系统事件不响
|
||||
playAudioTip()
|
||||
}
|
||||
}
|
||||
|
|
@ -440,7 +446,7 @@ export const useImWebSocketStore = defineStore('imWebSocketStore', {
|
|||
console.warn('[IM WS] 自动已读上报失败', e)
|
||||
})
|
||||
} else if (!conversation?.silent && isNormalMessage(websocketMessage.type)) {
|
||||
// GROUP_* 群广播事件 / TIP_TEXT 等系统提示不响提示音
|
||||
// GROUP_* 群广播事件等系统消息不响提示音
|
||||
playAudioTip()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<!-- 文本 / 系统提示文本:直接显示纯文本 -->
|
||||
<!-- 文本:直接显示纯文本 -->
|
||||
<span v-if="isText" class="whitespace-pre-wrap break-all">{{ textContent }}</span>
|
||||
|
||||
<!-- 图片:缩略图 + 点击放大 -->
|
||||
|
|
@ -84,7 +84,15 @@
|
|||
{{ groupNotificationText }}
|
||||
</span>
|
||||
|
||||
<!-- 系统事件类(FRIEND_*):content 通常是结构化 JSON,回退原始预览 -->
|
||||
<!-- 好友会话事件(FRIEND_ADD / FRIEND_DELETE):固定中文文案 -->
|
||||
<span
|
||||
v-else-if="isFriendChatTipType"
|
||||
class="text-12px text-[var(--el-text-color-secondary)]"
|
||||
>
|
||||
{{ friendChatTipText }}
|
||||
</span>
|
||||
|
||||
<!-- 其它系统事件 / 未知类型:content 通常是结构化 JSON,回退原始预览 -->
|
||||
<span v-else class="whitespace-pre-wrap break-all">{{ fallbackText }}</span>
|
||||
</template>
|
||||
|
||||
|
|
@ -93,16 +101,23 @@ import { computed } from 'vue'
|
|||
import Icon from '@/components/Icon/src/Icon.vue'
|
||||
import { formatFileSize } from '@/utils/file'
|
||||
import { formatSeconds } from '@/utils/formatTime'
|
||||
import { ImMessageType, isGroupNotification } from '@/views/im/utils/constants'
|
||||
import {
|
||||
ImMessageType,
|
||||
isFriendChatTip,
|
||||
isGroupNotification
|
||||
} from '@/views/im/utils/constants'
|
||||
import {
|
||||
parseMessage,
|
||||
resolveTipText,
|
||||
type ImageMessage,
|
||||
type FileMessage,
|
||||
type AudioMessage,
|
||||
type VideoMessage
|
||||
type VideoMessage,
|
||||
type TextMessage
|
||||
} from '@/views/im/utils/message'
|
||||
import { resolveGroupNotificationText } from '@/views/im/utils/user'
|
||||
import {
|
||||
resolveFriendNotificationText,
|
||||
resolveGroupNotificationText
|
||||
} from '@/views/im/utils/user'
|
||||
|
||||
defineOptions({ name: 'ImMessageContentPreview' })
|
||||
|
||||
|
|
@ -116,16 +131,16 @@ const props = defineProps<{
|
|||
}>()
|
||||
|
||||
/** 各类型判定 */
|
||||
const isText = computed(
|
||||
() => props.type === ImMessageType.TEXT || props.type === ImMessageType.TIP_TEXT
|
||||
)
|
||||
const isText = computed(() => props.type === ImMessageType.TEXT)
|
||||
const isImage = computed(() => props.type === ImMessageType.IMAGE)
|
||||
const isFile = computed(() => props.type === ImMessageType.FILE)
|
||||
const isVoice = computed(() => props.type === ImMessageType.VOICE)
|
||||
const isVideo = computed(() => props.type === ImMessageType.VIDEO)
|
||||
|
||||
/** 文本内容:兼容 JSON 包裹和裸字符串两种形态 */
|
||||
const textContent = computed(() => resolveTipText(props.content || ''))
|
||||
/** 文本内容:从 TextMessage payload 取 .content */
|
||||
const textContent = computed(
|
||||
() => parseMessage<TextMessage>(props.content || '')?.content ?? ''
|
||||
)
|
||||
|
||||
const imagePayload = computed(() =>
|
||||
isImage.value ? parseMessage<ImageMessage>(props.content || '') : null
|
||||
|
|
@ -197,6 +212,12 @@ const fallbackText = computed(() => {
|
|||
return raw
|
||||
})
|
||||
|
||||
/** 是否好友会话事件气泡(FRIEND_ADD / FRIEND_DELETE) */
|
||||
const isFriendChatTipType = computed(() => isFriendChatTip(props.type ?? -1))
|
||||
|
||||
/** 好友会话事件文案:固定文案,不依赖 payload */
|
||||
const friendChatTipText = computed(() => resolveFriendNotificationText({ type: props.type }))
|
||||
|
||||
/** 是否群广播事件 */
|
||||
const isGroupNotificationType = computed(() => isGroupNotification(props.type ?? -1))
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,15 @@
|
|||
/** IM 消息类型枚举(对齐后端 ImMessageTypeEnum) */
|
||||
export const ImMessageType = {
|
||||
TEXT: 0, // 文本
|
||||
IMAGE: 1, // 图片
|
||||
FILE: 2, // 文件
|
||||
VOICE: 3, // 语音
|
||||
VIDEO: 4, // 视频
|
||||
RECALL: 10, // 撤回
|
||||
READ: 11, // 已读
|
||||
RECEIPT: 12, // 回执
|
||||
TIP_TEXT: 21, // 提示文本(撤回提示等)
|
||||
// ========== 用户聊天消息(101-105 直接复用 OpenIM 段位编号) ==========
|
||||
TEXT: 101, // 文本(对应 OpenIM Text=101)
|
||||
IMAGE: 102, // 图片(对应 OpenIM Picture=102)
|
||||
VOICE: 103, // 语音(对应 OpenIM Sound=103)
|
||||
VIDEO: 104, // 视频(对应 OpenIM Video=104)
|
||||
FILE: 105, // 文件(对应 OpenIM File=105)
|
||||
// ========== 信号类(2101 / 2200 直接复用 OpenIM 段位编号;2201 自有扩展) ==========
|
||||
RECALL: 2101, // 撤回(对应 OpenIM RevokeNotification=2101)
|
||||
RECEIPT: 2200, // 回执(对应 OpenIM HasReadReceipt=2200)
|
||||
READ: 2201, // 已读(多端同步,OpenIM 无对应;自有扩展)
|
||||
// ========== 好友通知(1201-1210 直接复用 OpenIM 段位编号) ==========
|
||||
FRIEND_REQUEST_APPROVED: 1201, // 好友申请被同意
|
||||
FRIEND_REQUEST_REJECTED: 1202, // 好友申请被拒绝
|
||||
|
|
@ -62,6 +63,11 @@ export function isFriendNotification(type: number): boolean {
|
|||
return type >= ImMessageType.FRIEND_REQUEST_APPROVED && type <= ImMessageType.FRIEND_UPDATE
|
||||
}
|
||||
|
||||
/** 判断是否「会话内的好友事件气泡」:FRIEND_ADD / FRIEND_DELETE 直接渲染成灰色提示,与群事件同处理 */
|
||||
export function isFriendChatTip(type: number): boolean {
|
||||
return type === ImMessageType.FRIEND_ADD || type === ImMessageType.FRIEND_DELETE
|
||||
}
|
||||
|
||||
/** IM 普通消息类型集合(聊天气泡中显示,并作为会话最后一条摘要) */
|
||||
const ImMessageTypeNormals: number[] = [
|
||||
ImMessageType.TEXT,
|
||||
|
|
|
|||
|
|
@ -6,9 +6,13 @@
|
|||
// 2. fallbackName 由调用方传入(典型来源:Conversation.lastSenderDisplayName 快照),透传到 getSenderDisplayName 内部,算不出真名时兜底
|
||||
// ====================================================================
|
||||
|
||||
import { ImMessageType, isGroupNotification } from './constants'
|
||||
import { parseMessage, resolveTipText, type TextMessage } from './message'
|
||||
import { getSenderDisplayName, resolveGroupNotificationText } from './user'
|
||||
import { ImMessageType, isFriendChatTip, isGroupNotification } from './constants'
|
||||
import { parseMessage, type TextMessage } from './message'
|
||||
import {
|
||||
getSenderDisplayName,
|
||||
resolveFriendNotificationText,
|
||||
resolveGroupNotificationText
|
||||
} from './user'
|
||||
import type { Message } from '../home/types'
|
||||
|
||||
/** 会话主键:`type-targetId` 拼成稳定字符串,给 v-for :key、active 比对、map key 等场景共用 */
|
||||
|
|
@ -62,10 +66,10 @@ export function resolveConversationLastContent(
|
|||
)
|
||||
case ImMessageType.TEXT:
|
||||
return parseMessage<TextMessage>(message.content)?.content ?? ''
|
||||
case ImMessageType.TIP_TEXT:
|
||||
// TIP_TEXT 后端常发裸字符串(私聊好友建立 / 解除等),不能按 TextMessage JSON 解析,否则摘要变空
|
||||
return resolveTipText(message.content)
|
||||
default:
|
||||
if (isFriendChatTip(message.type)) {
|
||||
return resolveFriendNotificationText(message)
|
||||
}
|
||||
if (isGroupNotification(message.type)) {
|
||||
return resolveGroupNotificationText(message)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,9 +8,6 @@ import type { Message } from '../home/types'
|
|||
// cn.iocoder.yudao.module.im.service.websocket.dto.message.* 下的 DTO。
|
||||
// 各类消息 payload interface 字段对齐后端;解析统一用 parseMessage<T>,
|
||||
// 序列化直接 JSON.stringify(payload)。
|
||||
//
|
||||
// 例外:TIP_TEXT(私聊好友建立 / 解除等系统提示)后端会直接发裸字符串,
|
||||
// 展示侧需走 resolveTipText 兼容裸字符串 + 老接口可能的 {"content":"..."} 两种形态。
|
||||
// ====================================================================
|
||||
|
||||
// ==================== 客户端 ID ====================
|
||||
|
|
@ -146,34 +143,11 @@ export const getQuoteFromMessage = (content: string): QuoteMessage | null => {
|
|||
return parsed?.quote ?? null
|
||||
}
|
||||
|
||||
// ==================== TIP_TEXT ====================
|
||||
|
||||
/**
|
||||
* 解析 TIP_TEXT(系统提示)文案
|
||||
*
|
||||
* 后端:私聊好友建立 / 解除等系统提示直接发裸字符串;老接口可能包成 {"content": "..."}。
|
||||
* 解析得到 .content 就用,否则当裸文案返回,避免出现空行。
|
||||
*
|
||||
* MessageItem / conversationStore.resolveLastContent / MessageHistory.renderContent 三处共用,
|
||||
* 修一处兼容性问题不会漏到另两处
|
||||
*/
|
||||
export const resolveTipText = (content: string): string => {
|
||||
const raw = content || ''
|
||||
if (!raw) {
|
||||
return ''
|
||||
}
|
||||
const parsed = parseMessage<TextMessage>(raw)
|
||||
if (parsed && typeof parsed.content === 'string') {
|
||||
return parsed.content
|
||||
}
|
||||
return raw
|
||||
}
|
||||
|
||||
// ==================== 撤回 ====================
|
||||
|
||||
/**
|
||||
* 从后端下发的撤回 TIP_TEXT content 中解析出被撤回的原消息 id
|
||||
* content 形如 `{"messageId": 123}`,若不含 messageId 则返回 0(表示这条不是撤回 tip)
|
||||
* 从后端下发的撤回 RecallMessage content 中解析出被撤回的原消息 id
|
||||
* content 形如 `{"messageId": 123}`,若不含 messageId 则返回 0(表示这条不是撤回消息)
|
||||
*/
|
||||
export const parseRecallMessageId = (content: string): number => {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -258,6 +258,18 @@ export function resolveGroupNotificationText(
|
|||
}
|
||||
}
|
||||
|
||||
/** 会话内好友事件文案:FRIEND_ADD / FRIEND_DELETE 渲染成灰色提示气泡,文案固定不依赖 payload */
|
||||
export function resolveFriendNotificationText(message: { type?: number }): string {
|
||||
switch (message.type) {
|
||||
case ImMessageType.FRIEND_ADD:
|
||||
return '你们已经是好友了,开始聊天吧'
|
||||
case ImMessageType.FRIEND_DELETE:
|
||||
return '你已删除好友'
|
||||
default:
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
/** 性别图标:男 1 / 女 2,0 / null / undefined 一律不展示,对齐微信留白 */
|
||||
export function getGenderIcon(sex?: number): string {
|
||||
if (sex === 1) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue