feat(im): 优化名片消息类型 v0.4:增加转发成功失败的提示

im
YunaiV 2026-05-06 12:18:31 +08:00
parent 1ac0650984
commit 4868d69ed8
3 changed files with 50 additions and 15 deletions

View File

@ -293,7 +293,12 @@ function buildCardContent(user: User): string {
return serializeMessage(payload)
}
/** 确认发送:每个选中会话先发 CARD 再发 TEXT 留言;失败的消息会以 FAILED 状态留在对应会话气泡里供右键重试 */
/**
* 确认发送每个选中会话先发 CARDCARD 成功后才发留言保证先看到名片的顺序意图CARD 失败时不发留言避免错序
*
* 文案聚合全部成功已转发全部失败转发失败AB部分失败已转发 XY 失败具体列出失败会话名方便定位
* 失败的消息以 FAILED 状态留在对应会话气泡里可右键重试
*/
async function handleSend() {
const user = props.user
if (!user?.id || selectedKeys.value.length === 0) {
@ -305,13 +310,22 @@ async function handleSend() {
sending.value = true
try {
const tasks = targets.map(async (target) => {
await sendRaw(ImMessageType.CARD, cardContent, { conversation: target })
if (leaveText) {
await send(leaveText, { conversation: target })
const cardOk = await sendRaw(ImMessageType.CARD, cardContent, { conversation: target })
if (!cardOk) {
return { target, ok: false }
}
const ok = leaveText ? await send(leaveText, { conversation: target }) : true
return { target, ok }
})
await Promise.all(tasks)
message.success('已转发')
const results = await Promise.all(tasks)
const failedNames = results.filter((r) => !r.ok).map((r) => r.target.name || '未命名会话')
if (failedNames.length === 0) {
message.success('已转发')
} else if (failedNames.length === targets.length) {
message.error(`转发失败:${failedNames.join('、')}`)
} else {
message.warning(`已转发,但 ${failedNames.join('、')} 失败`)
}
visible.value = false
} finally {
sending.value = false

View File

@ -83,16 +83,22 @@ export const useMessageSender = () => {
*
* 1.
* 2. type / content
* 3. true / false FAILED false
* /
*/
const sendRaw = async (type: number, content: string, options?: SendExtOptions) => {
const sendRaw = async (
type: number,
content: string,
options?: SendExtOptions
): Promise<boolean> => {
// 1. 参数校验:优先用显式传入的 conversation转发场景否则取激活会话
const conversation = options?.conversation ?? conversationStore.activeConversation
if (!conversation) {
return
return false
}
const realTarget = options?.targetId || conversation.targetId
if (!realTarget) {
return
return false
}
// 2. 准备 clientMessageId媒体上传链路在 step 1 已经 insertMessage 占位,这里直接复用 id其余场景走默认乐观插入
@ -106,7 +112,7 @@ export const useMessageSender = () => {
(m) => m.clientMessageId === clientMessageId
)
if (!stillExists) {
return
return false
}
} else {
clientMessageId = generateClientMessageId()
@ -159,21 +165,26 @@ export const useMessageSender = () => {
content: data.content
})
}
return true
} catch (e) {
console.error('[IM] 消息发送失败', { type, realTarget, clientMessageId }, e)
conversationStore.ackMessage(conversation.type, realTarget, clientMessageId, {
status: ImMessageStatus.FAILED
})
return false
}
}
/** 发送文本消息最常用的快捷入口MessageInput.vue 文本回车走这里 */
const send = async (text: string, options?: SendExtOptions) => {
/**
* MessageInput.vue
* true / false / false sendRaw
*/
const send = async (text: string, options?: SendExtOptions): Promise<boolean> => {
if (!text.trim()) {
return
return false
}
const payload = withQuotePayload<TextMessage>({ content: text }, options?.quote)
await sendRaw(ImMessageType.TEXT, serializeMessage(payload), options)
return sendRaw(ImMessageType.TEXT, serializeMessage(payload), options)
}
/**

View File

@ -69,7 +69,17 @@ export function isFriendChatTip(type: number): boolean {
return type === ImMessageType.FRIEND_ADD || type === ImMessageType.FRIEND_DELETE
}
/** IM 普通消息类型集合(聊天气泡中显示,并作为会话最后一条摘要) */
/**
* IM normal vs event ImMessageTypeEnum.normal
*
*
* 1. Im{Private,Group}MessageSendReqVO.isNormalType normal=true
* 2. / websocketStore normal +
* 3. lastType / @ ConversationItem normal
* 4. MessageItem.vue canPin normal /
*
* CARD1/2/3 4 =
*/
const ImMessageTypeNormals: number[] = [
ImMessageType.TEXT,
ImMessageType.IMAGE,