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:
@@ -1,9 +1,7 @@
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import React, { useState, useEffect, useCallback, useMemo } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import { loadAllStocks } from '@store/slices/stockSlice';
|
||||
import { AutoComplete } from 'antd';
|
||||
import { stockService } from '@services/stockService';
|
||||
import { AutoComplete, Spin } from 'antd';
|
||||
import { useStockSearch } from '@hooks/useStockSearch';
|
||||
import {
|
||||
Container,
|
||||
Heading,
|
||||
@@ -42,22 +40,10 @@ const CompanyIndex = () => {
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const [stockCode, setStockCode] = useState(searchParams.get('scode') || '000001');
|
||||
const [inputCode, setInputCode] = useState(stockCode);
|
||||
const [stockOptions, setStockOptions] = useState([]);
|
||||
const { colorMode, toggleColorMode } = useColorMode();
|
||||
const toast = useToast();
|
||||
const { isAuthenticated } = useAuth();
|
||||
|
||||
// 从 Redux 获取股票列表数据
|
||||
const dispatch = useDispatch();
|
||||
const allStocks = useSelector((state) => state.stock.allStocks);
|
||||
|
||||
// 确保股票数据已加载
|
||||
useEffect(() => {
|
||||
if (!allStocks || allStocks.length === 0) {
|
||||
dispatch(loadAllStocks());
|
||||
}
|
||||
}, [dispatch, allStocks]);
|
||||
|
||||
// 🎯 PostHog 事件追踪
|
||||
const {
|
||||
trackStockSearched,
|
||||
@@ -66,6 +52,35 @@ const CompanyIndex = () => {
|
||||
trackWatchlistRemoved,
|
||||
} = useCompanyEvents({ stockCode });
|
||||
|
||||
// 🔍 股票搜索 Hook(支持代码、名称、拼音缩写)
|
||||
const {
|
||||
searchResults,
|
||||
isSearching,
|
||||
handleSearch: doSearch,
|
||||
clearSearch,
|
||||
} = useStockSearch({
|
||||
limit: 10,
|
||||
debounceMs: 300,
|
||||
onSearch: (query, _count) => trackStockSearched(query, stockCode),
|
||||
});
|
||||
|
||||
// 转换为 AutoComplete options
|
||||
const stockOptions = useMemo(() => {
|
||||
return searchResults.map((stock) => ({
|
||||
value: stock.stock_code,
|
||||
label: (
|
||||
<span>
|
||||
<strong>{stock.stock_code}</strong> {stock.stock_name}
|
||||
{stock.pinyin_abbr && (
|
||||
<span style={{ color: '#999', marginLeft: 8 }}>
|
||||
({stock.pinyin_abbr.toUpperCase()})
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
),
|
||||
}));
|
||||
}, [searchResults]);
|
||||
|
||||
// Tab 索引状态(用于追踪 Tab 切换)
|
||||
const [currentTabIndex, setCurrentTabIndex] = useState(0);
|
||||
|
||||
@@ -119,31 +134,11 @@ const CompanyIndex = () => {
|
||||
setSearchParams({ scode: inputCode });
|
||||
}
|
||||
};
|
||||
|
||||
const handleKeyPress = (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
handleSearch();
|
||||
}
|
||||
};
|
||||
|
||||
// 模糊搜索股票(由 onSearch 触发)
|
||||
const handleStockSearch = (value) => {
|
||||
if (!value || !allStocks || allStocks.length === 0) {
|
||||
setStockOptions([]);
|
||||
return;
|
||||
}
|
||||
const results = stockService.fuzzySearch(value, allStocks, 10);
|
||||
const options = results.map((stock) => ({
|
||||
value: stock.code,
|
||||
label: `${stock.code} ${stock.name}`,
|
||||
}));
|
||||
setStockOptions(options);
|
||||
};
|
||||
|
||||
// 选中股票
|
||||
const handleStockSelect = (value) => {
|
||||
setInputCode(value);
|
||||
setStockOptions([]);
|
||||
clearSearch();
|
||||
if (value !== stockCode) {
|
||||
trackStockSearched(value, stockCode);
|
||||
setStockCode(value);
|
||||
@@ -231,12 +226,13 @@ const CompanyIndex = () => {
|
||||
<AutoComplete
|
||||
value={inputCode}
|
||||
options={stockOptions}
|
||||
onSearch={handleStockSearch}
|
||||
onSearch={doSearch}
|
||||
onSelect={handleStockSelect}
|
||||
onChange={(value) => setInputCode(value)}
|
||||
placeholder="输入股票代码或名称"
|
||||
style={{ width: 260 }}
|
||||
placeholder="输入代码、名称或拼音缩写"
|
||||
style={{ width: 280 }}
|
||||
size="large"
|
||||
notFoundContent={isSearching ? <Spin size="small" /> : null}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
handleSearch();
|
||||
|
||||
Reference in New Issue
Block a user