- layoutConfig.js: 新增 LAYOUT_PADDING 常量 { base: 4, md: 6, lg: '80px' }
- MainLayout.js: 在 Outlet 容器上统一应用 px={LAYOUT_PADDING.x}
- HomeNavbar.js: 边距从 lg:8 改为 lg:'80px',与内容区对齐
- AppFooter.js: 移除 Container,边距改为 lg:'80px'
页面组件清理(移除冗余的 px/Container):
- Company, Community, Center, Profile, Settings
- ValueForum, DataBrowser, LimitAnalyse, StockOverview, Concept
特殊处理:
- CompanyHeader: 使用负边距实现全宽背景
- Concept Hero: 使用负边距实现全宽背景
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
155 lines
4.2 KiB
TypeScript
155 lines
4.2 KiB
TypeScript
/**
|
|
* Company 页面顶部搜索栏组件 - FUI 科幻风格
|
|
*/
|
|
|
|
import React, { memo, useMemo, useCallback, useState } from 'react';
|
|
import { Box, Flex, HStack, VStack, Text } from '@chakra-ui/react';
|
|
import { AutoComplete, Input, Spin } from 'antd';
|
|
import { SearchOutlined } from '@ant-design/icons';
|
|
import { useStockSearch } from '@hooks/useStockSearch';
|
|
import { LAYOUT_PADDING } from '@/layouts/config/layoutConfig';
|
|
import { THEME } from '../../config';
|
|
import { FUI_COLORS, FUI_GLOW } from '../../theme/fui';
|
|
import type { CompanyHeaderProps, StockSearchResult } from '../../types';
|
|
import {
|
|
DROPDOWN_STYLE,
|
|
SEARCH_ICON_STYLE,
|
|
INPUT_STYLE,
|
|
AUTOCOMPLETE_STYLE,
|
|
SEARCH_BOX_SX,
|
|
} from './constants';
|
|
|
|
// ============================================
|
|
// SearchBox 子组件
|
|
// ============================================
|
|
|
|
const SearchBox = memo<{
|
|
onStockChange: (value: string) => void;
|
|
}>(({ onStockChange }) => {
|
|
const [inputCode, setInputCode] = useState('');
|
|
|
|
const {
|
|
searchResults,
|
|
isSearching,
|
|
handleSearch: doSearch,
|
|
clearSearch,
|
|
} = useStockSearch({
|
|
limit: 10,
|
|
debounceMs: 300,
|
|
onSearch: () => {},
|
|
}) as {
|
|
searchResults: StockSearchResult[];
|
|
isSearching: boolean;
|
|
handleSearch: (query: string) => void;
|
|
clearSearch: () => void;
|
|
};
|
|
|
|
const stockOptions = useMemo(() => (
|
|
searchResults.map((stock: StockSearchResult) => ({
|
|
value: stock.stock_code,
|
|
label: (
|
|
<Flex justify="space-between" align="center" py={1}>
|
|
<HStack spacing={2}>
|
|
<Text fontWeight="bold" color={THEME.gold}>{stock.stock_code}</Text>
|
|
<Text color={THEME.textPrimary}>{stock.stock_name}</Text>
|
|
</HStack>
|
|
{stock.pinyin_abbr && (
|
|
<Text fontSize="xs" color={THEME.textMuted}>
|
|
{stock.pinyin_abbr.toUpperCase()}
|
|
</Text>
|
|
)}
|
|
</Flex>
|
|
),
|
|
}))
|
|
), [searchResults]);
|
|
|
|
const handleSearch = useCallback(() => {
|
|
if (inputCode) {
|
|
onStockChange(inputCode);
|
|
}
|
|
}, [inputCode, onStockChange]);
|
|
|
|
const handleSelect = useCallback((value: string) => {
|
|
clearSearch();
|
|
setInputCode(value);
|
|
onStockChange(value);
|
|
}, [clearSearch, onStockChange]);
|
|
|
|
const handleKeyDown = useCallback((e: React.KeyboardEvent) => {
|
|
if (e.key === 'Enter') handleSearch();
|
|
}, [handleSearch]);
|
|
|
|
const searchIcon = useMemo(() => (
|
|
<SearchOutlined style={SEARCH_ICON_STYLE} onClick={handleSearch} />
|
|
), [handleSearch]);
|
|
|
|
return (
|
|
<Box sx={SEARCH_BOX_SX}>
|
|
<AutoComplete
|
|
classNames={{ popup: { root: 'fui-autocomplete-dropdown' } }}
|
|
styles={{ popup: { root: DROPDOWN_STYLE } }}
|
|
value={inputCode}
|
|
options={stockOptions}
|
|
onSearch={doSearch}
|
|
onSelect={handleSelect}
|
|
onChange={setInputCode}
|
|
style={AUTOCOMPLETE_STYLE}
|
|
notFoundContent={isSearching ? <Spin size="small" /> : null}
|
|
>
|
|
<Input
|
|
placeholder="输入股票代码或名称"
|
|
prefix={searchIcon}
|
|
onKeyDown={handleKeyDown}
|
|
style={INPUT_STYLE}
|
|
/>
|
|
</AutoComplete>
|
|
</Box>
|
|
);
|
|
});
|
|
|
|
SearchBox.displayName = 'SearchBox';
|
|
|
|
// ============================================
|
|
// CompanyHeader 主组件
|
|
// ============================================
|
|
|
|
const CompanyHeader: React.FC<CompanyHeaderProps> = memo(({ onStockChange }) => (
|
|
<Box
|
|
position="relative"
|
|
border="1px solid"
|
|
borderRadius="8px"
|
|
sx={{
|
|
bg: FUI_COLORS.bg.primary,
|
|
borderColor: FUI_COLORS.line.default,
|
|
padding: '20px 20px',
|
|
}}
|
|
>
|
|
<Flex
|
|
position="relative"
|
|
zIndex={1}
|
|
justify="space-between"
|
|
align="center"
|
|
>
|
|
<VStack align="start" spacing={1}>
|
|
<Text
|
|
fontSize="2xl"
|
|
fontWeight="bold"
|
|
color={FUI_COLORS.gold[400]}
|
|
letterSpacing="wider"
|
|
textShadow={FUI_GLOW.text.gold}
|
|
>
|
|
个股详情
|
|
</Text>
|
|
<Text fontSize="sm" color={FUI_COLORS.text.muted} letterSpacing="wide">
|
|
查看股票实时行情、财务数据和盈利预测
|
|
</Text>
|
|
</VStack>
|
|
<SearchBox onStockChange={onStockChange} />
|
|
</Flex>
|
|
</Box>
|
|
));
|
|
|
|
CompanyHeader.displayName = 'CompanyHeader';
|
|
|
|
export default CompanyHeader;
|