Search

最近更新时间:2025-09-12 18:14:52

我的收藏

概述

Search 是一个功能完整的搜索解决方案,包含了搜索框、搜索结果展示、高级搜索等功能的完整组件集合。适用于即时通讯、在线会议、在线教育等场景的用户、群组、消息搜索。
说明:
此功能属于增值服务,需要您购买云端搜索插件,请点击 购买


搜索模式

迷你模式 (MINI)
标准模式 (STANDARD)
嵌入式模式 (EMBEDDED)
适用于侧边栏或小容器
显示有限结果数量
支持展开查看更多
适用于全屏搜索界面
完整功能展示
支持高级搜索
适用于聊天界面
专注消息搜索
优化的布局

Props

属性名
类型
默认值
说明
variant
VariantType
VariantType.MINI
搜索模式:mini(迷你)、standard(标准)、embedded(嵌入式)
SearchBar
Component<SearchBarProps>
DefaultSearchBar
自定义搜索框组件
SearchResults
Component<SearchResultsProps>
DefaultSearchResults
自定义搜索结果组件
SearchAdvanced
Component<SearchAdvancedProps>
DefaultSearchAdvanced
自定义高级搜索组件
SearchResultsPresearch
Component
-
搜索前占位符组件
SearchResultsLoading
Component
-
加载中占位符组件
SearchResultsEmpty
Component
-
空结果占位符组件
SearchResultItem
Component<ResultItemProps>
-
自定义搜索结果项组件
debounceTime
number
300
搜索防抖时间(毫秒)
autoFocus
boolean
false
是否自动聚焦搜索框
className
string
-
自定义样式类名
style
CSSProperties
-
自定义样式
onKeywordChange
(keyword: string) => void
-
搜索关键词变化回调
onSearchComplete
(results: Map<SearchType, SearchResult>) => void
-
搜索完成回调
onResultItemClick
(data: SearchResultItem, type: SearchType) => void
-
搜索结果点击回调
onError
(error: Error) => void
-
搜索错误回调

基础用法

<template>
<div class="app">
<Search
:variant="VariantType.STANDARD"
@result-item-click="handleResultClick"
@keyword-change="handleKeywordChange"
@error="handleError"
/>
</div>
</template>

<script setup lang="ts">
import { Search, VariantType } from '@tencentcloud/chat-uikit-vue3';
import type { SearchResultItemType, SearchType } from '@tencentcloud/chat-uikit-vue3';

const handleResultClick = (data: SearchResultItemType, type: SearchType) => {
console.log('搜索结果点击:', data, type);
};

const handleKeywordChange = (keyword: string) => {
console.log('搜索关键词变化:', keyword);
};

const handleError = (error: Error) => {
console.error('搜索错误:', error);
};
</script>

自定义

Search 组件提供了多个可替换的子组件,允许用户自定义 SearchBar, SearchResults, SearchAdvanced, SearchResultItem, SearchResultsPresearch, SearchResultsLoading, SearchResultsEmpty 等。同时,用户还可以利用默认子组件进行二次开发定制。
自定义 SearchBar
自定义 SearchResults
自定义 SearchAdvanced
自定义 SearchResultItem
自定义占位符
Props
属性名
类型
默认值
说明
value
string
-
搜索框值
onChange
(e: Event) => void
-
输入变化回调
onKeyDown
(e: KeyboardEvent) => void
-
键盘事件回调
onClear
() => void
-
清除回调
placeholder
string
-
占位符文本
SearchIcon
Component
-
自定义搜索图标
ClearIcon
Component
-
自定义清除图标
className
string
-
自定义样式类名
style
CSSProperties
-
自定义样式
onFocus
(e: FocusEvent) => void
-
获得焦点回调
onBlur
(e: FocusEvent) => void
-
失去焦点回调
autoFocus
boolean
false
是否自动聚焦
variant
VariantType
-
搜索模式
示例
<template>
<div class="custom-search-bar">
<div class="search-bar-header">
<span class="search-icon">🔍</span>
<span class="search-title">智能搜索</span>
</div>
<div class="search-input-wrapper">
<input
type="text"
:value="value"
:placeholder="placeholder || '输入关键词开始搜索...'"
@input="handleChange"
class="enhanced-search-input"
/>
<button
v-if="value"
@click="handleClear"
class="enhanced-clear-btn"
>
</button>
</div>
</div>
</template>

<script setup lang="ts">
interface SearchBarProps {
value?: string;
onChange?: (event: Event) => void;
onClear?: () => void;
placeholder?: string;
variant?: string;
}

const props = defineProps<SearchBarProps>();

const emit = defineEmits<{
change: [event: Event];
clear: [];
}>();

