# ConversationSearch 组件自定义文档

## 概述

ConversationSearch 是一个专门用于会话列表的搜索组件，提供了搜索用户、群组和消息的功能。组件支持两种显示模式：迷你模式（在会话列表中的简洁搜索）和标准模式（全屏搜索界面），能够智能切换搜索界面，并集成了完整的搜索结果处理逻辑。该组件继承了 Search 组件的所有功能，同时针对会话场景进行了专门的优化和定制。

## Props 配置表格

| 字段 | 类型 | 默认值 | 描述 |
|------|------|--------|------|
| visible | `boolean` | `true` | 是否显示搜索组件 |
| Search | `React.ComponentType<SearchProps>` | `Search` | 自定义搜索组件 |
| SearchBar | `React.ComponentType<SearchBarProps>` | `SearchBar` | 自定义搜索输入框组件 |
| SearchResultsPresearch | `React.ComponentType` | - | 自定义搜索前状态组件 |
| SearchResultsLoading | `React.ComponentType` | - | 自定义搜索加载状态组件 |
| SearchResultsEmpty | `React.ComponentType` | - | 自定义搜索空结果组件 |
| SearchResultItem | `React.ComponentType<ResultItemProps<SearchType>>` | - | 自定义搜索结果项组件 |
| debounceTime | `number` | `300` | 搜索防抖延迟时间（毫秒） |
| autoFocus | `boolean` | `false` | 是否自动聚焦搜索框 |
| className | `string` | - | 自定义样式类名 |
| style | `React.CSSProperties` | - | 自定义样式 |
| onKeywordChange | `(keyword: string) => void` | - | 搜索关键词变化回调 |
| onSearchComplete | `(results: Map<SearchType, SearchResult<SearchType>>) => void` | - | 搜索完成回调 |
| onResultItemClick | `(data: SearchResultItemType, type: SearchType) => void` | - | 搜索结果项点击回调 |
| onError | `(error: Error) => void` | - | 搜索错误回调 |

## Props 详解

### visible

type: `boolean`

visible 用于控制搜索组件是否显示，当设置为 false 时组件不会渲染，默认值为 `true`。

### Search

type: `React.ComponentType<SearchProps>`

Search 用于自定义整个搜索组件，可以完全替换默认的搜索功能，默认值为 `Search`。

#### Example1: 自定义搜索组件样式

```tsx
import { ConversationSearch } from 'tuikit-atomicx-react';
import type { SearchProps } from 'tuikit-atomicx-react';

function CustomSearch(props: SearchProps) {
  const {
    SearchBar,
    SearchResults,
    onKeywordChange,
    variant,
    className,
    style,
    ...restProps
  } = props;

  return (
    <div 
      className={`custom-search ${className || ''}`}
      style={{
        border: '2px solid #e0e0e0',
        borderRadius: '12px',
        padding: '16px',
        backgroundColor: '#fafafa',
        ...style
      }}
    >
      {/* 自定义搜索标题 */}
      <div style={{
        marginBottom: '12px',
        fontSize: '16px',
        fontWeight: '600',
        color: '#333'
      }}>
        🔍 智能搜索
      </div>
      
      {/* 搜索输入框 */}
      <SearchBar
        placeholder="搜索用户、群组或消息..."
        style={{
          borderRadius: '8px',
          border: '1px solid #d0d0d0'
        }}
      />
      
      {/* 搜索结果 */}
      <SearchResults
        style={{
          marginTop: '12px',
          maxHeight: '400px',
          overflowY: 'auto'
        }}
        {...restProps}
      />
    </div>
  );
}

function ConversationWithCustomSearch() {
  return (
    <ConversationSearch
      Search={CustomSearch}
      onKeywordChange={(keyword) => {
        console.log('搜索关键词:', keyword);
      }}
    />
  );
}
```

#### Example2: 带快捷搜索的自定义组件

