feat: Company 页面搜索框支持拼音缩写搜索

- 新增 useStockSearch Hook,提取通用股票搜索能力
  - 支持代码、名称、拼音缩写模糊搜索
  - 内置 300ms 防抖,避免频繁 API 调用
  - 使用 useRef 存储回调,防止防抖函数重建
- Company/index.js 使用新 Hook 替换本地搜索
  - 搜索结果显示拼音缩写 (如 GZMT)
  - 搜索框宽度调整为 280px
- Mock handler 添加拼音缩写支持
  - 新增 PINYIN_MAP 字符映射表
  - 搜索逻辑支持拼音匹配和排序

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
zdl
2025-12-10 15:45:30 +08:00
parent fbeb66fb39
commit b151400c65
3 changed files with 189 additions and 48 deletions

View File

@@ -0,0 +1,99 @@
// src/hooks/useStockSearch.js
// 通用股票搜索 Hook - 支持代码、名称、拼音缩写搜索
import { useState, useCallback, useMemo, useEffect, useRef } from 'react';
import debounce from 'lodash/debounce';
/**
* 股票搜索 Hook
* @param {Object} options 配置项
* @param {number} options.limit 返回结果数量限制,默认 10
* @param {number} options.debounceMs 防抖延迟,默认 300ms
* @param {Function} options.onSearch 搜索回调(用于追踪)
* @returns {Object} 搜索状态和方法
*/
export const useStockSearch = (options = {}) => {
const { limit = 10, debounceMs = 300, onSearch } = options;
// 使用 ref 存储 onSearch避免因回调变化导致防抖函数重建
const onSearchRef = useRef(onSearch);
useEffect(() => {
onSearchRef.current = onSearch;
}, [onSearch]);
const [searchQuery, setSearchQuery] = useState('');
const [searchResults, setSearchResults] = useState([]);
const [isSearching, setIsSearching] = useState(false);
const [showResults, setShowResults] = useState(false);
// 调用 API 搜索
const searchStocks = useCallback(async (query) => {
if (!query || !query.trim()) {
setSearchResults([]);
setShowResults(false);
return;
}
setIsSearching(true);
try {
const response = await fetch(
`/api/stocks/search?q=${encodeURIComponent(query.trim())}&limit=${limit}`
);
const data = await response.json();
if (data.success && data.data) {
setSearchResults(data.data);
setShowResults(true);
// 触发搜索回调(用于追踪)
onSearchRef.current?.(query, data.data.length);
} else {
setSearchResults([]);
setShowResults(true);
onSearchRef.current?.(query, 0);
}
} catch (error) {
console.error('搜索股票失败:', error);
setSearchResults([]);
} finally {
setIsSearching(false);
}
}, [limit]);
// 防抖搜索
const debouncedSearch = useMemo(
() => debounce(searchStocks, debounceMs),
[searchStocks, debounceMs]
);
// 处理搜索输入
const handleSearch = useCallback((value) => {
setSearchQuery(value);
debouncedSearch(value);
}, [debouncedSearch]);
// 清空搜索
const clearSearch = useCallback(() => {
setSearchQuery('');
setSearchResults([]);
setShowResults(false);
}, []);
// 清理防抖
useEffect(() => {
return () => debouncedSearch.cancel?.();
}, [debouncedSearch]);
return {
// 状态
searchQuery,
searchResults,
isSearching,
showResults,
// 方法
handleSearch,
clearSearch,
setShowResults,
};
};
export default useStockSearch;