const handleChange = (event: Event) => {
if (props.onChange) {
props.onChange(event);
}
emit('change', event);
};

const handleClear = () => {
if (props.onClear) {
props.onClear();
}
emit('clear');
};
</script>

<style scoped>
.custom-search-bar {
display: flex;
flex-direction: column;
gap: 8px;
}

.search-bar-header {
display: flex;
align-items: center;
gap: 8px;
}

.search-icon {
font-size: 16px;
}

.search-title {
font-weight: 500;
color: #333;
}

.search-input-wrapper {
position: relative;
display: flex;
align-items: center;
}

.enhanced-search-input {
width: 100%;
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
outline: none;
}

.enhanced-search-input:focus {
border-color: #007bff;
}

.enhanced-clear-btn {
position: absolute;
right: 8px;
background: none;
border: none;
cursor: pointer;
color: #999;
font-size: 16px;
}

.enhanced-clear-btn:hover {
color: #666;
}
</style>

<template>
<Search :SearchBar="CustomSearchBar" />
</template>

<script setup lang="ts">
import { Search } from '@tencentcloud/chat-uikit-vue3';
import CustomSearchBar from './CustomSearchBar.vue';
</script>

Props
属性名
类型
默认值
说明
results
Map<SearchType, SearchResult>
-
搜索结果数据
isLoading
boolean
-
是否正在加载
error
Error | null
-
错误信息
keyword
string
-
搜索关键词
typeLabels
Record<SearchType, string>
-
搜索类型标签
onLoadMore
(type: SearchType) => void
-
加载更多回调
onResultItemClick
(data: SearchResultItemType, type: SearchType) => void
-
结果项点击回调
SearchResultsLoading
Component
Loading
自定义加载组件
SearchResultsPresearch
Component
-
搜索前占位符组件
SearchResultsEmpty
Component
EmptyResult
空结果占位符组件
SearchResultItem
Component<ResultItemProps>
DefaultSearchResultsItem
自定义结果项组件
variant
VariantType
VariantType.STANDARD
显示模式
searchType
SearchType | 'all'
'all'
当前搜索类型
示例
<template>
<div class="custom-search-results">
<div class="search-results-header">
<span class="results-icon">📋</span>
<span class="results-title">搜索结果</span>
</div>
<div class="search-results-content">
<div v-if="!results || results.size === 0" class="no-results">
暂无搜索结果
</div>
<div v-else class="results-list">
<div
v-for="(item, key) in results.keys()"
:key="key"
class="result-item"
>
<div class="result-group">
<div class="result-title">
{{ item }}
</div>
<ul v-if="item === SearchType.MESSAGE" class="result-description">
<li
v-for="(data, index) in results.get(item)?.resultList"
:key="index"
@click="handleResultClick(data)"
>
{{ (data as SearchCloudMessagesResultItem).conversation?.getShowName() }}
</li>
</ul>
<ul v-if="item === SearchType.USER" class="result-description">
<li
v-for="(data, index) in results.get(item)?.resultList"
:key="index"
@click="handleResultClick(data)"
>
{{ (data as SearchCloudUsersResultItem).profile.nick
|| (data as SearchCloudUsersResultItem).profile.userID }}
</li>
</ul>
<ul v-if="item === SearchType.GROUP" class="result-description">
<li
v-for="(data, index) in results.get(item)?.resultList"
:key="index"
@click="handleResultClick(data)"
>
{{ (data as SearchCloudGroupsResultItem).groupInfo.name
|| (data as SearchCloudGroupsResultItem).groupInfo.groupID }}
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</template>

<script setup lang="ts">
import { SearchType } from '@tencentcloud/chat-uikit-vue3';
import type {
SearchCloudGroupsResultItem,
SearchCloudMessagesResultItem,
SearchCloudUsersResultItem,
SearchResult,
SearchResultItemType,
} from '@tencentcloud/chat-uikit-vue3';

interface SearchResultsProps {
results?: Map<SearchType, SearchResult<any>>;
loading?: boolean;
keyword?: string;
searchType?: SearchType;
}

const props = defineProps<SearchResultsProps>();

const emit = defineEmits<{
'result-item-click': [data: SearchResultItemType, type: any];
}>();

const handleResultClick = (item: SearchResultItemType) => {
emit('result-item-click', item, props.searchType || 'ALL');
};
</script>

<style scoped>
.custom-search-results {
display: flex;
flex-direction: column;
gap: 8px;
}

.search-results-header {
display: flex;
align-items: center;
gap: 8px;
}

.results-icon {
font-size: 16px;
}

.results-title {
font-weight: 500;
color: #333;
}

