bug修复
This commit is contained in:
58
src/api/modules/search.ts
Normal file
58
src/api/modules/search.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import request from '../request'
|
||||
import type { User } from '@/types/api'
|
||||
|
||||
/**
|
||||
* 搜索相关API
|
||||
*/
|
||||
|
||||
// 联系人搜索结果
|
||||
export interface ContactSearchResult {
|
||||
user: User
|
||||
remark_name: string
|
||||
room_id: string
|
||||
}
|
||||
|
||||
// 群聊搜索结果
|
||||
export interface GroupSearchResult {
|
||||
room_id: string
|
||||
room_name: string
|
||||
room_avatar: string
|
||||
owner_id: string
|
||||
member_count: number
|
||||
}
|
||||
|
||||
// 消息搜索结果
|
||||
export interface MessageSearchResult {
|
||||
id: number
|
||||
room_id: string
|
||||
room_name: string
|
||||
content: string
|
||||
message_type: number
|
||||
created_at: string
|
||||
sender: User
|
||||
is_group_chat: boolean
|
||||
match_content: string
|
||||
}
|
||||
|
||||
// 聚合搜索结果
|
||||
export interface GlobalSearchResult {
|
||||
contacts: ContactSearchResult[]
|
||||
groups: GroupSearchResult[]
|
||||
messages: MessageSearchResult[]
|
||||
}
|
||||
|
||||
// 搜索类型
|
||||
export type SearchType = 'all' | 'contacts' | 'groups' | 'messages'
|
||||
|
||||
/**
|
||||
* 聚合搜索
|
||||
* @param keyword 搜索关键词
|
||||
* @param type 搜索类型:all(默认)、contacts、groups、messages
|
||||
* @param limit 每类结果的最大数量,默认20
|
||||
*/
|
||||
export function globalSearch(keyword: string, type: SearchType = 'all', limit: number = 20) {
|
||||
return request.get<GlobalSearchResult>('/search', {
|
||||
params: { keyword, type, limit }
|
||||
})
|
||||
}
|
||||
|
||||
220
src/components/common/SearchResults.vue
Normal file
220
src/components/common/SearchResults.vue
Normal file
@@ -0,0 +1,220 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="show"
|
||||
class="absolute top-full left-0 right-0 mt-1 bg-[#1e2329] border border-gray-700 rounded-xl shadow-2xl z-50 max-h-[70vh] overflow-hidden flex flex-col"
|
||||
>
|
||||
<!-- 加载状态 -->
|
||||
<div v-if="loading" class="flex items-center justify-center py-8">
|
||||
<i class="fas fa-circle-notch fa-spin text-indigo-400 text-xl"></i>
|
||||
<span class="ml-2 text-gray-400 text-sm">搜索中...</span>
|
||||
</div>
|
||||
|
||||
<!-- 空结果 -->
|
||||
<div v-else-if="isEmpty" class="flex flex-col items-center justify-center py-8 text-gray-500">
|
||||
<i class="fas fa-search text-3xl mb-2 opacity-50"></i>
|
||||
<span class="text-sm">未找到相关结果</span>
|
||||
<span class="text-xs mt-1 opacity-70">换个关键词试试</span>
|
||||
</div>
|
||||
|
||||
<!-- 搜索结果 -->
|
||||
<div v-else class="overflow-y-auto custom-scrollbar flex-1">
|
||||
<!-- 联系人 -->
|
||||
<div v-if="safeResult.contacts.length > 0" class="border-b border-gray-700/50">
|
||||
<div class="px-4 py-2 text-xs font-bold text-gray-500 uppercase tracking-wider bg-black/20 flex items-center gap-2">
|
||||
<i class="fas fa-user-friends"></i>
|
||||
<span>联系人</span>
|
||||
<span class="ml-auto text-gray-600">{{ safeResult.contacts.length }}</span>
|
||||
</div>
|
||||
<div class="py-1">
|
||||
<div
|
||||
v-for="contact in safeResult.contacts"
|
||||
:key="contact.user?.id || Math.random()"
|
||||
class="flex items-center gap-3 px-4 py-2.5 hover:bg-white/5 cursor-pointer transition-colors"
|
||||
@click="handleContactClick(contact)"
|
||||
>
|
||||
<Avatar
|
||||
:name="contact.remark_name || contact.user?.name"
|
||||
:avatar="contact.user?.avatar"
|
||||
size="sm"
|
||||
rounded="full"
|
||||
/>
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="text-sm text-white truncate" v-html="highlightKeyword(contact.remark_name || contact.user?.name || '')"></div>
|
||||
<div class="text-xs text-gray-500 truncate" v-html="highlightKeyword(contact.user?.email || contact.user?.phone || '')"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 群聊 -->
|
||||
<div v-if="safeResult.groups.length > 0" class="border-b border-gray-700/50">
|
||||
<div class="px-4 py-2 text-xs font-bold text-gray-500 uppercase tracking-wider bg-black/20 flex items-center gap-2">
|
||||
<i class="fas fa-users"></i>
|
||||
<span>群聊</span>
|
||||
<span class="ml-auto text-gray-600">{{ safeResult.groups.length }}</span>
|
||||
</div>
|
||||
<div class="py-1">
|
||||
<div
|
||||
v-for="group in safeResult.groups"
|
||||
:key="group.room_id"
|
||||
class="flex items-center gap-3 px-4 py-2.5 hover:bg-white/5 cursor-pointer transition-colors"
|
||||
@click="handleGroupClick(group)"
|
||||
>
|
||||
<Avatar
|
||||
:name="group.room_name"
|
||||
:avatar="group.room_avatar"
|
||||
size="sm"
|
||||
rounded="lg"
|
||||
/>
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="text-sm text-white truncate" v-html="highlightKeyword(group.room_name || '')"></div>
|
||||
<div class="text-xs text-gray-500">{{ group.member_count || 0 }} 人</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 聊天记录 -->
|
||||
<div v-if="safeResult.messages.length > 0">
|
||||
<div class="px-4 py-2 text-xs font-bold text-gray-500 uppercase tracking-wider bg-black/20 flex items-center gap-2">
|
||||
<i class="fas fa-comment-dots"></i>
|
||||
<span>聊天记录</span>
|
||||
<span class="ml-auto text-gray-600">{{ safeResult.messages.length }}</span>
|
||||
</div>
|
||||
<div class="py-1">
|
||||
<div
|
||||
v-for="msg in safeResult.messages"
|
||||
:key="msg.id"
|
||||
class="flex items-start gap-3 px-4 py-2.5 hover:bg-white/5 cursor-pointer transition-colors"
|
||||
@click="handleMessageClick(msg)"
|
||||
>
|
||||
<Avatar
|
||||
:name="msg.sender?.name"
|
||||
:avatar="msg.sender?.avatar"
|
||||
size="sm"
|
||||
rounded="full"
|
||||
/>
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex items-center gap-2 mb-0.5">
|
||||
<span class="text-sm text-white truncate">{{ msg.sender?.name || '未知用户' }}</span>
|
||||
<span v-if="msg.is_group_chat" class="text-xs text-indigo-400 bg-indigo-500/20 px-1.5 rounded">
|
||||
{{ msg.room_name || '群聊' }}
|
||||
</span>
|
||||
<span class="text-xs text-gray-600 ml-auto shrink-0">{{ formatTime(msg.created_at) }}</span>
|
||||
</div>
|
||||
<div class="text-xs text-gray-400 truncate leading-relaxed" v-html="highlightKeyword(msg.match_content || '')"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 底部操作 -->
|
||||
<div v-if="!loading && !isEmpty" class="px-4 py-2 border-t border-gray-700/50 bg-black/20 text-center">
|
||||
<span class="text-xs text-gray-500">
|
||||
按 <kbd class="px-1.5 py-0.5 bg-gray-700 rounded text-gray-300 mx-0.5">ESC</kbd> 关闭
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import Avatar from '@/components/common/Avatar.vue'
|
||||
import type { GlobalSearchResult, ContactSearchResult, GroupSearchResult, MessageSearchResult } from '@/api/modules/search'
|
||||
|
||||
interface Props {
|
||||
show: boolean
|
||||
loading: boolean
|
||||
keyword: string
|
||||
result: GlobalSearchResult
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
show: false,
|
||||
loading: false,
|
||||
keyword: '',
|
||||
result: () => ({ contacts: [], groups: [], messages: [] })
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
'select-contact': [contact: ContactSearchResult]
|
||||
'select-group': [group: GroupSearchResult]
|
||||
'select-message': [message: MessageSearchResult]
|
||||
'close': []
|
||||
}>()
|
||||
|
||||
// 安全获取结果数据
|
||||
const safeResult = computed(() => ({
|
||||
contacts: props.result?.contacts ?? [],
|
||||
groups: props.result?.groups ?? [],
|
||||
messages: props.result?.messages ?? []
|
||||
}))
|
||||
|
||||
// 是否为空结果
|
||||
const isEmpty = computed(() => {
|
||||
return !props.loading &&
|
||||
safeResult.value.contacts.length === 0 &&
|
||||
safeResult.value.groups.length === 0 &&
|
||||
safeResult.value.messages.length === 0
|
||||
})
|
||||
|
||||
// 高亮关键词
|
||||
function highlightKeyword(text: string): string {
|
||||
if (!text || !props.keyword) return text || ''
|
||||
const regex = new RegExp(`(${escapeRegExp(props.keyword)})`, 'gi')
|
||||
return text.replace(regex, '<span class="text-indigo-400 font-medium">$1</span>')
|
||||
}
|
||||
|
||||
// 转义正则特殊字符
|
||||
function escapeRegExp(str: string): string {
|
||||
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
||||
}
|
||||
|
||||
// 格式化时间
|
||||
function formatTime(timeStr: string): string {
|
||||
if (!timeStr) return ''
|
||||
const date = new Date(timeStr)
|
||||
const now = new Date()
|
||||
const diff = now.getTime() - date.getTime()
|
||||
const days = Math.floor(diff / (1000 * 60 * 60 * 24))
|
||||
|
||||
if (days === 0) {
|
||||
return date.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' })
|
||||
} else if (days === 1) {
|
||||
return '昨天'
|
||||
} else if (days < 7) {
|
||||
return `${days}天前`
|
||||
} else {
|
||||
return date.toLocaleDateString('zh-CN', { month: 'short', day: 'numeric' })
|
||||
}
|
||||
}
|
||||
|
||||
// 点击联系人
|
||||
function handleContactClick(contact: ContactSearchResult) {
|
||||
emit('select-contact', contact)
|
||||
}
|
||||
|
||||
// 点击群聊
|
||||
function handleGroupClick(group: GroupSearchResult) {
|
||||
emit('select-group', group)
|
||||
}
|
||||
|
||||
// 点击消息
|
||||
function handleMessageClick(msg: MessageSearchResult) {
|
||||
emit('select-message', msg)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.custom-scrollbar::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
.custom-scrollbar::-webkit-scrollbar-thumb {
|
||||
@apply bg-gray-700 rounded-full;
|
||||
}
|
||||
.custom-scrollbar::-webkit-scrollbar-track {
|
||||
@apply bg-transparent;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -171,20 +171,23 @@ export function useWebRTC(
|
||||
const roomId = getSafeRoomId(currentReceiverUserId)
|
||||
if (!roomId) return
|
||||
|
||||
// 通话类型说明
|
||||
const callTypeText = call.type === 'video' ? '[视频通话]' : '[语音通话]'
|
||||
|
||||
// 根据原因生成系统消息内容
|
||||
let content = ''
|
||||
switch (reason) {
|
||||
case 'connected':
|
||||
content = `通话时长 ${formatDuration(call.duration)}`
|
||||
content = `${callTypeText} 通话时长 ${formatDuration(call.duration)}`
|
||||
break
|
||||
case 'cancelled':
|
||||
content = '已取消'
|
||||
content = `${callTypeText} 已取消`
|
||||
break
|
||||
case 'rejected':
|
||||
content = '对方未接听'
|
||||
content = `${callTypeText} 对方未接听`
|
||||
break
|
||||
case 'busy':
|
||||
content = '对方忙'
|
||||
content = `${callTypeText} 对方忙`
|
||||
break
|
||||
}
|
||||
|
||||
@@ -402,17 +405,15 @@ export function useWebRTC(
|
||||
|
||||
function endCall() {
|
||||
stopRingtone()
|
||||
// 主动挂断时发送通话结束系统消息
|
||||
// 只有发起方发送通话结束系统消息,避免重复
|
||||
if (isCaller.value) {
|
||||
if (call.status === 'outgoing') {
|
||||
sendSummaryMessage('cancelled') // 呼出状态主动取消
|
||||
} else if (call.status === 'connected') {
|
||||
sendSummaryMessage('connected') // 通话中主动挂断
|
||||
}
|
||||
} else if (call.status === 'connected') {
|
||||
// 被叫方在通话中挂断也发送消息
|
||||
sendSummaryMessage('connected')
|
||||
}
|
||||
// 被叫方不发送消息,由发起方统一发送
|
||||
sendSignal('hangup')
|
||||
closeCall()
|
||||
}
|
||||
|
||||
@@ -111,12 +111,26 @@
|
||||
</button>
|
||||
</div>
|
||||
<div class="relative group">
|
||||
<i class="fas fa-search absolute left-4 top-3 text-gray-500 group-focus-within:text-primary transition"></i>
|
||||
<i class="fas fa-search absolute left-4 top-3 text-gray-500 group-focus-within:text-primary transition z-10"></i>
|
||||
<input
|
||||
v-model="searchQuery"
|
||||
type="text"
|
||||
class="w-full bg-input rounded-2xl py-2.5 pl-10 pr-4 text-sm text-white focus:ring-2 ring-primary outline-none transition placeholder-gray-500 shadow-inner"
|
||||
placeholder="搜索会话或联系人..."
|
||||
@input="handleSearchInput"
|
||||
@focus="handleSearchFocus"
|
||||
@keydown.esc="closeSearchResults"
|
||||
/>
|
||||
<!-- 聚合搜索结果 -->
|
||||
<SearchResults
|
||||
:show="showSearchResults"
|
||||
:loading="searchLoading"
|
||||
:keyword="searchQuery"
|
||||
:result="searchResult"
|
||||
@select-contact="handleSearchContactSelect"
|
||||
@select-group="handleSearchGroupSelect"
|
||||
@select-message="handleSearchMessageSelect"
|
||||
@close="closeSearchResults"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -712,8 +726,11 @@ import GroupChatPanel from '@/components/chat/GroupChatPanel.vue'
|
||||
import GroupCallBanner from '@/components/chat/GroupCallBanner.vue'
|
||||
import UserInfoCard from '@/components/common/UserInfoCard.vue'
|
||||
import MomentPanel from '@/components/moment/MomentPanel.vue'
|
||||
import SearchResults from '@/components/common/SearchResults.vue'
|
||||
import * as groupApi from '@/api/modules/room'
|
||||
import * as systemApi from '@/api/modules/system'
|
||||
import * as searchApi from '@/api/modules/search'
|
||||
import type { GlobalSearchResult, ContactSearchResult, GroupSearchResult, MessageSearchResult } from '@/api/modules/search'
|
||||
|
||||
const router = useRouter()
|
||||
const authStore = useAuthStore()
|
||||
@@ -737,6 +754,12 @@ let connectionCheckInterval: number | null = null
|
||||
const currentTab = ref<'chat' | 'contact' | 'moment'>(storage.getCurrentTab() as 'chat' | 'contact' | 'moment')
|
||||
const searchQuery = ref('')
|
||||
const inputText = ref('')
|
||||
|
||||
// 聚合搜索状态
|
||||
const showSearchResults = ref(false)
|
||||
const searchLoading = ref(false)
|
||||
const searchResult = ref<GlobalSearchResult>({ contacts: [], groups: [], messages: [] })
|
||||
let searchDebounceTimer: ReturnType<typeof setTimeout> | null = null
|
||||
const chatVisible = ref(false)
|
||||
const isMobile = ref(window.innerWidth < 768)
|
||||
const showProfileModal = ref(false)
|
||||
@@ -1926,6 +1949,114 @@ async function handleGroupMemberRemoved() {
|
||||
await conversationStore.loadConversations()
|
||||
}
|
||||
|
||||
// ========== 聚合搜索相关函数 ==========
|
||||
|
||||
// 搜索输入处理(带防抖)
|
||||
function handleSearchInput() {
|
||||
if (searchDebounceTimer) {
|
||||
clearTimeout(searchDebounceTimer)
|
||||
}
|
||||
|
||||
const query = searchQuery.value.trim()
|
||||
if (!query) {
|
||||
showSearchResults.value = false
|
||||
searchResult.value = { contacts: [], groups: [], messages: [] }
|
||||
return
|
||||
}
|
||||
|
||||
showSearchResults.value = true
|
||||
searchLoading.value = true
|
||||
|
||||
searchDebounceTimer = setTimeout(async () => {
|
||||
try {
|
||||
const result = await searchApi.globalSearch(query, 'all', 20)
|
||||
searchResult.value = result
|
||||
} catch (error) {
|
||||
console.error('搜索失败:', error)
|
||||
searchResult.value = { contacts: [], groups: [], messages: [] }
|
||||
} finally {
|
||||
searchLoading.value = false
|
||||
}
|
||||
}, 300)
|
||||
}
|
||||
|
||||
// 搜索框获得焦点
|
||||
function handleSearchFocus() {
|
||||
if (searchQuery.value.trim()) {
|
||||
showSearchResults.value = true
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭搜索结果
|
||||
function closeSearchResults() {
|
||||
showSearchResults.value = false
|
||||
}
|
||||
|
||||
// 选择搜索结果中的联系人
|
||||
function handleSearchContactSelect(contact: ContactSearchResult) {
|
||||
closeSearchResults()
|
||||
searchQuery.value = ''
|
||||
|
||||
// 构造 Contact 对象并选中
|
||||
const targetContact: Contact = {
|
||||
id: contact.user.id,
|
||||
user_id: contact.user.id,
|
||||
contact_user_id: contact.user.id,
|
||||
room_id: contact.room_id,
|
||||
room_type: 'p2p',
|
||||
is_group: false,
|
||||
remark_name: contact.remark_name,
|
||||
is_top: false,
|
||||
is_muted: false,
|
||||
user: contact.user
|
||||
}
|
||||
chatStore.setCurrentTarget(targetContact)
|
||||
}
|
||||
|
||||
// 选择搜索结果中的群聊
|
||||
function handleSearchGroupSelect(group: GroupSearchResult) {
|
||||
closeSearchResults()
|
||||
searchQuery.value = ''
|
||||
|
||||
// 构造群聊 Contact 对象并选中
|
||||
const targetGroup: Contact = {
|
||||
id: group.room_id,
|
||||
user_id: group.room_id,
|
||||
contact_user_id: group.room_id,
|
||||
room_id: group.room_id,
|
||||
room_type: 'group',
|
||||
is_group: true,
|
||||
remark_name: group.room_name,
|
||||
member_count: group.member_count,
|
||||
owner_id: group.owner_id,
|
||||
is_top: false,
|
||||
is_muted: false
|
||||
}
|
||||
chatStore.setCurrentTarget(targetGroup)
|
||||
}
|
||||
|
||||
// 选择搜索结果中的消息
|
||||
function handleSearchMessageSelect(message: MessageSearchResult) {
|
||||
closeSearchResults()
|
||||
searchQuery.value = ''
|
||||
|
||||
// 根据消息的 room_id 跳转到对应会话
|
||||
const isGroup = message.is_group_chat
|
||||
const targetContact: Contact = {
|
||||
id: message.room_id,
|
||||
user_id: isGroup ? message.room_id : (message.sender?.id || message.room_id),
|
||||
contact_user_id: isGroup ? message.room_id : (message.sender?.id || message.room_id),
|
||||
room_id: message.room_id,
|
||||
room_type: isGroup ? 'group' : 'p2p',
|
||||
is_group: isGroup,
|
||||
remark_name: message.room_name || message.sender?.name || '',
|
||||
is_top: false,
|
||||
is_muted: false,
|
||||
user: message.sender
|
||||
}
|
||||
chatStore.setCurrentTarget(targetContact)
|
||||
}
|
||||
|
||||
// 刷新会话列表
|
||||
async function refreshConversations() {
|
||||
if (refreshingConversations.value) return
|
||||
|
||||
@@ -4,12 +4,26 @@
|
||||
<div class="p-4 border-b border-gray-800/50">
|
||||
<div class="flex gap-2">
|
||||
<div class="relative group flex-1">
|
||||
<i class="fas fa-search absolute left-3 top-1/2 -translate-y-1/2 text-gray-500 group-focus-within:text-primary transition-colors text-sm"></i>
|
||||
<i class="fas fa-search absolute left-3 top-1/2 -translate-y-1/2 text-gray-500 group-focus-within:text-primary transition-colors text-sm z-10"></i>
|
||||
<input
|
||||
v-model="searchKeyword"
|
||||
type="text"
|
||||
class="w-full bg-black/20 hover:bg-black/30 focus:bg-black/40 border border-transparent focus:border-primary/30 rounded-xl py-2.5 pl-9 pr-3 text-sm text-white focus:ring-0 outline-none transition-all placeholder-gray-500"
|
||||
placeholder="搜索好友、群组..."
|
||||
@input="handleSearchInput"
|
||||
@focus="handleSearchFocus"
|
||||
@keydown.esc="closeSearchResults"
|
||||
/>
|
||||
<!-- 聚合搜索结果 -->
|
||||
<SearchResults
|
||||
:show="showSearchResults"
|
||||
:loading="searchLoading"
|
||||
:keyword="searchKeyword"
|
||||
:result="searchResult"
|
||||
@select-contact="handleSearchContactSelect"
|
||||
@select-group="handleSearchGroupSelect"
|
||||
@select-message="handleSearchMessageSelect"
|
||||
@close="closeSearchResults"
|
||||
/>
|
||||
</div>
|
||||
<!-- 加号按钮 -->
|
||||
@@ -470,7 +484,10 @@ import ContextMenu from '@/components/common/ContextMenu.vue'
|
||||
import ConfirmModal from '@/components/common/ConfirmModal.vue'
|
||||
import SelectContactsModal from '@/components/chat/SelectContactsModal.vue'
|
||||
import GroupChatCard from '@/components/contact/GroupChatCard.vue'
|
||||
import SearchResults from '@/components/common/SearchResults.vue'
|
||||
import * as searchApi from '@/api/modules/search'
|
||||
import type { Contact, ContactGroup } from '@/types/api'
|
||||
import type { GlobalSearchResult, ContactSearchResult, GroupSearchResult, MessageSearchResult } from '@/api/modules/search'
|
||||
|
||||
const router = useRouter()
|
||||
const contactStore = useContactStore()
|
||||
@@ -487,6 +504,12 @@ const showActionMenu = ref(false)
|
||||
const showCreateGroupModal = ref(false)
|
||||
const showJoinGroupModal = ref(false)
|
||||
|
||||
// 聚合搜索状态
|
||||
const showSearchResults = ref(false)
|
||||
const searchLoading = ref(false)
|
||||
const searchResult = ref<GlobalSearchResult>({ contacts: [], groups: [], messages: [] })
|
||||
let searchDebounceTimer: ReturnType<typeof setTimeout> | null = null
|
||||
|
||||
// 群聊列表状态
|
||||
const groupChats = ref<Array<{
|
||||
room_id: string
|
||||
@@ -581,6 +604,108 @@ const filteredGroupChats = computed(() => {
|
||||
})
|
||||
})
|
||||
|
||||
// --- 聚合搜索相关函数 ---
|
||||
|
||||
function handleSearchInput() {
|
||||
if (searchDebounceTimer) {
|
||||
clearTimeout(searchDebounceTimer)
|
||||
}
|
||||
|
||||
const query = searchKeyword.value.trim()
|
||||
if (!query) {
|
||||
showSearchResults.value = false
|
||||
searchResult.value = { contacts: [], groups: [], messages: [] }
|
||||
return
|
||||
}
|
||||
|
||||
showSearchResults.value = true
|
||||
searchLoading.value = true
|
||||
|
||||
searchDebounceTimer = setTimeout(async () => {
|
||||
try {
|
||||
const result = await searchApi.globalSearch(query, 'all', 20)
|
||||
searchResult.value = result
|
||||
} catch (error) {
|
||||
console.error('搜索失败:', error)
|
||||
searchResult.value = { contacts: [], groups: [], messages: [] }
|
||||
} finally {
|
||||
searchLoading.value = false
|
||||
}
|
||||
}, 300)
|
||||
}
|
||||
|
||||
function handleSearchFocus() {
|
||||
if (searchKeyword.value.trim()) {
|
||||
showSearchResults.value = true
|
||||
}
|
||||
}
|
||||
|
||||
function closeSearchResults() {
|
||||
showSearchResults.value = false
|
||||
}
|
||||
|
||||
function handleSearchContactSelect(contact: ContactSearchResult) {
|
||||
closeSearchResults()
|
||||
searchKeyword.value = ''
|
||||
|
||||
const targetContact: Contact = {
|
||||
id: contact.user.id,
|
||||
user_id: contact.user.id,
|
||||
contact_user_id: contact.user.id,
|
||||
room_id: contact.room_id,
|
||||
room_type: 'p2p',
|
||||
is_group: false,
|
||||
remark_name: contact.remark_name,
|
||||
is_top: false,
|
||||
is_muted: false,
|
||||
user: contact.user
|
||||
}
|
||||
chatStore.setCurrentTarget(targetContact)
|
||||
router.push('/chat')
|
||||
}
|
||||
|
||||
function handleSearchGroupSelect(group: GroupSearchResult) {
|
||||
closeSearchResults()
|
||||
searchKeyword.value = ''
|
||||
|
||||
const targetGroup: Contact = {
|
||||
id: group.room_id,
|
||||
user_id: group.room_id,
|
||||
contact_user_id: group.room_id,
|
||||
room_id: group.room_id,
|
||||
room_type: 'group',
|
||||
is_group: true,
|
||||
remark_name: group.room_name,
|
||||
member_count: group.member_count,
|
||||
owner_id: group.owner_id,
|
||||
is_top: false,
|
||||
is_muted: false
|
||||
}
|
||||
chatStore.setCurrentTarget(targetGroup)
|
||||
router.push('/chat')
|
||||
}
|
||||
|
||||
function handleSearchMessageSelect(message: MessageSearchResult) {
|
||||
closeSearchResults()
|
||||
searchKeyword.value = ''
|
||||
|
||||
const isGroup = message.is_group_chat
|
||||
const targetContact: Contact = {
|
||||
id: message.room_id,
|
||||
user_id: isGroup ? message.room_id : (message.sender?.id || message.room_id),
|
||||
contact_user_id: isGroup ? message.room_id : (message.sender?.id || message.room_id),
|
||||
room_id: message.room_id,
|
||||
room_type: isGroup ? 'group' : 'p2p',
|
||||
is_group: isGroup,
|
||||
remark_name: message.room_name || message.sender?.name || '',
|
||||
is_top: false,
|
||||
is_muted: false,
|
||||
user: message.sender
|
||||
}
|
||||
chatStore.setCurrentTarget(targetContact)
|
||||
router.push('/chat')
|
||||
}
|
||||
|
||||
// --- API & Actions ---
|
||||
|
||||
async function loadGroups() {
|
||||
|
||||
Reference in New Issue
Block a user