refactor(Company): 提取共享的 useStockSearch Hook
- 新增 useStockSearch.ts:统一股票模糊搜索逻辑 - 支持按代码或名称搜索 - 支持排除指定股票(用于对比场景) - 使用 useMemo 优化性能 - 重构 SearchBar.js:使用共享 Hook,减少 15 行代码 - 重构 CompareStockInput.tsx:使用共享 Hook,减少 20 行代码 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -13,6 +13,7 @@ import {
|
|||||||
VStack,
|
VStack,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { SearchIcon } from '@chakra-ui/icons';
|
import { SearchIcon } from '@chakra-ui/icons';
|
||||||
|
import { useStockSearch } from '../../hooks/useStockSearch';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 股票搜索栏组件(带模糊搜索下拉)
|
* 股票搜索栏组件(带模糊搜索下拉)
|
||||||
@@ -31,27 +32,18 @@ const SearchBar = ({
|
|||||||
}) => {
|
}) => {
|
||||||
// 下拉状态
|
// 下拉状态
|
||||||
const [showDropdown, setShowDropdown] = useState(false);
|
const [showDropdown, setShowDropdown] = useState(false);
|
||||||
const [filteredStocks, setFilteredStocks] = useState([]);
|
|
||||||
const containerRef = useRef(null);
|
const containerRef = useRef(null);
|
||||||
|
|
||||||
// 从 Redux 获取全部股票列表
|
// 从 Redux 获取全部股票列表
|
||||||
const allStocks = useSelector(state => state.stock.allStocks);
|
const allStocks = useSelector(state => state.stock.allStocks);
|
||||||
|
|
||||||
// 模糊搜索过滤
|
// 使用共享的搜索 Hook
|
||||||
|
const filteredStocks = useStockSearch(allStocks, inputCode, { limit: 10 });
|
||||||
|
|
||||||
|
// 根据搜索结果更新下拉显示状态
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (inputCode && inputCode.trim()) {
|
setShowDropdown(filteredStocks.length > 0 && !!inputCode?.trim());
|
||||||
const searchTerm = inputCode.trim().toLowerCase();
|
}, [filteredStocks, inputCode]);
|
||||||
const filtered = allStocks.filter(stock =>
|
|
||||||
stock.code.toLowerCase().includes(searchTerm) ||
|
|
||||||
stock.name.includes(inputCode.trim())
|
|
||||||
).slice(0, 10); // 限制显示10条
|
|
||||||
setFilteredStocks(filtered);
|
|
||||||
setShowDropdown(filtered.length > 0);
|
|
||||||
} else {
|
|
||||||
setFilteredStocks([]);
|
|
||||||
setShowDropdown(false);
|
|
||||||
}
|
|
||||||
}, [inputCode, allStocks]);
|
|
||||||
|
|
||||||
// 点击外部关闭下拉
|
// 点击外部关闭下拉
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import {
|
|||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { SearchIcon } from '@chakra-ui/icons';
|
import { SearchIcon } from '@chakra-ui/icons';
|
||||||
import { BarChart2 } from 'lucide-react';
|
import { BarChart2 } from 'lucide-react';
|
||||||
|
import { useStockSearch, type Stock } from '../../../hooks/useStockSearch';
|
||||||
|
|
||||||
interface CompareStockInputProps {
|
interface CompareStockInputProps {
|
||||||
onCompare: (stockCode: string) => void;
|
onCompare: (stockCode: string) => void;
|
||||||
@@ -25,11 +26,6 @@ interface CompareStockInputProps {
|
|||||||
currentStockCode?: string;
|
currentStockCode?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Stock {
|
|
||||||
code: string;
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface RootState {
|
interface RootState {
|
||||||
stock: {
|
stock: {
|
||||||
allStocks: Stock[];
|
allStocks: Stock[];
|
||||||
@@ -43,7 +39,6 @@ const CompareStockInput: React.FC<CompareStockInputProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const [inputValue, setInputValue] = useState('');
|
const [inputValue, setInputValue] = useState('');
|
||||||
const [showDropdown, setShowDropdown] = useState(false);
|
const [showDropdown, setShowDropdown] = useState(false);
|
||||||
const [filteredStocks, setFilteredStocks] = useState<Stock[]>([]);
|
|
||||||
const [selectedStock, setSelectedStock] = useState<Stock | null>(null);
|
const [selectedStock, setSelectedStock] = useState<Stock | null>(null);
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
@@ -55,25 +50,16 @@ const CompareStockInput: React.FC<CompareStockInputProps> = ({
|
|||||||
const goldColor = '#F4D03F';
|
const goldColor = '#F4D03F';
|
||||||
const bgColor = '#1A202C';
|
const bgColor = '#1A202C';
|
||||||
|
|
||||||
// 模糊搜索过滤
|
// 使用共享的搜索 Hook(排除当前股票)
|
||||||
|
const filteredStocks = useStockSearch(allStocks, inputValue, {
|
||||||
|
excludeCode: currentStockCode,
|
||||||
|
limit: 8,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 根据搜索结果更新下拉显示状态
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (inputValue && inputValue.trim()) {
|
setShowDropdown(filteredStocks.length > 0 && !!inputValue?.trim());
|
||||||
const searchTerm = inputValue.trim().toLowerCase();
|
}, [filteredStocks, inputValue]);
|
||||||
const filtered = allStocks
|
|
||||||
.filter(
|
|
||||||
(stock) =>
|
|
||||||
stock.code !== currentStockCode && // 排除当前股票
|
|
||||||
(stock.code.toLowerCase().includes(searchTerm) ||
|
|
||||||
stock.name.includes(inputValue.trim()))
|
|
||||||
)
|
|
||||||
.slice(0, 8); // 限制显示8条
|
|
||||||
setFilteredStocks(filtered);
|
|
||||||
setShowDropdown(filtered.length > 0);
|
|
||||||
} else {
|
|
||||||
setFilteredStocks([]);
|
|
||||||
setShowDropdown(false);
|
|
||||||
}
|
|
||||||
}, [inputValue, allStocks, currentStockCode]);
|
|
||||||
|
|
||||||
// 点击外部关闭下拉
|
// 点击外部关闭下拉
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
59
src/views/Company/hooks/useStockSearch.ts
Normal file
59
src/views/Company/hooks/useStockSearch.ts
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
/**
|
||||||
|
* useStockSearch - 股票模糊搜索 Hook
|
||||||
|
*
|
||||||
|
* 提取自 SearchBar.js 和 CompareStockInput.tsx 的共享搜索逻辑
|
||||||
|
* 支持按代码或名称搜索,可选排除指定股票
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
|
export interface Stock {
|
||||||
|
code: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UseStockSearchOptions {
|
||||||
|
excludeCode?: string;
|
||||||
|
limit?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 股票模糊搜索 Hook
|
||||||
|
*
|
||||||
|
* @param allStocks - 全部股票列表
|
||||||
|
* @param searchTerm - 搜索关键词
|
||||||
|
* @param options - 可选配置
|
||||||
|
* @param options.excludeCode - 排除的股票代码(用于对比场景)
|
||||||
|
* @param options.limit - 返回结果数量限制,默认 10
|
||||||
|
* @returns 过滤后的股票列表
|
||||||
|
*/
|
||||||
|
export const useStockSearch = (
|
||||||
|
allStocks: Stock[],
|
||||||
|
searchTerm: string,
|
||||||
|
options: UseStockSearchOptions = {}
|
||||||
|
): Stock[] => {
|
||||||
|
const { excludeCode, limit = 10 } = options;
|
||||||
|
|
||||||
|
return useMemo(() => {
|
||||||
|
const trimmed = searchTerm?.trim();
|
||||||
|
if (!trimmed) return [];
|
||||||
|
|
||||||
|
const term = trimmed.toLowerCase();
|
||||||
|
|
||||||
|
return allStocks
|
||||||
|
.filter((stock) => {
|
||||||
|
// 排除指定股票
|
||||||
|
if (excludeCode && stock.code === excludeCode) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// 按代码或名称匹配
|
||||||
|
return (
|
||||||
|
stock.code.toLowerCase().includes(term) ||
|
||||||
|
stock.name.includes(trimmed)
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.slice(0, limit);
|
||||||
|
}, [allStocks, searchTerm, excludeCode, limit]);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useStockSearch;
|
||||||
Reference in New Issue
Block a user