.search-results-content {
max-height: 400px;
overflow-y: auto;
}

.no-results {
padding: 20px;
text-align: center;
color: #999;
font-size: 14px;
}

.results-list {
display: flex;
flex-direction: column;
gap: 4px;
}

.result-item {
padding: 12px;
border: 1px solid #eee;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.2s;
}

.result-item:hover {
background-color: #f5f5f5;
}

.result-content {
display: flex;
flex-direction: column;
gap: 4px;
}

.result-title {
font-weight: 500;
color: #333;
font-size: 14px;
}

.result-description {
color: #666;
font-size: 12px;
line-height: 1.4;
}
</style>

<template>
<Search :SearchResults="CustomSearchResults" />
</template>

<script setup lang="ts">
import { Search } from '@tencentcloud/chat-uikit-vue3';
import CustomSearchResults from './CustomSearchResults.vue';
</script>
Props
属性名
类型
默认值
说明
variant
VariantType
-
显示模式
searchType
SearchTabType
-
当前搜索类型
advancedParams
Map<SearchType, SearchParamsMap[SearchType]>
-
高级搜索参数
onAdvancedParamsChange
(type: SearchType, params: SearchParamsMap[SearchType]) => void
-
参数变化回调
示例
<template>
<div class="custom-search-advanced">
<div class="search-advanced-header">
<span class="advanced-icon">⚙️</span>
<span class="advanced-title">高级搜索</span>
</div>
<div class="search-advanced-content">
<div class="search-type-section">
<div class="section-title">
搜索类型
</div>
<div class="search-type-options">
<label
v-for="type in searchTypes"
:key="type.value"
class="type-option"
>
<input
type="radio"
:value="type.value"
:checked="searchType === type.value"
@change="handleSearchTypeChange(type.value as SearchType)"
>
<span class="type-label">{{ type.label }}</span>
</label>
</div>
</div>
</div>
</div>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { SearchType, useSearchState } from '@tencentcloud/chat-uikit-vue3';
import type { VariantType } from '@tencentcloud/chat-uikit-vue3';

interface CustomSearchAdvancedProps {
variant?: VariantType;
searchType?: SearchType;
}

const props = defineProps<CustomSearchAdvancedProps>();
const { setSelectedType } = useSearchState();

const emit = defineEmits<{
'search-type-change': [type: SearchType | 'all'];
}>();

const searchTypes = [
{ value: 'all', label: '全部' },
{ value: SearchType.MESSAGE, label: '消息' },
{ value: SearchType.USER, label: '用户' },
{ value: SearchType.GROUP, label: '群组' },
];

const searchType = ref<SearchType | 'all'>(props.searchType || 'all');

const handleSearchTypeChange = (type: SearchType) => {
searchType.value = type;
setSelectedType(type);
emit('search-type-change', type);
};
</script>

<style scoped>
.custom-search-advanced {
display: flex;
flex-direction: column;
gap: 16px;
padding: 16px;
border: 1px solid #eee;
border-radius: 8px;
background-color: #fff;
}

.search-advanced-header {
display: flex;
align-items: center;
gap: 8px;
padding-bottom: 12px;
border-bottom: 1px solid #f0f0f0;
}

.advanced-icon {
font-size: 16px;
}

.advanced-title {
font-weight: 500;
color: #333;
font-size: 16px;
}

.search-advanced-content {
display: flex;
flex-direction: column;
gap: 16px;
}

.section-title {
font-weight: 500;
color: #333;
font-size: 14px;
margin-bottom: 8px;
}

.search-type-options {
display: flex;
flex-wrap: wrap;
gap: 12px;
}

.type-option {
display: flex;
align-items: center;
gap: 6px;
cursor: pointer;
}

.type-option input[type="radio"] {
margin: 0;
}

.type-label {
font-size: 14px;
color: #666;
}
</style>

<template>
<Search :SearchAdvanced="CustomSearchAdvanced" />
</template>

<script setup lang="ts">
import { Search } from '@tencentcloud/chat-uikit-vue3';
import CustomSearchAdvanced from './CustomSearchAdvanced.vue';
</script>
Props
属性名
类型
默认值
说明
data
SearchResultItem
-
搜索结果数据
type
SearchType
-
搜索结果类型
keyword
string
-
搜索关键词
onClick
(data: SearchResultItem, type: SearchType) => void
-
点击回调
className
string
-
自定义样式类名
示例
<template>
<div class="custom-search-item">
<div
v-if="type === SearchType.USER"
class="user-item"
@click="handleClick"
>
<div class="user-avatar">
<img :src="data.profile.avatar" :alt="data.profile.nick" />
</div>
<div class="user-info">
<div class="user-name">{{ data.profile.nick }}</div>
<div class="user-id">{{ data.profile.userID }}</div>
</div>
</div>

