From fead28239529bf96e462d6251a09060bacbd3bb8 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Thu, 21 May 2026 15:57:46 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(im):=20=E4=BF=AE=E4=B8=80?= =?UTF-8?q?=E6=89=B9=E6=AD=A3=E7=A1=AE=E6=80=A7=20/=20UX=20=E7=BB=86?= =?UTF-8?q?=E8=8A=82=EF=BC=9A=E7=BE=A4=E5=90=8D=20trim=20=E7=A9=BA?= =?UTF-8?q?=E3=80=81=E6=95=8F=E6=84=9F=E8=AF=8D=20/=20=E5=B0=81=E7=A6=81?= =?UTF-8?q?=E7=90=86=E7=94=B1=E7=A9=BA=E7=99=BD=E6=A0=A1=E9=AA=8C=E3=80=81?= =?UTF-8?q?=E9=BB=98=E8=AE=A4=E7=BE=A4=E5=90=8D=E8=AE=A1=E5=85=A5=E5=88=9B?= =?UTF-8?q?=E5=BB=BA=E8=80=85=E3=80=81ack=20=E5=90=8E=E9=87=8D=E7=AE=97?= =?UTF-8?q?=E4=BC=9A=E8=AF=9D=E6=91=98=E8=A6=81=E3=80=81=E6=96=87=E6=9C=AC?= =?UTF-8?q?=E9=87=8D=E8=AF=95=E5=A4=8D=E7=94=A8=20clientMessageId=20?= =?UTF-8?q?=E9=98=B2=E9=87=8D=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../conversation/ConversationGroupSide.vue | 9 +++++++-- .../components/message/MessageItem.vue | 14 +++++++------- src/views/im/home/store/conversationStore.ts | 12 +++++++++--- src/views/im/manager/group/GroupBanForm.vue | 2 +- .../im/manager/sensitiveword/SensitiveWordForm.vue | 2 +- src/views/im/utils/group.ts | 3 ++- 6 files changed, 27 insertions(+), 15 deletions(-) diff --git a/src/views/im/home/pages/conversation/components/conversation/ConversationGroupSide.vue b/src/views/im/home/pages/conversation/components/conversation/ConversationGroupSide.vue index 34b633320..1567d432a 100644 --- a/src/views/im/home/pages/conversation/components/conversation/ConversationGroupSide.vue +++ b/src/views/im/home/pages/conversation/components/conversation/ConversationGroupSide.vue @@ -539,12 +539,17 @@ watch(visible, (v) => { } }) -/** 群主:保存群名(走 /im/group/update) */ +/** 群主:保存群名(走 /im/group/update);trim 后空字符串拒提交,与 saveGroupRemark 行为对齐 */ async function saveName() { if (!props.group) { return } - await updateGroup({ id: props.group.id, name: editName.value }) + const trimmed = editName.value.trim() + if (!trimmed) { + message.warning('群名称不能为空') + return + } + await updateGroup({ id: props.group.id, name: trimmed }) namePopoverVisible.value = false message.success('保存成功') emit('reload') 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 fc2accbcb..70688a712 100644 --- a/src/views/im/home/pages/conversation/components/message/MessageItem.vue +++ b/src/views/im/home/pages/conversation/components/message/MessageItem.vue @@ -939,9 +939,9 @@ async function handleRecall() { * 失败消息点击重试 * * - 媒体消息(image / file / voice / video):_localFile 在内存就重走 uploadAndSendMedia(重新上传 + 占位 + 进度) - * - 文本消息:移除 FAILED 占位 + 用原 content 走一遍 sendRaw 新建占位 + * - 文本消息:复用原 clientMessageId + status 回滚到 SENDING,走 existingClientMessageId 路径让服务端按 cmid 幂等 * - * 媒体类型若 _localFile 已丢(理论上 IDB 恢复阶段就被 drop,进不到这里;保险起见仍走文本兜底)则按 sendRaw 重发, + * 媒体类型若 _localFile 已丢(理论上 IDB 恢复阶段就被 drop,进不到这里;保险起见仍走文本兜底)则按文本路径重发, * 后端拒绝失效 blob URL 时再次 FAILED,用户可右键删除 * * 不还原原 receipt:群回执是发送时的扩展选项、不会持久化到 message,强行猜测可能与原意不符; @@ -983,13 +983,13 @@ async function handleResend() { } } - // 文本类型 / 媒体类型但 _localFile 已丢:原 content 走 sendRaw 重发 - conversationStore.removeMessage(conversation.type, conversation.targetId, { - id: message.id, - clientMessageId: message.clientMessageId + // 文本类型 / 媒体类型但 _localFile 已丢:把 FAILED 占位回滚到 SENDING,复用 clientMessageId 让服务端按 cmid 幂等去重 + conversationStore.patchMessage(conversation.type, conversation.targetId, message.clientMessageId, { + status: ImMessageStatus.SENDING }) await sendRaw(message.type, message.content, { - atUserIds: message.atUserIds + atUserIds: message.atUserIds, + existingClientMessageId: message.clientMessageId }) } diff --git a/src/views/im/home/store/conversationStore.ts b/src/views/im/home/store/conversationStore.ts index 03932c8f8..a3b47c61f 100644 --- a/src/views/im/home/store/conversationStore.ts +++ b/src/views/im/home/store/conversationStore.ts @@ -657,11 +657,17 @@ export const useConversationStore = defineStore('imConversationStore', { if (!conversation) { return } - const message = conversation.messages.find((item) => item.clientMessageId === clientMessageId) - if (!message) { + const messageIndex = conversation.messages.findIndex( + (item) => item.clientMessageId === clientMessageId + ) + if (messageIndex < 0) { return } - applyServerMessageUpdate(message, updates) + applyServerMessageUpdate(conversation.messages[messageIndex], updates) + // ack 命中末尾消息时按服务端 sendTime / content 重算会话摘要,让会话列表跟着权威值排序 + if (messageIndex === conversation.messages.length - 1) { + recomputeConversationLast(conversation) + } if (updates.id) { this.updateMaxId(conversationType, updates.id) } diff --git a/src/views/im/manager/group/GroupBanForm.vue b/src/views/im/manager/group/GroupBanForm.vue index c094d6d76..438e3c8a9 100644 --- a/src/views/im/manager/group/GroupBanForm.vue +++ b/src/views/im/manager/group/GroupBanForm.vue @@ -34,7 +34,7 @@ const formLoading = ref(false) // 提交的加载中 const formData = reactive({ id: 0, groupName: '', reason: '' }) // 封禁表单 const formRef = ref() // 表单 Ref const formRules = { - reason: [{ required: true, message: '封禁原因不能为空', trigger: 'blur' }] + reason: [{ required: true, whitespace: true, message: '封禁原因不能为空', trigger: 'blur' }] } /** 打开弹窗 */ diff --git a/src/views/im/manager/sensitiveword/SensitiveWordForm.vue b/src/views/im/manager/sensitiveword/SensitiveWordForm.vue index 3c08f46f1..b947d6e33 100644 --- a/src/views/im/manager/sensitiveword/SensitiveWordForm.vue +++ b/src/views/im/manager/sensitiveword/SensitiveWordForm.vue @@ -54,7 +54,7 @@ const formData = ref({ status: CommonStatusEnum.ENABLE }) const formRules = reactive({ - word: [{ required: true, message: '敏感词不能为空', trigger: 'blur' }], + word: [{ required: true, whitespace: true, message: '敏感词不能为空', trigger: 'blur' }], status: [{ required: true, message: '状态不能为空', trigger: 'blur' }] }) const formRef = ref() // 表单 Ref diff --git a/src/views/im/utils/group.ts b/src/views/im/utils/group.ts index bff86b92b..2118a5c8a 100644 --- a/src/views/im/utils/group.ts +++ b/src/views/im/utils/group.ts @@ -10,7 +10,8 @@ export function buildDefaultGroupName(members: FriendLite[]): string { const names = members.slice(0, 4).map((m) => m.displayName || m.nickname || '') const head = names.filter(Boolean).join('、') if (members.length > 4) { - return `${head}等${members.length}人` + // members 只含被选好友,+1 把创建者也计入实际成员数 + return `${head}等${members.length + 1}人` } return head || '群聊' }