diff --git a/src/components/Navbars/SearchBar/SearchBar.js b/src/components/Navbars/SearchBar/SearchBar.js index a9b01329..332e50a6 100755 --- a/src/components/Navbars/SearchBar/SearchBar.js +++ b/src/components/Navbars/SearchBar/SearchBar.js @@ -1,45 +1,188 @@ -import React from "react"; +// src/components/Navbars/SearchBar/SearchBar.js +// 全局股票搜索栏 - 模糊搜索 + 下拉选择 + +import React, { useRef, useEffect, useCallback } from "react"; +import { useNavigate } from "react-router-dom"; import { - IconButton, + Box, Input, InputGroup, InputLeftElement, + InputRightElement, + IconButton, + Text, + VStack, + HStack, + Spinner, useColorModeValue, + Tag, + Center, + List, + ListItem, + Flex, } from "@chakra-ui/react"; -import { SearchIcon } from "@chakra-ui/icons"; +import { SearchIcon, CloseIcon } from "@chakra-ui/icons"; +import { useStockSearch } from "@hooks/useStockSearch"; + export function SearchBar(props) { - // Pass the computed styles into the `__css` prop const { variant, children, ...rest } = props; - // Chakra Color Mode - const searchIconColor = useColorModeValue("gray.700", "gray.200"); - const inputBg = useColorModeValue("white", "navy.800"); + const navigate = useNavigate(); + const containerRef = useRef(null); + + // 颜色配置 + const searchIconColor = useColorModeValue("gray.500", "gray.400"); + const inputBg = useColorModeValue("white", "whiteAlpha.100"); + const dropdownBg = useColorModeValue("white", "#1a1a2e"); + const borderColor = useColorModeValue("gray.200", "whiteAlpha.200"); + const hoverBg = useColorModeValue("gray.50", "whiteAlpha.100"); + const textColor = useColorModeValue("gray.800", "white"); + const subTextColor = useColorModeValue("gray.500", "whiteAlpha.600"); + const accentColor = useColorModeValue("blue.500", "#D4AF37"); + + // 使用搜索 Hook + const { + searchQuery, + searchResults, + isSearching, + showResults, + handleSearch, + clearSearch, + setShowResults, + } = useStockSearch({ limit: 10, debounceMs: 300 }); + + // 点击外部关闭下拉 + useEffect(() => { + const handleClickOutside = (event) => { + if (containerRef.current && !containerRef.current.contains(event.target)) { + setShowResults(false); + } + }; + document.addEventListener("mousedown", handleClickOutside); + return () => document.removeEventListener("mousedown", handleClickOutside); + }, [setShowResults]); + + // 选择股票 - 跳转到详情页 + const handleSelectStock = useCallback((stock) => { + clearSearch(); + // 跳转到股票详情页 + navigate(`/company/${stock.stock_code}`); + }, [navigate, clearSearch]); + + // 处理键盘事件 + const handleKeyDown = useCallback((e) => { + if (e.key === "Enter" && searchResults.length > 0) { + handleSelectStock(searchResults[0]); + } else if (e.key === "Escape") { + setShowResults(false); + } + }, [searchResults, handleSelectStock, setShowResults]); + return ( - - - }> - } - /> - - + + + + + + handleSearch(e.target.value)} + onKeyDown={handleKeyDown} + onFocus={() => searchQuery && searchResults.length > 0 && setShowResults(true)} + borderColor={borderColor} + _hover={{ borderColor: accentColor }} + _focus={{ borderColor: accentColor, boxShadow: `0 0 0 1px ${accentColor}` }} + /> + {(searchQuery || isSearching) && ( + + {isSearching ? ( + + ) : ( + } + onClick={clearSearch} + aria-label="清除搜索" + _hover={{ bg: "transparent" }} + /> + )} + + )} + + + {/* 搜索结果下拉 */} + {showResults && ( + + {searchResults.length > 0 ? ( + + {searchResults.map((stock, index) => ( + handleSelectStock(stock)} + borderBottomWidth={index < searchResults.length - 1 ? "1px" : "0"} + borderColor={borderColor} + > + + + + {stock.stock_name} + + + + {stock.stock_code} + + {stock.pinyin_abbr && ( + + ({stock.pinyin_abbr.toUpperCase()}) + + )} + + + {stock.exchange && ( + + {stock.exchange} + + )} + + + ))} + + ) : ( +
+ + {searchQuery ? "未找到相关股票" : "输入股票代码或名称搜索"} + +
+ )} +
+ )} +
); }