<div
v-else-if="type === SearchType.GROUP"
class="group-item"
@click="handleClick"
>
<div class="group-avatar">👥</div>
<div class="group-info">
<div class="group-name">{{ data.groupInfo.name }}</div>
<div class="group-id">{{ data.groupInfo.groupID }}</div>
</div>
</div>

<div
v-else-if="type === SearchType.MESSAGE"
class="message-item"
@click="handleClick"
>
<div class="message-content">{{ data.conversation.getShowName() }}</div>
<div class="message-num">{{ data.messageCount }}</div>
</div>
</div>
</template>

<script setup lang="ts">
import { SearchType } from '@tencentcloud/chat-uikit-vue3';
import type { SearchResultItemType } from '@tencentcloud/chat-uikit-vue3';

interface CustomSearchItemProps {
data: SearchResultItemType;
type?: SearchType;
onClick?: (data: SearchResultItemType, type: SearchType) => void;
}

const props = defineProps<CustomSearchItemProps>();

console.warn('CustomSearchItem', props);

const handleClick = () => {
if (props.onClick && props.type) {
props.onClick(props.data, props.type);
}
};

const formatTime = (timestamp: number) => {
return new Date(timestamp).toLocaleString();
};
</script>

<style scoped>
.custom-search-item {
cursor: pointer;
padding: 12px;
border-bottom: 1px solid #f0f0f0;
transition: background-color 0.2s;
}

.custom-search-item:hover {
background-color: #f5f5f5;
}

.user-item,
.group-item,
.message-item {
display: flex;
align-items: center;
gap: 12px;
}

.user-avatar img {
width: 40px;
height: 40px;
border-radius: 50%;
object-fit: cover;
}

.group-avatar {
width: 40px;
height: 40px;
border-radius: 8px;
background-color: #e9ecef;
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
}

.user-info,
.group-info {
flex: 1;
}

.user-name,
.group-name {
font-weight: 500;
color: #333;
font-size: 14px;
margin-bottom: 4px;
}

.user-id,
.group-id {
color: #666;
font-size: 12px;
}

.message-content {
flex: 1;
color: #333;
font-size: 14px;
line-height: 1.4;
}

.message-num {
color: #666;
font-size: 12px;
white-space: nowrap;
}
</style>
<template>
<Search :SearchResultItem="CustomSearchItem" />
</template>

<script setup lang="ts">
import { Search } from '@tencentcloud/chat-uikit-vue3';
import CustomSearchItem from './CustomSearchItem.vue';
</script>
Props
属性名
类型
默认值
说明
SearchResultsPresearch
Component
-
搜索前占位符组件
SearchResultsLoading
Component
-
加载中占位符组件
SearchResultsEmpty
Component
-
空结果占位符组件
示例
<template>
<Search
:search-results-presearch="PresearchComponent"
:search-results-loading="LoadingComponent"
:search-results-empty="EmptyComponent"
/>
</template>

<script setup lang="ts">
import { defineComponent, h } from 'vue';
import { Search } from '@tencentcloud/chat-uikit-vue3';

const PresearchComponent = defineComponent({
name: 'PresearchComponent',
setup() {
return () => h('div', { class: 'presearch-placeholder' }, [
h('div', { class: 'placeholder-icon' }, '🔍'),
h('div', { class: 'placeholder-text' }, '输入关键词开始搜索')
]);
}
});

const LoadingComponent = defineComponent({
name: 'LoadingComponent',
setup() {
return () => h('div', { class: 'loading-placeholder' }, [
h('div', { class: 'loading-spinner' }),
h('div', { class: 'loading-text' }, '搜索中...')
]);
}
});

const EmptyComponent = defineComponent({
name: 'EmptyComponent',
setup() {
return () => h('div', { class: 'empty-placeholder' }, [
h('div', { class: 'empty-icon' }, '😔'),
h('div', { class: 'empty-text' }, '未找到相关结果'),
h('div', { class: 'empty-hint' }, '尝试使用其他关键词搜索')
]);
}
});
</script>

<style scoped>
.presearch-placeholder,
.loading-placeholder,
.empty-placeholder {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 60px 20px;
color: #999;
}

.placeholder-icon,
.empty-icon {
font-size: 48px;
margin-bottom: 16px;
}

.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;
}

@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}

.placeholder-text,
.loading-text,
.empty-text {
font-size: 16px;
margin-bottom: 8px;
}

.empty-hint {
font-size: 14px;
color: #bbb;
}
</style>