概述
SearchState 是基于 Vue 3 Composition API 的搜索状态管理钩子,为 Search 组件提供完整的状态管理能力。它支持多种搜索模式(标准模式、嵌入式模式),管理搜索关键词、搜索结果、加载状态、错误处理等功能。如果自定义组件能力不能支持您的业务,可以使用 SearchState 实现您的需求。
数据
属性名 | 类型 | 说明 |
keyword | Ref<string> | 当前搜索关键词 |
results | Ref<Map<SearchType, SearchResult<SearchType>>> | 搜索结果集合 |
isLoading | Ref<boolean> | 是否正在搜索 |
error | Ref<Error | null> | 搜索错误信息 |
searchAdvancedParams | Ref<Map<SearchType, SearchParamsMap[SearchType]>> | 高级搜索参数 |
selectedSearchType | Ref<SearchType | 'all'> | 当前选中的搜索类型 |
方法
方法名 | 类型 | 说明 |
setKeyword | (k: string) => Promise<void> | 设置搜索关键词 |
loadMore | (type?: SearchType) => Promise<void> | 加载更多搜索结果 |
setSelectedType | (type: SearchType | 'all') => void | 设置搜索类型 |
setSearchMessageAdvancedParams | (params: SearchCloudMessagesParams) => void | 设置消息搜索高级参数 |
setSearchUserAdvancedParams | (params: SearchCloudUsersParams) => void | 设置用户搜索高级参数 |
setSearchGroupAdvancedParams | (params: SearchCloudGroupsParams) => void | 设置群组搜索高级参数 |
使用示例
<template><div class="custom-search-app"><div class="search-header"><inputv-model="searchInput"type="text"placeholder="输入搜索关键词..."@keyup.enter="performSearch"/><button @click="performSearch" :disabled="isLoading">{{ isLoading ? '搜索中...' : '搜索' }}</button></div><div class="search-tabs"><buttonv-for="tab in searchTabs":key="tab.type":class="['tab-btn', { active: selectedSearchType === tab.type }]"@click="setSelectedType(tab.type)">{{ tab.label }}<span v-if="getResultCount(tab.type) > 0" class="result-count">{{ getResultCount(tab.type) }}</span></button></div><div class="search-content"><div v-if="isLoading" class="loading-state"><div class="loading-spinner"></div><div>搜索中...</div></div><div v-else-if="error" class="error-state"><div class="error-icon">❌</div><div>搜索失败: {{ error.message }}</div><button @click="retrySearch">重试</button></div><div v-else-if="keyword && results.size === 0" class="empty-state"><div class="empty-icon">🔍</div><div>未找到相关结果</div></div><div v-else-if="keyword" class="results-container"><divv-for="[type, result] in filteredResults":key="type"class="result-section"><h3>{{ getTypeLabel(type) }} ({{ result.resultList.length }})</h3><divv-for="(item, index) in result.resultList":key="index"class="result-item"@click="handleItemClick(item, type)"><component:is="getResultItemComponent(type)":data="item":keyword="keyword":type="type"/></div><buttonv-if="result.hasMore"@click="loadMore(type)"class="load-more-btn">加载更多</button></div></div><div v-else class="presearch-state"><div class="presearch-icon">🔍</div><div>输入关键词开始搜索</div></div></div></div></template><script setup lang="ts">import { ref, computed } from 'vue';import { useSearchState, VariantType, SearchType } from '@tencentcloud/chat-uikit-vue3';import type { SearchResultItemType } from '@tencentcloud/chat-uikit-vue3';const {keyword,results,isLoading,error,selectedSearchType,setKeyword,setSelectedType,loadMore} = useSearchState(VariantType.STANDARD);const searchInput = ref('');const searchTabs = [{ type: 'all', label: '全部' },{ type: SearchType.USER, label: '用户' },{ type: SearchType.GROUP, label: '群组' },{ type: SearchType.MESSAGE, label: '消息' }];const filteredResults = computed(() => {if (selectedSearchType.value === 'all') {return Array.from(results.value.entries());} else {const result = results.value.get(selectedSearchType.value);return result ? [[selectedSearchType.value, result]] : [];}});const getResultCount = (type: string) => {if (type === 'all') {return Array.from(results.value.values()).reduce((total, result) => total + result.resultList.length, 0);}return results.value.get(type as SearchType)?.resultList.length || 0;};const getTypeLabel = (type: SearchType) => {const labels = {[SearchType.USER]: '用户',[SearchType.GROUP]: '群组',[SearchType.MESSAGE]: '消息',[SearchType.CHAT_MESSAGE]: '聊天消息'};return labels[type] || type;};const getResultItemComponent = (type: SearchType) => {// 返回对应类型的结果项组件return 'div'; // 简化示例};const performSearch = async () => {if (!searchInput.value.trim()) return;try {await setKeyword(searchInput.value);} catch (err) {console.error('搜索失败:', err);}};const retrySearch = () => {performSearch();};const handleItemClick = (item: SearchResultItemType, type: SearchType) => {console.log('点击结果:', item, type);};</script><style scoped>.custom-search-app {max-width: 800px;margin: 0 auto;padding: 20px;}.search-header {display: flex;gap: 12px;margin-bottom: 20px;}.search-header input {flex: 1;padding: 10px 12px;border: 1px solid #d9d9d9;border-radius: 6px;font-size: 14px;}.search-header button {padding: 10px 20px;background: #1890ff;color: white;border: none;border-radius: 6px;cursor: pointer;font-size: 14px;}.search-header button:disabled {background: #d9d9d9;cursor: not-allowed;}.search-tabs {display: flex;gap: 8px;margin-bottom: 20px;border-bottom: 1px solid #f0f0f0;padding-bottom: 16px;}.tab-btn {padding: 8px 16px;border: 1px solid #d9d9d9;border-radius: 4px;background: white;cursor: pointer;font-size: 14px;transition: all 0.2s;display: flex;align-items: center;gap: 6px;}.tab-btn:hover {border-color: #1890ff;color: #1890ff;}.tab-btn.active {background: #1890ff;border-color: #1890ff;color: white;}.result-count {background: rgba(255, 255, 255, 0.2);padding: 2px 6px;border-radius: 10px;font-size: 12px;font-weight: 600;}.search-content {min-height: 400px;}.loading-state,.error-state,.empty-state,.presearch-state {display: flex;flex-direction: column;align-items: center;justify-content: center;padding: 80px 20px;color: #666;}.loading-spinner {width: 32px;height: 32px;border: 3px solid #f3f3f3;border-top: 3px solid #1890ff;border-radius: 50%;animation: spin 1s linear infinite;margin-bottom: 16px;}.error-icon,.empty-icon,.presearch-icon {font-size: 48px;margin-bottom: 16px;}.result-section {margin-bottom: 24px;}.result-section h3 {color: #333;margin-bottom: 12px;font-size: 16px;}.result-item {border-bottom: 1px solid #f0f0f0;}.load-more-btn {width: 100%;padding: 12px;background: #f8f9fa;border: 1px solid #e0e0e0;border-radius: 4px;cursor: pointer;font-size: 14px;color: #666;margin-top: 8px;transition: all 0.2s;}.load-more-btn:hover {background: #e6f7ff;border-color: #1890ff;color: #1890ff;}@keyframes spin {0% { transform: rotate(0deg); }100% { transform: rotate(360deg); }}</style>
接入组件后效果如下:
搜索页示例 | 搜索结果示例 |
![]() | ![]() |