| ¶Ô±ÈÐÂÎļþ |
| | |
| | | // ==================================================================== |
| | | // IM ç¨æ·å±ç¤º utility |
| | | // ==================================================================== |
| | | // èè´£ï¼ç»ä¸åç"æä¸ªç¨æ·å¨ UI ä¸åºè¯¥å¦ä½å±ç¤º"ï¼å
å«ï¼ |
| | | // 1. æ¾ç¤ºåï¼getFriendDisplayName / getMemberDisplayName / getGroupDisplayName / getSenderDisplayName çï¼ |
| | | // 2. ä¸ä¸ææç¥åï¼tryGetSenderDisplayName / getSenderRealNicknameï¼ï¼æ¸²ææ¶æ conversation ä¸ä¸æå®æ¶æ¥ friendStore / groupStore / userStoreï¼è®©å¤æ³¨ / 群æµç§° / ç宿µç§°åæ´åææå岿¶æ¯ç«å³ååºå¼å·æ° |
| | | // 3. æ§å«å¾æ / é¢è²ï¼getGenderIcon / getGenderColorï¼ï¼ç·èã女ç²ï¼æªç¥ä¸å±ç¤ºï¼ææ UserInfo å¡ç / å表è¡å
±ç¨ |
| | | // |
| | | // å½å约å®ï¼æ¾ç¤ºåç¸å
³å½æ°ä¸å¾ä½¿ç¨ displayNameï¼ä¸ friend.displayName / member.displayUserName åæ®µå¯¹é½ |
| | | // ==================================================================== |
| | | |
| | | import type { Conversation, Friend, Group, User } from '../home/types' |
| | | |
| | | import { CommonStatusEnum } from '#/packages/constants/src' |
| | | import { useUserStore } from '#/packages/stores/src' |
| | | |
| | | import { getCurrentUserId } from '#/views/im/utils/auth' |
| | | |
| | | import { useConversationStore } from '../home/store/conversationStore' |
| | | import { useFriendStore } from '../home/store/friendStore' |
| | | import { useGroupStore } from '../home/store/groupStore' |
| | | import { useImUiStore } from '../home/store/uiStore' |
| | | import { |
| | | IM_AT_ALL_NICKNAME, |
| | | IM_AT_ALL_USER_ID, |
| | | ImConversationType, |
| | | ImFriendAddSource |
| | | } from './constants' |
| | | import { type MentionCandidate } from './message' |
| | | |
| | | const SystemUserSexEnum = { |
| | | FEMALE: 2, |
| | | MALE: 1, |
| | | UNKNOWN: 0 |
| | | } as const |
| | | |
| | | // åéç¼ºå¤±åºæ¯ç稳å®ç©ºæ°ç»ï¼è®© textMentions computed å¨é TEXT / é群è / æ @ æ¶è¿ååä¸å¼ç¨ï¼ |
| | | // MessageBubble ç textSegments æä¸ä¼è·çæ è°éç® |
| | | const EMPTY_MENTIONS: MentionCandidate[] = [] |
| | | |
| | | /** |
| | | * æ¯å¦åå²é群群ï¼joinStatus 为 DISABLEï¼å·²é群 / 被移é¤ï¼ |
| | | * |
| | | * /im/group/list ä¼å¸¦ååå²é群群ï¼ä¾å±ç¤ºå岿¶æ¯ç群å / 头åï¼ï¼è¿ç±»ç¾¤åºç¦æ¢åéãéè群æä½å
¥å£ãä¸å¯è¢«è½¬å / æ¨èéä¸ |
| | | */ |
| | | export function isGroupQuit(group?: Group | null): boolean { |
| | | return group?.joinStatus === CommonStatusEnum.DISABLE |
| | | } |
| | | |
| | | /** |
| | | * ç§èå¥½åæ¾ç¤ºåï¼å¤æ³¨ > ç宿µç§° |
| | | * |
| | | * displayName æ¯ãæå¯¹è¿ä¸ªäººçç§äººç§°å¼ãå±äºæçæ°æ®ï¼å 好åï¼DISABLEï¼ä¹ä¿çï¼å äºåå 忥æ¶å¤æ³¨èªç¶å»¶ç»ï¼å岿¶æ¯éä»ä»¥å¤æ³¨è¾¨è¯ |
| | | */ |
| | | export function getFriendDisplayName(friend: Pick<Friend, 'displayName' | 'nickname'>): string { |
| | | return friend.displayName || friend.nickname |
| | | } |
| | | |
| | | /** |
| | | * 群æåæ¾ç¤ºåï¼å¥½å夿³¨ > ç¨æ·ç¾¤å¤æ³¨ï¼displayUserNameï¼ > ç宿µç§° |
| | | * |
| | | * WeChat ä¼å
级ï¼å¥½å夿³¨æ¯"æ"对该æåçç§äººç§°å¼ï¼æé«ä¼å
ï¼å
¶æ¬¡æ¯ ta å¨ç¾¤å
èªå®ä¹æµç§°ï¼æåç宿µç§°å
åºï¼è°ç¨æ¹æ¿å° friend æä¼ å
¥ï¼æ²¡æ¿å°ï¼éç人ï¼å°±åªç¨ member åæ®µé级 |
| | | */ |
| | | export function getMemberDisplayName( |
| | | member: { displayUserName?: string; nickname: string }, |
| | | friend?: null | Pick<Friend, 'displayName'> |
| | | ): string { |
| | | return friend?.displayName || member.displayUserName || member.nickname |
| | | } |
| | | |
| | | /** 群æ¾ç¤ºåï¼å½åç¨æ·å¯¹è¯¥ç¾¤ç夿³¨ï¼groupRemarkï¼ > 群åï¼nameï¼ */ |
| | | export function getGroupDisplayName(group: Pick<Group, 'groupRemark' | 'name'>): string { |
| | | return group.groupRemark || group.name |
| | | } |
| | | |
| | | /** |
| | | * æ¶æ¯åéè
æ¾ç¤ºåï¼ä¸¥æ ¼çï¼ï¼ç®ä¸åºçåè¿å undefined |
| | | * |
| | | * ç»éè¦"æ¯å¦çå"ä¿¡å·çè°ç¨æ¹ç¨ââæ¯å¦ conversationStore å³å®è¦ä¸è¦å lastSenderDisplayName å¿«ç
§ãè¦ä¸è¦è§¦å fetchGroupMemberList å
åºææå |
| | | * |
| | | * GROUP åºæ¯ä¸ member 已就ä½ä¼å
displayUserName / 好å夿³¨ / ç宿µç§°ï¼member 缺失æ¶åºåä¸¤ç§ senderï¼ |
| | | * - self â ç´æ¥æ¿ userStore.nickname å
åºï¼æ¬ç«¯æ°¸è¿ç¥éèªå·±çæµç§°ï¼ä¸éè¦ fetchï¼åæ¶é¿å
self é群å GROUP_MEMBER_QUIT éç¥è§¦åå
åºææå â 403ï¼ |
| | | * - å
¶ä»äºº â è¿å undefinedï¼è®© deriveLastSenderDisplayName èµ°å
åºææå |
| | | */ |
| | | export function tryGetSenderDisplayName( |
| | | senderId: number, |
| | | conversationType: number, |
| | | conversationTargetId: number |
| | | ): string | undefined { |
| | | if (conversationType === ImConversationType.GROUP) { |
| | | const group = useGroupStore().getGroup(conversationTargetId) |
| | | const member = group?.members?.find((m) => m.userId === senderId) |
| | | if (member) { |
| | | const friend = useFriendStore().getFriend(senderId) |
| | | return getMemberDisplayName(member, friend) |
| | | } |
| | | if (senderId === getCurrentUserId()) { |
| | | return useUserStore().userInfo?.nickname || undefined |
| | | } |
| | | return undefined |
| | | } |
| | | |
| | | // PRIVATE / æªç¥ä¼è¯ç±»åï¼self èµ° userStoreï¼å¯¹æ¹èµ° friend |
| | | if (senderId === getCurrentUserId()) { |
| | | return useUserStore().userInfo?.nickname || undefined |
| | | } |
| | | if (conversationType === ImConversationType.PRIVATE) { |
| | | const friend = useFriendStore().getFriend(senderId) |
| | | return friend ? getFriendDisplayName(friend) : undefined |
| | | } |
| | | return undefined |
| | | } |
| | | |
| | | /** |
| | | * æ¶æ¯åéè
æ¾ç¤ºåï¼æ¸²ææ¶å®æ¶ç®ï¼æ WeChat ä¼å
级 |
| | | * |
| | | * - èªå·±ï¼userStore.nickname |
| | | * - ç§è对æ¹ï¼å¥½å夿³¨ > ç宿µç§° |
| | | * - 群è对æ¹ï¼å¥½å夿³¨ > ç¾¤å¤æ³¨ï¼displayUserNameï¼ > ç宿µç§° |
| | | * - æ¥ä¸å°ï¼fallbackName || (self èµ° userStore.nickname) || String(senderId) |
| | | */ |
| | | export function getSenderDisplayName( |
| | | senderId: number, |
| | | conversationType: number, |
| | | conversationTargetId: number, |
| | | fallbackName?: string |
| | | ): string { |
| | | const real = tryGetSenderDisplayName(senderId, conversationType, conversationTargetId) |
| | | if (real) { |
| | | return real |
| | | } |
| | | if (fallbackName) { |
| | | return fallbackName |
| | | } |
| | | // self å¨ GROUP members 没å è½½æ¶ï¼è³å°ç¨ç宿µç§°å
åºæ¸²æï¼æ¯ String(senderId) å好ï¼ï¼å
åºææåç± conversationStore 触åï¼åæ¥å try çæ¬è½å½ä¸çåèªç¶å·æ° |
| | | const userStore = useUserStore() |
| | | if (senderId === getCurrentUserId()) { |
| | | return userStore.userInfo?.nickname || String(senderId) |
| | | } |
| | | return String(senderId) |
| | | } |
| | | |
| | | /** |
| | | * æ¶æ¯åéè
ãç宿µç§°ãï¼æ°¸è¿æ¯ nicknameï¼ä¸æºå¤æ³¨ |
| | | * |
| | | * - èªå·±ï¼userStore.nickname |
| | | * - ç§è对æ¹ï¼friend.nickname |
| | | * - 群è对æ¹ï¼member.nickname |
| | | * |
| | | * ä¸ç» UserAvatar ç :name ç¨ââè²å¡é¦åæ¯ / alt ææ¬è¦ä¿è¯åä¸ä¸ªäººå¨ææçé¢ä¸è´ï¼ä¸è·å¤æ³¨å |
| | | */ |
| | | export function getSenderRealNickname( |
| | | senderId: number, |
| | | conversationType: number, |
| | | conversationTargetId: number |
| | | ): string { |
| | | const userStore = useUserStore() |
| | | const selfUserId = getCurrentUserId() |
| | | |
| | | // 群èå
èµ° member.nicknameï¼self 乿¯ memberï¼ï¼å¼å¸¸æ¶åèµ° self / senderId å
åº |
| | | if (conversationType === ImConversationType.GROUP) { |
| | | const group = useGroupStore().getGroup(conversationTargetId) |
| | | const member = group?.members?.find((m) => m.userId === senderId) |
| | | if (member?.nickname) { |
| | | return member.nickname |
| | | } |
| | | if (senderId === selfUserId) { |
| | | return userStore.userInfo?.nickname || String(senderId) |
| | | } |
| | | return String(senderId) |
| | | } |
| | | |
| | | if (conversationType === ImConversationType.PRIVATE) { |
| | | if (senderId === selfUserId) { |
| | | return userStore.userInfo?.nickname || String(senderId) |
| | | } |
| | | const friend = useFriendStore().getFriend(senderId) |
| | | return friend?.nickname || String(senderId) |
| | | } |
| | | |
| | | if (senderId === selfUserId) { |
| | | return userStore.userInfo?.nickname || String(senderId) |
| | | } |
| | | return String(senderId) |
| | | } |
| | | |
| | | /** |
| | | * æ¶æ¯åéè
头åï¼æ conversation ä¸ä¸æå®æ¶æ¥ group.members / friend / userStore |
| | | * |
| | | * - èªå·±ï¼userStore.avatar |
| | | * - ç§è对æ¹ï¼friend.avatar |
| | | * - 群è对æ¹ï¼å
member.avatarï¼ç¼ºåé级 friend.avatar |
| | | * - æ¥ä¸å°ï¼è¿å空串ï¼è°ç¨æ¹èµ° UserAvatar è²å¡å
åº |
| | | */ |
| | | export function getSenderAvatar( |
| | | senderId: number, |
| | | conversationType: number, |
| | | conversationTargetId: number |
| | | ): string { |
| | | const userStore = useUserStore() |
| | | if (senderId === getCurrentUserId()) { |
| | | return userStore.userInfo?.avatar || '' |
| | | } |
| | | if (conversationType === ImConversationType.GROUP) { |
| | | const group = useGroupStore().getGroup(conversationTargetId) |
| | | const member = group?.members?.find((m) => m.userId === senderId) |
| | | if (member?.avatar) { |
| | | return member.avatar |
| | | } |
| | | } |
| | | return useFriendStore().getFriend(senderId)?.avatar || '' |
| | | } |
| | | |
| | | /** |
| | | * ç¾¤æ¶æ¯ @ mention åéï¼æ atUserIds åæ¥ç¾¤æåï¼name æ¶éææå¯è½åé¢éï¼å«å¥½å夿³¨ / 群èªå®ä¹æµç§°ï¼ï¼displayName ç»ä¸æåç宿µç§° |
| | | * |
| | | * ååé¢éè¢«å¤ userId å
±äº«æ¶æ è®° ambiguousï¼ç± parser æ´æ®µåææ®éææ¬ï¼ |
| | | * ç´æ¥åé¤ä¼è®©çåç¼åéï¼å¦ã@å¼ ãï¼æ¢åã@å¼ ä¸ãçåç¼ï¼éç»å°å¯ä¸çãå¼ ãç¨æ· |
| | | */ |
| | | export function getMentionCandidates( |
| | | atUserIds: number[] | undefined, |
| | | conversation: null | Pick<Conversation, 'targetId' | 'type'> | undefined |
| | | ): MentionCandidate[] { |
| | | if (!atUserIds || atUserIds.length === 0) { |
| | | return EMPTY_MENTIONS |
| | | } |
| | | if (!conversation || conversation.type !== ImConversationType.GROUP) { |
| | | return EMPTY_MENTIONS |
| | | } |
| | | // 群æåé¢å»º Mapï¼é¿å
æ¯ä¸ª atUserId èµ°ä¸æ¬¡ array findï¼@å
¨ä½æååºæ¯ä¸æåæ° Ã atUserIds æ¯ NÂ²ï¼ |
| | | const members = useGroupStore().getGroup(conversation.targetId)?.members || [] |
| | | const memberById = new Map(members.map((m) => [m.userId, m])) |
| | | const friendStore = useFriendStore() |
| | | const candidates: MentionCandidate[] = [] |
| | | const seen = new Set<string>() |
| | | for (const userId of atUserIds) { |
| | | // @å
¨ä½æåæ¯èæä¼ªæåï¼userId = -1 å¨ group.members 鿥ä¸å°ï¼æ³¨å
¥åé¢éãææäººãåé |
| | | if (userId === IM_AT_ALL_USER_ID) { |
| | | const key = `${IM_AT_ALL_USER_ID}#${IM_AT_ALL_NICKNAME}` |
| | | if (!seen.has(key)) { |
| | | seen.add(key) |
| | | candidates.push({ |
| | | userId: IM_AT_ALL_USER_ID, |
| | | name: IM_AT_ALL_NICKNAME, |
| | | displayName: IM_AT_ALL_NICKNAME |
| | | }) |
| | | } |
| | | continue |
| | | } |
| | | const member = memberById.get(userId) |
| | | const friend = friendStore.getFriend(userId) |
| | | const nickname = (member?.nickname || friend?.nickname || '').trim() |
| | | if (!nickname) { |
| | | continue |
| | | } |
| | | for (const literal of [nickname, friend?.displayName, member?.displayUserName]) { |
| | | const trimmed = (literal || '').trim() |
| | | if (!trimmed) { |
| | | continue |
| | | } |
| | | const key = `${userId}#${trimmed}` |
| | | if (seen.has(key)) { |
| | | continue |
| | | } |
| | | seen.add(key) |
| | | candidates.push({ userId, name: trimmed, displayName: nickname }) |
| | | } |
| | | } |
| | | const nameCount = new Map<string, number>() |
| | | for (const candidate of candidates) { |
| | | nameCount.set(candidate.name, (nameCount.get(candidate.name) ?? 0) + 1) |
| | | } |
| | | for (const candidate of candidates) { |
| | | if ((nameCount.get(candidate.name) ?? 0) > 1) { |
| | | candidate.ambiguous = true |
| | | } |
| | | } |
| | | return candidates |
| | | } |
| | | |
| | | /** |
| | | * ææ¬ / tip é mention 段ç¹å»çç»ä¸å
¥å£ï¼æ´¾ç User å¼¹ UserInfoCard |
| | | * |
| | | * 头åèµ° getSenderAvatarï¼æµç§°åç宿µç§°ï¼friend / member 齿¥ä¸å°æ¶ç¨ fallbackName å
åºï¼å¦ç°æ¡ tip 段çåé¢éææ¬ï¼ï¼ |
| | | * addSource 群è带 GROUP + 群åï¼ç¨äºãå 为好åãçæ¥æº + 夿³¨å¼¹ææ¡ï¼ï¼ç§èé级 SEARCHï¼ |
| | | * @å
¨ä½æå æ¯å¹¿æ mention 没æå
·ä½ç¨æ·å®ä½ï¼å¯¹é½å¾®ä¿¡ PC ä¸å¼¹å¡ç |
| | | */ |
| | | export function openMentionUserInfoCardAtEvent( |
| | | userId: number, |
| | | event: MouseEvent, |
| | | fallbackName?: string |
| | | ): void { |
| | | if (userId === IM_AT_ALL_USER_ID) { |
| | | return |
| | | } |
| | | const conversation = useConversationStore().activeConversation |
| | | const isGroup = conversation?.type === ImConversationType.GROUP |
| | | const group = isGroup && conversation ? useGroupStore().getGroup(conversation.targetId) : undefined |
| | | const member = group?.members?.find((m) => m.userId === userId) |
| | | const friend = useFriendStore().getFriend(userId) |
| | | const user: User = { |
| | | id: userId, |
| | | nickname: friend?.nickname || member?.nickname || fallbackName || String(userId), |
| | | avatar: getSenderAvatar(userId, conversation?.type ?? 0, conversation?.targetId ?? 0) |
| | | } |
| | | useImUiStore().openUserInfoCardAtEvent( |
| | | user, |
| | | event, |
| | | isGroup ? ImFriendAddSource.GROUP : ImFriendAddSource.SEARCH, |
| | | isGroup ? group?.name || '' : '' |
| | | ) |
| | | } |
| | | |
| | | /** æ§å«å¾æ ï¼UNKNOWN / null / undefined ä¸å¾ä¸å±ç¤ºï¼å¯¹é½å¾®ä¿¡çç½ */ |
| | | export function getGenderIcon(sex?: number): string { |
| | | if (sex === SystemUserSexEnum.MALE) { |
| | | return 'mdi:human-male' |
| | | } |
| | | if (sex === SystemUserSexEnum.FEMALE) { |
| | | return 'mdi:human-female' |
| | | } |
| | | return '' |
| | | } |
| | | |
| | | /** æ§å«å¾æ 主é¢è²ï¼ç·èãå¥³ç² */ |
| | | export function getGenderColor(sex?: number): string { |
| | | if (sex === SystemUserSexEnum.MALE) { |
| | | return '#5b97f5' |
| | | } |
| | | if (sex === SystemUserSexEnum.FEMALE) { |
| | | return '#f56c92' |
| | | } |
| | | return '' |
| | | } |
| | | |
| | | /** 头åè²å¡åºè²è°è²æ¿ï¼åèå¾®ä¿¡ï¼ */ |
| | | const AVATAR_BG_COLORS = ['#07C160', '#1A95FF', '#FA9D3B', '#9163E0', '#F76760', '#1ABC9C'] |
| | | |
| | | /** 头åè²å¡æåï¼ä¸æåé¦åãè±æåå 2 忝大åãå
¶ä»åé¦å大åã空åè¿å空串 */ |
| | | export function getAvatarText(name?: string): string { |
| | | const trimmed = name?.trim() |
| | | if (!trimmed) { |
| | | return '' |
| | | } |
| | | const first = trimmed.charAt(0) |
| | | const code = first.codePointAt(0) ?? 0 |
| | | if (code >= 0x4E_00 && code <= 0x9F_A5) { |
| | | return first |
| | | } |
| | | const letters = trimmed.match(/[A-Za-z]/g) |
| | | if (!letters || letters.length === 0) { |
| | | return first.toUpperCase() |
| | | } |
| | | return letters.slice(0, 2).join('').toUpperCase() |
| | | } |
| | | |
| | | /** 头åè²å¡åºè²ï¼æ name charCode ä¹ååè°è²æ¿è²ï¼ç©ºåèµ°é»è®¤ç° */ |
| | | export function getAvatarBgColor(name?: string): string { |
| | | if (!name) { |
| | | return '#909399' |
| | | } |
| | | let hash = 0 |
| | | for (let i = 0; i < name.length; i++) { |
| | | hash += name.codePointAt(i) ?? 0 |
| | | } |
| | | return AVATAR_BG_COLORS[hash % AVATAR_BG_COLORS.length] || '#909399' |
| | | } |