fix(im): 修复群备注首屏展示和聊天列表名称覆盖
- 后端群 VO 返回当前用户维度的 groupRemark 和 silent - 群列表构建时通过成员关系回填个人群设置,并继续仅对有效成员回填置顶消息 - Vue3 群列表同步时以接口返回的个人群设置为准,只保留成员缓存 - 会话名写入入口统一使用 getGroupDisplayName,避免群备注被原群名覆盖 - 空群头像且成员未加载时异步预拉群成员,用于合成群头像 - 启用 IM Maven 模块和 yudao-server 对 IM 模块的依赖im
parent
ab2fa4e6b8
commit
61c9e1acf2
|
|
@ -17,6 +17,8 @@ export interface ImGroupRespVO {
|
||||||
createTime?: string // 创建时间
|
createTime?: string // 创建时间
|
||||||
pinnedMessages?: ImGroupMessageRespVO[] // 群置顶消息列表(后端关联回填,仅当登录用户是群成员时非空)
|
pinnedMessages?: ImGroupMessageRespVO[] // 群置顶消息列表(后端关联回填,仅当登录用户是群成员时非空)
|
||||||
joinStatus?: number // 当前登录用户在该群的成员状态(参见 CommonStatusEnum:0 在群 / 1 已退群);历史退群群仍返回,供展示离线消息的群名 / 头像
|
joinStatus?: number // 当前登录用户在该群的成员状态(参见 CommonStatusEnum:0 在群 / 1 已退群);历史退群群仍返回,供展示离线消息的群名 / 头像
|
||||||
|
groupRemark?: string // 当前登录用户对该群的备注
|
||||||
|
silent?: boolean // 当前登录用户是否免打扰
|
||||||
}
|
}
|
||||||
|
|
||||||
// 群消息置顶 / 取消置顶 Request VO
|
// 群消息置顶 / 取消置顶 Request VO
|
||||||
|
|
|
||||||
|
|
@ -119,7 +119,7 @@ import { ImConversationType, ImContentType, isGroupConversation } from '../../..
|
||||||
import { getConversationKey } from '../../../utils/conversation'
|
import { getConversationKey } from '../../../utils/conversation'
|
||||||
import { buildDefaultGroupName } from '../../../utils/group'
|
import { buildDefaultGroupName } from '../../../utils/group'
|
||||||
import { serializeMessage, type CardTarget } from '../../../utils/message'
|
import { serializeMessage, type CardTarget } from '../../../utils/message'
|
||||||
import { isGroupQuit } from '../../../utils/user'
|
import { getGroupDisplayName, isGroupQuit } from '../../../utils/user'
|
||||||
import type { Conversation, FriendLite } from '../../types'
|
import type { Conversation, FriendLite } from '../../types'
|
||||||
|
|
||||||
defineOptions({ name: 'ImRecommendCardDialog' })
|
defineOptions({ name: 'ImRecommendCardDialog' })
|
||||||
|
|
@ -283,7 +283,7 @@ async function handleCreateGroupAndSend() {
|
||||||
const newConversation: Conversation = {
|
const newConversation: Conversation = {
|
||||||
type: ImConversationType.GROUP,
|
type: ImConversationType.GROUP,
|
||||||
targetId: group.id,
|
targetId: group.id,
|
||||||
name: group.name || name,
|
name: getGroupDisplayName(group) || name,
|
||||||
avatar: group.avatar || '',
|
avatar: group.avatar || '',
|
||||||
unreadCount: 0,
|
unreadCount: 0,
|
||||||
lastContent: '',
|
lastContent: '',
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { useConversationStore } from '../store/conversationStore'
|
||||||
import { useMessageStore, type PulledMessage } from '../store/messageStore'
|
import { useMessageStore, type PulledMessage } from '../store/messageStore'
|
||||||
import { useImWebSocketStore } from '../store/websocketStore'
|
import { useImWebSocketStore } from '../store/websocketStore'
|
||||||
import { useFriendStore } from '../store/friendStore'
|
import { useFriendStore } from '../store/friendStore'
|
||||||
import { getFriendDisplayName } from '../../utils/user'
|
import { getFriendDisplayName, getGroupDisplayName } from '../../utils/user'
|
||||||
import { useGroupStore } from '../store/groupStore'
|
import { useGroupStore } from '../store/groupStore'
|
||||||
import { useGroupRequestStore } from '../store/groupRequestStore'
|
import { useGroupRequestStore } from '../store/groupRequestStore'
|
||||||
import {
|
import {
|
||||||
|
|
@ -149,7 +149,7 @@ export const useMessagePuller = () => {
|
||||||
return {
|
return {
|
||||||
type: ImConversationType.GROUP,
|
type: ImConversationType.GROUP,
|
||||||
targetId: message.groupId,
|
targetId: message.groupId,
|
||||||
name: group?.name || String(message.groupId),
|
name: group ? getGroupDisplayName(group) : String(message.groupId),
|
||||||
avatar: group?.avatar || '',
|
avatar: group?.avatar || '',
|
||||||
silent: group?.silent
|
silent: group?.silent
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -85,9 +85,9 @@
|
||||||
/>
|
/>
|
||||||
<div class="flex justify-end gap-2">
|
<div class="flex justify-end gap-2">
|
||||||
<el-button size="small" @click="displayNamePopoverVisible = false">取消</el-button>
|
<el-button size="small" @click="displayNamePopoverVisible = false">取消</el-button>
|
||||||
<el-button size="small" type="primary" @click="handleSaveDisplayName"
|
<el-button size="small" type="primary" @click="handleSaveDisplayName">
|
||||||
>保存</el-button
|
保存
|
||||||
>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</el-popover>
|
</el-popover>
|
||||||
|
|
@ -141,7 +141,7 @@ import { useMessage } from '@/hooks/web/useMessage'
|
||||||
import { useConversationStore } from '@/views/im/home/store/conversationStore'
|
import { useConversationStore } from '@/views/im/home/store/conversationStore'
|
||||||
import { useFriendStore } from '@/views/im/home/store/friendStore'
|
import { useFriendStore } from '@/views/im/home/store/friendStore'
|
||||||
import { useGroupStore } from '@/views/im/home/store/groupStore'
|
import { useGroupStore } from '@/views/im/home/store/groupStore'
|
||||||
import { getFriendDisplayName } from '@/views/im/utils/user'
|
import { getFriendDisplayName, getGroupDisplayName } from '@/views/im/utils/user'
|
||||||
import { ImConversationType } from '@/views/im/utils/constants'
|
import { ImConversationType } from '@/views/im/utils/constants'
|
||||||
import type { Conversation, Friend } from '../../../../types'
|
import type { Conversation, Friend } from '../../../../types'
|
||||||
|
|
||||||
|
|
@ -248,7 +248,7 @@ function handleGroupCreated(groupId: number) {
|
||||||
conversationStore.openConversation(
|
conversationStore.openConversation(
|
||||||
groupId,
|
groupId,
|
||||||
ImConversationType.GROUP,
|
ImConversationType.GROUP,
|
||||||
group.name,
|
getGroupDisplayName(group),
|
||||||
group.avatar || '',
|
group.avatar || '',
|
||||||
{ silent: !!group.silent }
|
{ silent: !!group.silent }
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -260,6 +260,7 @@ import { ImRtcCallMediaType, ImRtcCallStatus, ImConversationType } from '@/views
|
||||||
import { resolveCallEndReasonText } from '@/views/im/utils/message'
|
import { resolveCallEndReasonText } from '@/views/im/utils/message'
|
||||||
import { getClientConversationId } from '@/views/im/utils/db'
|
import { getClientConversationId } from '@/views/im/utils/db'
|
||||||
import { getCurrentUserId } from '@/utils/auth'
|
import { getCurrentUserId } from '@/utils/auth'
|
||||||
|
import { getGroupDisplayName } from '@/views/im/utils/user'
|
||||||
import { useRtcStore } from '../../../../store/rtcStore'
|
import { useRtcStore } from '../../../../store/rtcStore'
|
||||||
import { useMessageStore } from '../../../../store/messageStore'
|
import { useMessageStore } from '../../../../store/messageStore'
|
||||||
|
|
||||||
|
|
@ -405,10 +406,11 @@ const groupInfo = computed<
|
||||||
}
|
}
|
||||||
const group = groupStore.getGroup(conversation.targetId)
|
const group = groupStore.getGroup(conversation.targetId)
|
||||||
const selfMember = group?.members?.find((member) => member.userId === getCurrentUserId())
|
const selfMember = group?.members?.find((member) => member.userId === getCurrentUserId())
|
||||||
|
const showGroupName = group ? getGroupDisplayName(group) : conversation.name
|
||||||
return {
|
return {
|
||||||
id: conversation.targetId,
|
id: conversation.targetId,
|
||||||
name: group?.name || conversation.name,
|
name: group?.name || conversation.name,
|
||||||
showGroupName: group?.name || conversation.name,
|
showGroupName,
|
||||||
showImage: group?.avatar || conversation.avatar,
|
showImage: group?.avatar || conversation.avatar,
|
||||||
notice: group?.notice,
|
notice: group?.notice,
|
||||||
remarkNickName: selfMember?.displayUserName,
|
remarkNickName: selfMember?.displayUserName,
|
||||||
|
|
|
||||||
|
|
@ -154,7 +154,7 @@ import FacePicker from '../../input/FacePicker.vue'
|
||||||
import { useConversationStore } from '@/views/im/home/store/conversationStore'
|
import { useConversationStore } from '@/views/im/home/store/conversationStore'
|
||||||
import { useFriendStore } from '@/views/im/home/store/friendStore'
|
import { useFriendStore } from '@/views/im/home/store/friendStore'
|
||||||
import { useGroupStore } from '@/views/im/home/store/groupStore'
|
import { useGroupStore } from '@/views/im/home/store/groupStore'
|
||||||
import { isGroupQuit } from '@/views/im/utils/user'
|
import { getGroupDisplayName, isGroupQuit } from '@/views/im/utils/user'
|
||||||
import { useMessageSender } from '@/views/im/home/composables/useMessageSender'
|
import { useMessageSender } from '@/views/im/home/composables/useMessageSender'
|
||||||
import { useMessageMultiSelect } from '@/views/im/home/composables/useMessageMultiSelect'
|
import { useMessageMultiSelect } from '@/views/im/home/composables/useMessageMultiSelect'
|
||||||
import {
|
import {
|
||||||
|
|
@ -406,7 +406,7 @@ async function handleCreateGroupAndSend() {
|
||||||
const newConversation: Conversation = {
|
const newConversation: Conversation = {
|
||||||
type: ImConversationType.GROUP,
|
type: ImConversationType.GROUP,
|
||||||
targetId: group.id,
|
targetId: group.id,
|
||||||
name: group.name || name,
|
name: getGroupDisplayName(group) || name,
|
||||||
avatar: group.avatar || '',
|
avatar: group.avatar || '',
|
||||||
unreadCount: 0,
|
unreadCount: 0,
|
||||||
lastContent: '',
|
lastContent: '',
|
||||||
|
|
|
||||||
|
|
@ -104,6 +104,7 @@ import { useImUiStore } from '../../store/uiStore'
|
||||||
import { ImConversationType } from '../../../utils/constants'
|
import { ImConversationType } from '../../../utils/constants'
|
||||||
import { StorageKeys } from '../../../utils/db'
|
import { StorageKeys } from '../../../utils/db'
|
||||||
import { filterConversationsByKeyword, getConversationKey } from '../../../utils/conversation'
|
import { filterConversationsByKeyword, getConversationKey } from '../../../utils/conversation'
|
||||||
|
import { getGroupDisplayName } from '../../../utils/user'
|
||||||
import type { Conversation } from '../../types'
|
import type { Conversation } from '../../types'
|
||||||
import ResizableAside from '../../components/ResizableAside.vue'
|
import ResizableAside from '../../components/ResizableAside.vue'
|
||||||
import ConversationItem from './components/conversation/ConversationItem.vue'
|
import ConversationItem from './components/conversation/ConversationItem.vue'
|
||||||
|
|
@ -220,7 +221,7 @@ function handleGroupCreated(groupId: number) {
|
||||||
conversationStore.openConversation(
|
conversationStore.openConversation(
|
||||||
groupId,
|
groupId,
|
||||||
ImConversationType.GROUP,
|
ImConversationType.GROUP,
|
||||||
group.name,
|
getGroupDisplayName(group),
|
||||||
group.avatar || '',
|
group.avatar || '',
|
||||||
{ silent: !!group.silent }
|
{ silent: !!group.silent }
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -227,7 +227,7 @@ export const useGroupStore = defineStore('imGroupStore', {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const fresh = (list || []).map((group) => convertGroup(group))
|
const fresh = (list || []).map((group) => convertGroup(group))
|
||||||
// 合并而非全量替换:silent / groupRemark / 成员缓存这些字段不在 ImGroupRespVO 里,得从旧 group 保留
|
// 合并而非全量替换:成员缓存只在成员列表接口维护,群个人设置以群列表接口为准
|
||||||
const groupMap = new Map(this.groups.map((group) => [group.id, group]))
|
const groupMap = new Map(this.groups.map((group) => [group.id, group]))
|
||||||
this.groups = fresh.map((group) => {
|
this.groups = fresh.map((group) => {
|
||||||
const existing = groupMap.get(group.id)
|
const existing = groupMap.get(group.id)
|
||||||
|
|
@ -238,8 +238,6 @@ export const useGroupStore = defineStore('imGroupStore', {
|
||||||
...group,
|
...group,
|
||||||
members: existing.members,
|
members: existing.members,
|
||||||
memberCount: existing.memberCount ?? group.memberCount,
|
memberCount: existing.memberCount ?? group.memberCount,
|
||||||
silent: existing.silent ?? group.silent,
|
|
||||||
groupRemark: existing.groupRemark,
|
|
||||||
membersLoaded: existing.membersLoaded,
|
membersLoaded: existing.membersLoaded,
|
||||||
membersExpired: existing.membersExpired
|
membersExpired: existing.membersExpired
|
||||||
}
|
}
|
||||||
|
|
@ -254,6 +252,24 @@ export const useGroupStore = defineStore('imGroupStore', {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
this.saveGroupList()
|
this.saveGroupList()
|
||||||
|
this.preloadMembersForEmptyAvatarGroups()
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 预加载空群头像的成员列表,供 GroupAvatar 异步合成群头像 */
|
||||||
|
preloadMembersForEmptyAvatarGroups() {
|
||||||
|
for (const group of this.groups) {
|
||||||
|
if (
|
||||||
|
group.avatar ||
|
||||||
|
group.joinStatus === CommonStatusEnum.DISABLE ||
|
||||||
|
(group.membersLoaded && !group.membersExpired && group.members?.length)
|
||||||
|
) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const force = !!group.membersLoaded && !group.membersExpired && !group.members?.length
|
||||||
|
this.fetchGroupMemberList(group.id, force).catch((error) => {
|
||||||
|
console.warn('[IM groupStore] 预加载群头像成员失败', { groupId: group.id }, error)
|
||||||
|
})
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/** 失效全部群成员缓存 */
|
/** 失效全部群成员缓存 */
|
||||||
|
|
@ -892,7 +908,9 @@ function convertGroup(group: ImGroupRespVO): Group {
|
||||||
mutedAll: group.mutedAll,
|
mutedAll: group.mutedAll,
|
||||||
banned: group.banned,
|
banned: group.banned,
|
||||||
joinApproval: group.joinApproval,
|
joinApproval: group.joinApproval,
|
||||||
joinStatus: group.joinStatus
|
joinStatus: group.joinStatus,
|
||||||
|
groupRemark: group.groupRemark,
|
||||||
|
silent: group.silent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ import {
|
||||||
import { useConversationStore } from './conversationStore'
|
import { useConversationStore } from './conversationStore'
|
||||||
import { useMessageStore } from './messageStore'
|
import { useMessageStore } from './messageStore'
|
||||||
import { useFriendStore, type FriendNotificationPayload } from './friendStore'
|
import { useFriendStore, type FriendNotificationPayload } from './friendStore'
|
||||||
import { getFriendDisplayName } from '../../utils/user'
|
import { getFriendDisplayName, getGroupDisplayName } from '../../utils/user'
|
||||||
import { useGroupStore } from './groupStore'
|
import { useGroupStore } from './groupStore'
|
||||||
import { useGroupRequestStore } from './groupRequestStore'
|
import { useGroupRequestStore } from './groupRequestStore'
|
||||||
import {
|
import {
|
||||||
|
|
@ -732,7 +732,7 @@ export const useImWebSocketStore = defineStore('imWebSocketStore', {
|
||||||
{
|
{
|
||||||
type: ImConversationType.GROUP,
|
type: ImConversationType.GROUP,
|
||||||
targetId: websocketMessage.groupId,
|
targetId: websocketMessage.groupId,
|
||||||
name: group?.name || String(websocketMessage.groupId),
|
name: group ? getGroupDisplayName(group) : String(websocketMessage.groupId),
|
||||||
avatar: group?.avatar || '',
|
avatar: group?.avatar || '',
|
||||||
silent: group?.silent
|
silent: group?.silent
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue