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:
99
src/hooks/useStockSearch.js
Normal file
99
src/hooks/useStockSearch.js
Normal 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;
|
||||
Reference in New Issue
Block a user