```tsx
import { ConversationSearch } from 'tuikit-atomicx-react';
import type { SearchProps } from 'tuikit-atomicx-react';
import { useState } from 'react';

function QuickSearchComponent(props: SearchProps) {
  const [quickSearchVisible, setQuickSearchVisible] = useState(true);
  const [currentKeyword, setCurrentKeyword] = useState('');

  const quickSearchItems = [
    { label: '最近联系人', icon: '👥', action: () => handleQuickSearch('recent') },
    { label: '群聊', icon: '👨‍👩‍👧‍👦', action: () => handleQuickSearch('groups') },
    { label: '未读消息', icon: '💬', action: () => handleQuickSearch('unread') },
    { label: '文件传输', icon: '📁', action: () => handleQuickSearch('files') }
  ];

  const handleQuickSearch = (type: string) => {
    const keywords = {
      recent: '',
      groups: '@群聊',
      unread: '@未读',
      files: '@文件'
    };
    
    const keyword = keywords[type as keyof typeof keywords] || '';
    setCurrentKeyword(keyword);
    props.onKeywordChange?.(keyword);
    setQuickSearchVisible(false);
  };

  return (
    <div className="quick-search-container">
      {/* 搜索输入框 */}
      <props.SearchBar
        value={currentKeyword}
        onChange={(e) => {
          const value = e.target.value;
          setCurrentKeyword(value);
          props.onKeywordChange?.(value);
          setQuickSearchVisible(value === '');
        }}
        placeholder="输入关键词或选择快捷搜索..."
      />
      
      {/* 快捷搜索选项 */}
      {quickSearchVisible && (
        <div style={{
          display: 'grid',
          gridTemplateColumns: 'repeat(2, 1fr)',
          gap: '8px',
          marginTop: '12px',
          padding: '12px',
          backgroundColor: '#f8f9fa',
          borderRadius: '8px'
        }}>
          {quickSearchItems.map((item, index) => (
            <button
              key={index}
              onClick={item.action}
              style={{
                display: 'flex',
                alignItems: 'center',
                gap: '8px',
                padding: '12px',
                border: '1px solid #e0e0e0',
                borderRadius: '6px',
                backgroundColor: 'white',
                cursor: 'pointer',
                fontSize: '14px',
                transition: 'all 0.2s ease'
              }}
              onMouseEnter={(e) => {
                e.currentTarget.style.backgroundColor = '#e3f2fd';
                e.currentTarget.style.borderColor = '#2196F3';
              }}
              onMouseLeave={(e) => {
                e.currentTarget.style.backgroundColor = 'white';
                e.currentTarget.style.borderColor = '#e0e0e0';
              }}
            >
              <span style={{ fontSize: '16px' }}>{item.icon}</span>
              <span>{item.label}</span>
            </button>
          ))}
        </div>
      )}
      
      {/* 搜索结果 */}
      {currentKeyword && (
        <props.SearchResults
          {...props}
          style={{ marginTop: '12px' }}
        />
      )}
    </div>
  );
}

function ConversationWithQuickSearch() {
  return (
    <ConversationSearch
      Search={QuickSearchComponent}
      onKeywordChange={(keyword) => {
        console.log('快捷搜索:', keyword);
      }}
    />
  );
}
```

### onKeywordChange

type: `(keyword: string) => void`

onKeywordChange 用于监听搜索关键词的变化，在用户输入搜索内容时触发，默认值为 `-`。

#### Example1: 搜索历史记录

