'use client' import { useState, useRef, useEffect } from 'react' import { pinyin } from 'pinyin-pro' interface User { id: string username: string displayName: string } interface SearchableUserSelectProps { users: User[] value: string onChange: (userId: string, displayName: string) => void placeholder?: string required?: boolean className?: string } export default function SearchableUserSelect({ users, value, onChange, placeholder = '请选择或搜索用户', required = false, className = '', }: SearchableUserSelectProps) { const [searchTerm, setSearchTerm] = useState('') const [isOpen, setIsOpen] = useState(false) const [highlightedIndex, setHighlightedIndex] = useState(0) const containerRef = useRef(null) const inputRef = useRef(null) // 获取选中用户的显示名称 const selectedUser = users.find(u => u.id === value) const displayValue = selectedUser ? `${selectedUser.displayName} (${selectedUser.username})` : '' // 拼音搜索匹配函数 const matchesSearch = (user: User, search: string): boolean => { if (!search) return true const searchLower = search.toLowerCase() // 1. 匹配显示名称 if (user.displayName.toLowerCase().includes(searchLower)) { return true } // 2. 匹配用户名 if (user.username.toLowerCase().includes(searchLower)) { return true } // 3. 匹配拼音全拼 const fullPinyin = pinyin(user.displayName, { toneType: 'none', type: 'array' }).join('').toLowerCase() if (fullPinyin.includes(searchLower)) { return true } // 4. 匹配拼音首字母 const firstLetters = pinyin(user.displayName, { pattern: 'first', toneType: 'none', type: 'array' }).join('').toLowerCase() if (firstLetters.includes(searchLower)) { return true } return false } // 过滤用户列表 const filteredUsers = users.filter(user => matchesSearch(user, searchTerm)) // 点击外部关闭下拉框 useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if (containerRef.current && !containerRef.current.contains(event.target as Node)) { setIsOpen(false) } } document.addEventListener('mousedown', handleClickOutside) return () => document.removeEventListener('mousedown', handleClickOutside) }, []) // 键盘导航 const handleKeyDown = (e: React.KeyboardEvent) => { if (!isOpen) { if (e.key === 'Enter' || e.key === 'ArrowDown') { e.preventDefault() setIsOpen(true) } return } switch (e.key) { case 'ArrowDown': e.preventDefault() setHighlightedIndex(prev => prev < filteredUsers.length - 1 ? prev + 1 : prev ) break case 'ArrowUp': e.preventDefault() setHighlightedIndex(prev => prev > 0 ? prev - 1 : 0) break case 'Enter': e.preventDefault() if (filteredUsers[highlightedIndex]) { handleSelect(filteredUsers[highlightedIndex]) } break case 'Escape': e.preventDefault() setIsOpen(false) break } } // 选择用户 const handleSelect = (user: User) => { onChange(user.id, user.displayName) setSearchTerm('') setIsOpen(false) setHighlightedIndex(0) } // 打开下拉框 const handleFocus = () => { setIsOpen(true) setHighlightedIndex(0) } // 清空选择 const handleClear = (e: React.MouseEvent) => { e.stopPropagation() onChange('', '') setSearchTerm('') setIsOpen(false) inputRef.current?.focus() } return (
setSearchTerm(e.target.value)} onFocus={handleFocus} onKeyDown={handleKeyDown} placeholder={placeholder} required={required} className="input-apple pr-20" autoComplete="off" />
{value && ( )}
{isOpen && (
{filteredUsers.length === 0 ? (
未找到匹配的用户
) : ( filteredUsers.map((user, index) => ( )) )}
)}
) }