更新Company页面的UI为FUI风格
This commit is contained in:
@@ -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 {
|
import {
|
||||||
IconButton,
|
Box,
|
||||||
Input,
|
Input,
|
||||||
InputGroup,
|
InputGroup,
|
||||||
InputLeftElement,
|
InputLeftElement,
|
||||||
|
InputRightElement,
|
||||||
|
IconButton,
|
||||||
|
Text,
|
||||||
|
VStack,
|
||||||
|
HStack,
|
||||||
|
Spinner,
|
||||||
useColorModeValue,
|
useColorModeValue,
|
||||||
|
Tag,
|
||||||
|
Center,
|
||||||
|
List,
|
||||||
|
ListItem,
|
||||||
|
Flex,
|
||||||
} from "@chakra-ui/react";
|
} 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) {
|
export function SearchBar(props) {
|
||||||
// Pass the computed styles into the `__css` prop
|
|
||||||
const { variant, children, ...rest } = props;
|
const { variant, children, ...rest } = props;
|
||||||
// Chakra Color Mode
|
const navigate = useNavigate();
|
||||||
const searchIconColor = useColorModeValue("gray.700", "gray.200");
|
const containerRef = useRef(null);
|
||||||
const inputBg = useColorModeValue("white", "navy.800");
|
|
||||||
|
// 颜色配置
|
||||||
|
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 (
|
return (
|
||||||
<InputGroup borderRadius='8px' w='200px' {...rest}>
|
<Box ref={containerRef} position="relative" {...rest}>
|
||||||
<InputLeftElement
|
<InputGroup borderRadius="8px" w="220px">
|
||||||
children={
|
<InputLeftElement pointerEvents="none">
|
||||||
<IconButton
|
<SearchIcon color={searchIconColor} w="15px" h="15px" />
|
||||||
bg='inherit'
|
</InputLeftElement>
|
||||||
borderRadius='inherit'
|
<Input
|
||||||
_hover={{}}
|
variant="search"
|
||||||
_active={{
|
fontSize="sm"
|
||||||
bg: "inherit",
|
bg={inputBg}
|
||||||
transform: "none",
|
placeholder="搜索股票..."
|
||||||
borderColor: "transparent",
|
value={searchQuery}
|
||||||
}}
|
onChange={(e) => handleSearch(e.target.value)}
|
||||||
_focus={{
|
onKeyDown={handleKeyDown}
|
||||||
boxShadow: "none",
|
onFocus={() => searchQuery && searchResults.length > 0 && setShowResults(true)}
|
||||||
}}
|
borderColor={borderColor}
|
||||||
icon={
|
_hover={{ borderColor: accentColor }}
|
||||||
<SearchIcon color={searchIconColor} w='15px' h='15px' />
|
_focus={{ borderColor: accentColor, boxShadow: `0 0 0 1px ${accentColor}` }}
|
||||||
}></IconButton>
|
/>
|
||||||
}
|
{(searchQuery || isSearching) && (
|
||||||
/>
|
<InputRightElement>
|
||||||
<Input
|
{isSearching ? (
|
||||||
variant='search'
|
<Spinner size="sm" color={accentColor} />
|
||||||
fontSize='xs'
|
) : (
|
||||||
bg={inputBg}
|
<IconButton
|
||||||
placeholder='Type here...'
|
size="xs"
|
||||||
/>
|
variant="ghost"
|
||||||
</InputGroup>
|
icon={<CloseIcon w="10px" h="10px" />}
|
||||||
|
onClick={clearSearch}
|
||||||
|
aria-label="清除搜索"
|
||||||
|
_hover={{ bg: "transparent" }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</InputRightElement>
|
||||||
|
)}
|
||||||
|
</InputGroup>
|
||||||
|
|
||||||
|
{/* 搜索结果下拉 */}
|
||||||
|
{showResults && (
|
||||||
|
<Box
|
||||||
|
position="absolute"
|
||||||
|
top="100%"
|
||||||
|
left={0}
|
||||||
|
mt={2}
|
||||||
|
w="320px"
|
||||||
|
bg={dropdownBg}
|
||||||
|
border="1px solid"
|
||||||
|
borderColor={borderColor}
|
||||||
|
borderRadius="md"
|
||||||
|
boxShadow="lg"
|
||||||
|
maxH="400px"
|
||||||
|
overflowY="auto"
|
||||||
|
zIndex={9999}
|
||||||
|
>
|
||||||
|
{searchResults.length > 0 ? (
|
||||||
|
<List spacing={0}>
|
||||||
|
{searchResults.map((stock, index) => (
|
||||||
|
<ListItem
|
||||||
|
key={stock.stock_code}
|
||||||
|
px={4}
|
||||||
|
py={3}
|
||||||
|
cursor="pointer"
|
||||||
|
_hover={{ bg: hoverBg }}
|
||||||
|
onClick={() => handleSelectStock(stock)}
|
||||||
|
borderBottomWidth={index < searchResults.length - 1 ? "1px" : "0"}
|
||||||
|
borderColor={borderColor}
|
||||||
|
>
|
||||||
|
<Flex align="center" justify="space-between">
|
||||||
|
<VStack align="start" spacing={0} flex={1}>
|
||||||
|
<Text fontWeight="bold" color={textColor} fontSize="sm">
|
||||||
|
{stock.stock_name}
|
||||||
|
</Text>
|
||||||
|
<HStack spacing={2}>
|
||||||
|
<Text fontSize="xs" color={subTextColor}>
|
||||||
|
{stock.stock_code}
|
||||||
|
</Text>
|
||||||
|
{stock.pinyin_abbr && (
|
||||||
|
<Text fontSize="xs" color={subTextColor}>
|
||||||
|
({stock.pinyin_abbr.toUpperCase()})
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</HStack>
|
||||||
|
</VStack>
|
||||||
|
{stock.exchange && (
|
||||||
|
<Tag
|
||||||
|
size="sm"
|
||||||
|
colorScheme="blue"
|
||||||
|
variant="subtle"
|
||||||
|
fontSize="xs"
|
||||||
|
>
|
||||||
|
{stock.exchange}
|
||||||
|
</Tag>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
</ListItem>
|
||||||
|
))}
|
||||||
|
</List>
|
||||||
|
) : (
|
||||||
|
<Center p={4}>
|
||||||
|
<Text color={subTextColor} fontSize="sm">
|
||||||
|
{searchQuery ? "未找到相关股票" : "输入股票代码或名称搜索"}
|
||||||
|
</Text>
|
||||||
|
</Center>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user