```tsx
import { ConversationSearch } from 'tuikit-atomicx-react';
import { useState, useEffect } from 'react';

function ConversationWithSearchHistory() {
  const [searchHistory, setSearchHistory] = useState<string[]>([]);
  const [currentKeyword, setCurrentKeyword] = useState('');

  // 从本地存储加载搜索历史
  useEffect(() => {
    const savedHistory = localStorage.getItem('conversation_search_history');
    if (savedHistory) {
      setSearchHistory(JSON.parse(savedHistory));
    }
  }, []);

  const handleKeywordChange = (keyword: string) => {
    setCurrentKeyword(keyword);
    
    // 记录搜索关键词（去重并限制数量）
    if (keyword.trim() && keyword.length >= 2) {
      setSearchHistory(prev => {
        const newHistory = [keyword, ...prev.filter(item => item !== keyword)].slice(0, 10);
        localStorage.setItem('conversation_search_history', JSON.stringify(newHistory));
        return newHistory;
      });
    }
    
    // 搜索行为统计
    analytics.track('conversation_search', {
      keyword: keyword,
      keywordLength: keyword.length,
      searchTime: new Date().toISOString()
    });
  };

  const clearSearchHistory = () => {
    setSearchHistory([]);
    localStorage.removeItem('conversation_search_history');
  };

  return (
    <div>
      <ConversationSearch
        onKeywordChange={handleKeywordChange}
        SearchResultsPresearch={() => (
          <div style={{ padding: '16px' }}>
            {searchHistory.length > 0 && (
              <div>
                <div style={{
                  display: 'flex',
                  justifyContent: 'space-between',
                  alignItems: 'center',
                  marginBottom: '12px'
                }}>
                  <h4 style={{ margin: 0, color: '#666', fontSize: '14px' }}>
                    搜索历史
                  </h4>
                  <button
                    onClick={clearSearchHistory}
                    style={{
                      background: 'none',
                      border: 'none',
                      color: '#999',
                      fontSize: '12px',
                      cursor: 'pointer'
                    }}
                  >
                    清除
                  </button>
                </div>
                
                <div style={{ display: 'flex', flexWrap: 'wrap', gap: '8px' }}>
                  {searchHistory.map((item, index) => (
                    <button
                      key={index}
                      onClick={() => handleKeywordChange(item)}
                      style={{
                        padding: '4px 8px',
                        backgroundColor: '#f0f0f0',
                        border: '1px solid #e0e0e0',
                        borderRadius: '12px',
                        fontSize: '12px',
                        cursor: 'pointer'
                      }}
                    >
                      {item}
                    </button>
                  ))}
                </div>
              </div>
            )}
            
            {/* 搜索提示 */}
            <div style={{ marginTop: '16px', fontSize: '12px', color: '#999' }}>
              💡 提示：输入用户名、群名或消息内容进行搜索
            </div>
          </div>
        )}
      />
      
      {/* 当前搜索信息 */}
      {currentKeyword && (
        <div style={{
          position: 'fixed',
          bottom: '20px',
          right: '20px',
          padding: '8px 12px',
          backgroundColor: '#333',
          color: 'white',
          borderRadius: '4px',
          fontSize: '12px'
        }}>
          正在搜索: {currentKeyword}
        </div>
      )}
    </div>
  );
}
```

#### Example2: 智能搜索建议

```tsx
import { ConversationSearch } from 'tuikit-atomicx-react';
import { useState, useEffect, useMemo } from 'react';

function ConversationWithSmartSuggestions() {
  const [searchSuggestions, setSearchSuggestions] = useState<string[]>([]);
  const [recentContacts, setRecentContacts] = useState<string[]>([]);
  const [popularKeywords, setPopularKeywords] = useState<string[]>([]);

  // 模拟获取搜索建议数据
  useEffect(() => {
    // 获取最近联系人
    const contacts = ['张三', '李四', '项目讨论群', '技术交流群'];
    setRecentContacts(contacts);
    
    // 获取热门搜索词
    const keywords = ['会议纪要', '项目文档', '工作安排', '技术分享'];
    setPopularKeywords(keywords);
  }, []);

  const handleKeywordChange = (keyword: string) => {
    // 生成智能搜索建议
    if (keyword.trim()) {
      const suggestions = [
        ...recentContacts.filter(contact => 
          contact.toLowerCase().includes(keyword.toLowerCase())
        ),
        ...popularKeywords.filter(kw => 
          kw.toLowerCase().includes(keyword.toLowerCase())
        )
      ].slice(0, 5);
      
      setSearchSuggestions(suggestions);
    } else {
      setSearchSuggestions([]);
    }
    
    // 发送搜索建议请求
    if (keyword.length >= 2) {
      fetchSearchSuggestions(keyword).then(suggestions => {
        setSearchSuggestions(prev => [...new Set([...prev, ...suggestions])]);
      });
    }
  };

  const SearchBarWithSuggestions = useMemo(() => {
    return function CustomSearchBar(props: any) {
      const [showSuggestions, setShowSuggestions] = useState(false);
      
      return (
        <div style={{ position: 'relative' }}>
          <input
            {...props}
            onFocus={() => setShowSuggestions(true)}
            onBlur={() => setTimeout(() => setShowSuggestions(false), 200)}
            style={{
              width: '100%',
              padding: '12px 16px',
              border: '1px solid #e0e0e0',
              borderRadius: '8px',
              fontSize: '14px'
            }}
          />
          
          {/* 搜索建议下拉框 */}
          {showSuggestions && searchSuggestions.length > 0 && (
            <div style={{
              position: 'absolute',
              top: '100%',
              left: 0,
              right: 0,
              backgroundColor: 'white',
              border: '1px solid #e0e0e0',
              borderTop: 'none',
              borderRadius: '0 0 8px 8px',
              boxShadow: '0 2px 8px rgba(0,0,0,0.1)',
              zIndex: 1000
            }}>
              {searchSuggestions.map((suggestion, index) => (
                <div
                  key={index}
                  style={{
                    padding: '8px 16px',
                    cursor: 'pointer',
                    fontSize: '14px',
                    borderBottom: index < searchSuggestions.length - 1 ? '1px solid #f0f0f0' : 'none'
                  }}
                  onClick={() => {
                    props.onChange?.({ target: { value: suggestion } });
                    setShowSuggestions(false);
                  }}
                  onMouseEnter={(e) => {
                    e.currentTarget.style.backgroundColor = '#f8f9fa';
                  }}
                  onMouseLeave={(e) => {
                    e.currentTarget.style.backgroundColor = 'transparent';
                  }}
                >
                  🔍 {suggestion}
                </div>
              ))}
            </div>
          )}
        </div>
      );
    };
  }, [searchSuggestions]);

  return (
    <ConversationSearch
      SearchBar={SearchBarWithSuggestions}
      onKeywordChange={handleKeywordChange}
    />
  );
}

// 模拟获取搜索建议的API
async function fetchSearchSuggestions(keyword: string): Promise<string[]> {
  // 模拟API调用延迟
  await new Promise(resolve => setTimeout(resolve, 300));
  
  // 返回模拟的搜索建议
  const suggestions = [
    `${keyword}相关用户`,
    `${keyword}相关群组`,
    `${keyword}相关消息`
  ];
  
  return suggestions;
}
```

### onResultItemClick

type: `(data: SearchResultItemType, type: SearchType) => void`

onResultItemClick 用于处理搜索结果项的点击事件，可以自定义点击后的行为，默认值为 `-`。

#### Example1: 自定义结果处理

```tsx
import { ConversationSearch } from 'tuikit-atomicx-react';
import type { SearchResultItemType, SearchType } from 'tuikit-atomicx-react';

function ConversationWithCustomResultHandling() {
  const handleResultItemClick = (data: SearchResultItemType, type: SearchType) => {
    console.log('点击搜索结果:', { data, type });
    
    switch (type) {
      case 'USER':
        // 处理用户点击
        const userItem = data as SearchCloudUsersResultItem;
        showUserProfile(userItem.profile);
        break;
        
      case 'GROUP':
        // 处理群组点击
        const groupItem = data as SearchCloudGroupsResultItem;
        if (groupItem.conversation) {
          joinAndEnterGroup(groupItem.conversation);
        } else {
          showJoinGroupDialog(groupItem);
        }
        break;
        
      case 'MESSAGE':
      case 'CHAT_MESSAGE':
        // 处理消息点击
        const messageItem = data as SearchCloudMessagesResultItem;
        jumpToMessage(messageItem);
        break;
        
      default:
        console.warn('未知的搜索结果类型:', type);
    }
  };

  const showUserProfile = (profile: any) => {
    // 显示用户资料弹窗
    Modal.show({
      title: '用户资料',
      content: (
        <div>
          <img src={profile.avatar} alt={profile.nick} />
          <h3>{profile.nick || profile.userID}</h3>
          <p>用户ID: {profile.userID}</p>
        </div>
      ),
      actions: [
        { text: '发送消息', onClick: () => startChat(profile.userID) },
        { text: '关闭', onClick: () => Modal.hide() }
      ]
    });
  };

  const joinAndEnterGroup = (conversation: any) => {
    // 加入并进入群组
    TUIConversationService.switchConversation(conversation.conversationID);
    showSuccessToast(`已进入群组: ${conversation.groupProfile?.name}`);
  };

  const jumpToMessage = (messageItem: any) => {
    // 跳转到具体消息
    const conversationID = messageItem.conversation?.conversationID || messageItem.conversationID;
    if (conversationID) {
      TUIConversationService.switchConversation(conversationID);
      // 定位到具体消息
      TUIMessageService.scrollToMessage(messageItem.ID);
    }
  };

  return (
    <ConversationSearch
      onResultItemClick={handleResultItemClick}
    />
  );
}
```

#### Example2: 权限控制和统计

```tsx
import { ConversationSearch } from 'tuikit-atomicx-react';
import type { SearchResultItemType, SearchType } from 'tuikit-atomicx-react';

function ConversationWithPermissionControl() {
  const handleResultItemClick = async (data: SearchResultItemType, type: SearchType) => {
    try {
      // 权限检查
      const hasPermission = await checkAccessPermission(data, type);
      if (!hasPermission) {
        showErrorToast('您没有权限访问此内容');
        return;
      }
      
      // 记录用户行为
      analytics.track('search_result_click', {
        resultType: type,
        resultId: getResultId(data, type),
        timestamp: new Date().toISOString()
      });
      
      // 根据类型处理点击
      switch (type) {
        case 'USER':
          await handleUserClick(data as SearchCloudUsersResultItem);
          break;
        case 'GROUP':
          await handleGroupClick(data as SearchCloudGroupsResultItem);
          break;
        case 'MESSAGE':
        case 'CHAT_MESSAGE':
          await handleMessageClick(data as SearchCloudMessagesResultItem);
          break;
      }
      
    } catch (error) {
      console.error('处理搜索结果点击失败:', error);
      showErrorToast('操作失败，请重试');
    }
  };

  const checkAccessPermission = async (data: SearchResultItemType, type: SearchType): Promise<boolean> => {
    // 根据类型检查权限
    switch (type) {
      case 'USER':
        return await checkUserAccessPermission((data as SearchCloudUsersResultItem).profile.userID);
      case 'GROUP':
        return await checkGroupAccessPermission((data as SearchCloudGroupsResultItem).groupID);
      case 'MESSAGE':
      case 'CHAT_MESSAGE':
        return await checkMessageAccessPermission((data as SearchCloudMessagesResultItem).conversationID);
      default:
        return true;
    }
  };

  const handleUserClick = async (userItem: SearchCloudUsersResultItem) => {
    const { profile } = userItem;
    
    // 检查是否已是好友
    const isFriend = await checkFriendship(profile.userID);
    
    if (isFriend) {
      // 直接开始聊天
      TUIConversationService.switchConversation(`C2C${profile.userID}`);
    } else {
      // 显示添加好友对话框
      showAddFriendDialog(profile);
    }
  };

  const handleGroupClick = async (groupItem: SearchCloudGroupsResultItem) => {
    const { conversation, groupProfile } = groupItem;
    
    if (conversation) {
      // 已加入群组，直接进入
      TUIConversationService.switchConversation(conversation.conversationID);
    } else {
      // 未加入群组，显示群组信息并提供加入选项
      showGroupInfoDialog(groupProfile);
    }
  };

  const handleMessageClick = async (messageItem: SearchCloudMessagesResultItem) => {
    const { conversation } = messageItem;
    
    // 进入会话
    TUIConversationService.switchConversation(conversation.conversationID);
    
    // 定位到消息（如果有消息ID）
    if (messageItem.ID) {
      setTimeout(() => {
        TUIMessageService.scrollToMessage(messageItem.ID);
      }, 500);
    }
  };

  return (
    <ConversationSearch
      onResultItemClick={handleResultItemClick}
      onError={(error) => {
        console.error('搜索错误:', error);
        showErrorToast('搜索失败，请重试');
      }}
    />
  );
}

// 辅助函数
function getResultId(data: SearchResultItemType, type: SearchType): string {
  switch (type) {
    case 'USER':
      return (data as SearchCloudUsersResultItem).profile.userID;
    case 'GROUP':
      return (data as SearchCloudGroupsResultItem).groupID;
    case 'MESSAGE':
    case 'CHAT_MESSAGE':
      return (data as SearchCloudMessagesResultItem).ID || '';
    default:
      return '';
  }
}
```

### className

type: `string`

className 用于自定义组件的样式类名，默认值为 `-`。

### style

type: `React.CSSProperties`

style 用于自定义组件的内联样式，默认值为 `-`。 