Files
vf_react/src/views/Company/components/CompanyHeader/index.tsx
zdl f5dbdfa84c refactor(layout): 统一页面边距管理,移除 Container 限制
- 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>
2025-12-24 18:34:42 +08:00

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;