From 27cd042df9aca0383a49f3514bc21958dd890912 Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期一, 29 六月 2026 15:42:23 +0800
Subject: [PATCH] 银川 1.联调产品维护页面 2.添加IM即时通讯模块
---
src/views/im/home/pages/conversation/components/input/mention-picker.vue | 207 +++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 207 insertions(+), 0 deletions(-)
diff --git a/src/views/im/home/pages/conversation/components/input/mention-picker.vue b/src/views/im/home/pages/conversation/components/input/mention-picker.vue
new file mode 100644
index 0000000..ba510a7
--- /dev/null
+++ b/src/views/im/home/pages/conversation/components/input/mention-picker.vue
@@ -0,0 +1,207 @@
+<script lang="ts" setup>
+import { computed, ref, useTemplateRef, watch } from 'vue'
+
+import { CommonStatusEnum } from '#/packages/constants/src'
+import { IconifyIcon as Icon } from '#/packages/icons/src'
+
+import { getCurrentUserId } from '#/views/im/utils/auth'
+import { IM_AT_ALL_NICKNAME, IM_AT_ALL_USER_ID } from '#/views/im/utils/constants'
+
+import { GroupMember, type GroupMemberLite } from '../../../../components/group'
+
+defineOptions({ name: 'ImMentionPicker' })
+
+const props = withDefaults(
+ defineProps<{
+ canAtAll?: boolean // 褰撳墠鐢ㄦ埛鏄惁鑳� @ 鍏ㄥ憳锛堢兢涓� / 绠$悊鍛橈級锛岀埗缁勪欢鎸夎鑹茬畻濂戒紶鍏�
+ members: GroupMemberLite[] // 褰撳墠缇ょ殑鎴愬憳鍒楄〃
+ // 娴眰浣嶇疆锛歺 妯潗鏍� + top / bottom 浜岄�変竴锛坆ottom 閿氬畾鏃� picker 涓嬫部璐� @ 涓婃柟锛�
+ position?: { bottom?: number; top?: number; x: number; }
+ searchText?: string // @ 鍚庤緭鍏ョ殑杩囨护鏂囨湰
+ visible: boolean // 鏄惁鏄剧ず
+ }>(),
+ {
+ searchText: '',
+ position: () => ({ x: 0, bottom: 0 })
+ }
+)
+
+const emit = defineEmits<{
+ select: [member: GroupMemberLite]
+ 'update:visible': [value: boolean]
+}>()
+
+const scrollRef = useTemplateRef<HTMLDivElement>('scrollRef')
+const activeIdx = ref(0)
+
+/** 褰撳墠鐧诲綍鐢ㄦ埛 id锛堟垚鍛樺垪琛ㄨ繃婊ゆ帀鑷繁锛� */
+const selfUserId = computed(() => getCurrentUserId())
+
+/**
+ * 铏氭嫙"鎵�鏈変汉"椤癸細缇や富 / 绠$悊鍛橈紙canAtAll=true锛�+ 鍏抽敭瀛楀懡涓�"鎵�鏈変汉"鍓嶇紑鏃跺瓨鍦�
+ *
+ * MessageInput 璧� token data-id 鏀堕泦 atUserIds锛屼笉渚濊禆鏂囨瀛楃涓诧紱
+ * 杩欓噷鐨� userId / 鏂囨閮戒粠 im/utils/constants 鍙栵紝閬垮厤鏁h惤
+ */
+const allItem = computed<GroupMemberLite | null>(() => {
+ if (!props.canAtAll) {
+ return null
+ }
+ if (!IM_AT_ALL_NICKNAME.startsWith(props.searchText)) {
+ return null
+ }
+ // @鎵�鏈変汉 鏄釜浼垚鍛橈紝nickname 缁� IM_AT_ALL_NICKNAME 璁╁ご鍍� :name 琛屼负瀵归綈鏅�氭垚鍛�
+ return {
+ userId: IM_AT_ALL_USER_ID,
+ showName: IM_AT_ALL_NICKNAME,
+ nickname: IM_AT_ALL_NICKNAME
+ }
+})
+
+/** 鐪熸垚鍛橈細杩囨护鑷繁 / 閫�缇� / 涓嶅尮閰嶅叧閿瓧锛涗笉鎴柇鏁伴噺锛屾诞灞� max-height + el-scrollbar 鎾戞粴鍔� */
+const memberItems = computed<GroupMemberLite[]>(() =>
+ props.members.filter(
+ (member) =>
+ member.userId !== selfUserId.value &&
+ member.status !== CommonStatusEnum.DISABLE &&
+ !!member.showName &&
+ member.showName.startsWith(props.searchText)
+ )
+)
+
+/** 閿洏瀵艰埅涓� pickActive 璧扮殑鎵佸钩鍒楄〃锛宎llItem 鍦ㄥ墠銆乵emberItems 鍦ㄥ悗 */
+const showMembers = computed<GroupMemberLite[]>(() => {
+ return allItem.value ? [allItem.value, ...memberItems.value] : memberItems.value
+})
+
+/** 鍊欓�夊垪琛ㄥ彉鍖栵紙鐢ㄦ埛杈撳叆鍏抽敭璇嶅湪杩囨护锛夆啋 閲嶇疆楂樹寒鍒伴椤� + 婊氬洖椤� */
+watch(showMembers, (list) => {
+ activeIdx.value = list.length > 0 ? 0 : -1
+ scrollToTop()
+})
+
+/** 娴眰閲嶆柊鎵撳紑 鈫� 閲嶇疆楂樹寒 + 婊氬洖椤讹紙閬垮厤涓婃鐨勪腑闂寸姸鎬佹畫鐣欙級 */
+watch(
+ () => props.visible,
+ (v) => {
+ if (v) {
+ activeIdx.value = showMembers.value.length > 0 ? 0 : -1
+ scrollToTop()
+ }
+ }
+)
+
+/** el-scrollbar 娌℃毚闇� scrollTo锛岀洿鎺ユ嬁鍐呴儴 wrap 璋� scrollTop */
+function scrollToTop() {
+ const scrollWrap = scrollRef.value
+ if (scrollWrap) {
+ scrollWrap.scrollTop = 0
+ }
+}
+
+/** 閿洏涓婁笅瀵艰埅鏃舵妸楂樹寒椤规粴鍒板彲瑙嗗尯锛氳秴鍑哄簳杈逛笅鎺ㄣ�佽秴鍑洪《杈逛笂鎷夛紝鍚﹀垯涓嶅姩 */
+function scrollToActive() {
+ const scrollWrap = scrollRef.value
+ if (!scrollWrap) {
+ return
+ }
+ const itemHeight = 40
+ const activeOffsetTop = activeIdx.value * itemHeight
+ if (activeOffsetTop + itemHeight > scrollWrap.scrollTop + scrollWrap.clientHeight) {
+ scrollWrap.scrollTop = activeOffsetTop + itemHeight - scrollWrap.clientHeight
+ } else if (activeOffsetTop < scrollWrap.scrollTop) {
+ scrollWrap.scrollTop = activeOffsetTop
+ }
+}
+
+/** 閫変腑涓�椤癸細emit 缁� MessageInput 钀� token锛屽悓鏃跺叧鎺夋诞灞� */
+function handleSelect(member: GroupMemberLite) {
+ emit('select', member)
+ emit('update:visible', false)
+}
+
+// 鏆撮湶缁欑埗缁勪欢鐨勯敭鐩樺鑸柟娉�
+defineExpose({
+ moveUp() {
+ if (activeIdx.value > 0) {
+ activeIdx.value--
+ scrollToActive()
+ }
+ },
+ moveDown() {
+ if (activeIdx.value < showMembers.value.length - 1) {
+ activeIdx.value++
+ scrollToActive()
+ }
+ },
+ pickActive() {
+ const member = showMembers.value[activeIdx.value]
+ if (activeIdx.value >= 0 && member) {
+ handleSelect(member)
+ }
+ },
+ hasCandidates: () => showMembers.value.length > 0
+})
+</script>
+
+<template>
+ <!--
+ @ 鎴愬憳閫夋嫨娴眰锛堝榻愬井淇� PC锛氭墍鏈変汉鍦ㄩ《 + 缇ゆ垚鍛樺垎缁� + 搴曢儴涓夎鎸囬拡锛�
+ - 鐖剁粍浠堕�氳繃 v-model:visible 鎺у埗鏄鹃殣锛宻earchText 杩囨护
+ - 鐖剁粍浠堕�氳繃 ref 璋� moveUp / moveDown / pickActive 瀹炵幇閿洏涓婁笅 + Enter 閫変腑
+ - @select 鍙戝皠琚�変腑鐨勬垚鍛�
+ -->
+ <div
+ v-show="visible && showMembers.length > 0"
+ class="message-input__mention-picker !fixed z-100 w-50 rounded-md bg-[var(--ant-color-bg-container)] shadow-[0_4px_16px_rgba(0,0,0,0.12)]"
+ :style="{
+ left: `${position.x }px`,
+ top: position.top != null ? `${position.top }px` : 'auto',
+ bottom: position.bottom != null ? `${position.bottom }px` : 'auto'
+ }"
+ >
+ <div ref="scrollRef" style="max-height: 300px; overflow-y: auto">
+ <!-- 鎵�鏈変汉锛氳櫄鎷熼」锛屼粎缇や富鍙�夛紱钃濊壊鏂瑰潡 + 缇や綋鍥炬爣锛岃窡涓嬮潰鐨勬垚鍛樺ご鍍忓尯鍒� -->
+ <div
+ v-if="allItem"
+ class="flex items-center gap-2.5 px-[5px] h-10 cursor-pointer transition-colors hover:bg-[var(--ant-color-fill)]"
+ :class="{ 'bg-[#e1eaf7] dark:bg-[var(--ant-color-primary-bg)]': activeIdx === 0 }"
+ @click.stop="handleSelect(allItem)"
+ >
+ <div
+ class="flex items-center justify-center w-[30px] h-[30px] rounded text-white bg-[var(--ant-color-primary)] flex-shrink-0"
+ >
+ <Icon icon="ep:user-filled" :size="18" />
+ </div>
+ <span class="overflow-hidden text-sm truncate text-[var(--ant-color-text)]">
+ {{ allItem.showName }}
+ </span>
+ </div>
+
+ <!-- 缇ゆ垚鍛� section header锛氬彧鏈�"鎵�鏈変汉 + 鐪熸垚鍛�"涓よ竟閮藉湪鏃舵墠鍑虹幇锛岄伩鍏嶅崟涓�鏉ユ簮鏃舵樉寰楀浣� -->
+ <div
+ v-if="allItem && memberItems.length > 0"
+ class="px-2 pt-2 pb-1 text-12px text-[var(--ant-color-text-secondary)]"
+ >
+ 缇ゆ垚鍛�
+ </div>
+
+ <!-- 鐪熸垚鍛樿 -->
+ <GroupMember
+ v-for="(member, idx) in memberItems"
+ :key="member.userId"
+ :member="member"
+ :height="40"
+ :active="activeIdx === (allItem ? idx + 1 : idx)"
+ :clickable="false"
+ @click.stop="handleSelect(member)"
+ />
+ </div>
+
+ <!-- 涓夎鎸囬拡锛歱icker 鍦� @ 涓嬫柟锛坧osition.top 閿氬畾锛夆啋 绠ご鏈濅笂璐撮《锛沺icker 鍦� @ 涓婃柟锛坧osition.bottom 閿氬畾锛夆啋 绠ご鏈濅笅璐村簳 -->
+ <div
+ class="absolute left-4 w-3 h-3 rotate-45 bg-[var(--ant-color-bg-container)]"
+ :class="position.top != null ? '-top-1.5' : '-bottom-1.5'"
+ ></div>
+ </div>
+</template>
--
Gitblit v1.9.3