✨ feat(im): 修一批正确性 / UX 细节:群名 trim 空、敏感词 / 封禁理由空白校验、默认群名计入创建者、ack 后重算会话摘要、文本重试复用 clientMessageId 防重复
parent
73aa578c9b
commit
fead282395
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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' }]
|
||||
}
|
||||
|
||||
/** 打开弹窗 */
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 || '群聊'
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue