更新Company页面的UI为FUI风格
This commit is contained in:
@@ -1,8 +1,11 @@
|
||||
/**
|
||||
* Company 页面顶部搜索栏组件
|
||||
* - 显示股票代码、名称、价格、涨跌幅
|
||||
* - 股票搜索功能
|
||||
* - 自选股操作
|
||||
* Company 页面顶部搜索栏组件 - FUI 科幻风格
|
||||
*
|
||||
* 设计特点:
|
||||
* - Glassmorphism 毛玻璃背景
|
||||
* - 发光效果和微动画
|
||||
* - Ash Thorp 风格的数据展示
|
||||
* - James Turrell 柔和光影
|
||||
*/
|
||||
|
||||
import React, { memo, useMemo, useCallback, useState } from 'react';
|
||||
@@ -14,17 +17,24 @@ import {
|
||||
Text,
|
||||
Button,
|
||||
Icon,
|
||||
Badge,
|
||||
Skeleton,
|
||||
keyframes,
|
||||
} from '@chakra-ui/react';
|
||||
import { AutoComplete, Spin } from 'antd';
|
||||
import { Search, Star } from 'lucide-react';
|
||||
import { Search, Star, TrendingUp, TrendingDown } from 'lucide-react';
|
||||
import { useStockSearch } from '@hooks/useStockSearch';
|
||||
import { THEME, getSearchBoxStyles } from '../../config';
|
||||
import { FUI_COLORS, FUI_GLOW, FUI_ANIMATION, FUI_GLASS } from '../../theme/fui';
|
||||
import type { CompanyHeaderProps, StockSearchResult } from '../../types';
|
||||
|
||||
// 发光脉冲动画
|
||||
const glowPulse = keyframes`
|
||||
0%, 100% { box-shadow: 0 0 20px rgba(212, 175, 55, 0.2); }
|
||||
50% { box-shadow: 0 0 30px rgba(212, 175, 55, 0.4); }
|
||||
`;
|
||||
|
||||
/**
|
||||
* 股票信息展示组件
|
||||
* 股票信息展示组件 - FUI 风格
|
||||
*/
|
||||
const StockInfoDisplay = memo<{
|
||||
stockCode: string;
|
||||
@@ -35,47 +45,95 @@ const StockInfoDisplay = memo<{
|
||||
}>(({ stockCode, stockName, price, change, loading }) => {
|
||||
if (loading) {
|
||||
return (
|
||||
<VStack align="start" spacing={1}>
|
||||
<Skeleton height="32px" width="200px" />
|
||||
<Skeleton height="24px" width="150px" />
|
||||
<VStack align="start" spacing={2}>
|
||||
<Skeleton height="36px" width="220px" startColor={FUI_COLORS.bg.surface} endColor={FUI_COLORS.bg.elevated} />
|
||||
<Skeleton height="28px" width="160px" startColor={FUI_COLORS.bg.surface} endColor={FUI_COLORS.bg.elevated} />
|
||||
</VStack>
|
||||
);
|
||||
}
|
||||
|
||||
const isPositive = change !== null && change !== undefined && change >= 0;
|
||||
const TrendIcon = isPositive ? TrendingUp : TrendingDown;
|
||||
|
||||
return (
|
||||
<VStack align="start" spacing={0}>
|
||||
<HStack spacing={2}>
|
||||
<VStack align="start" spacing={1}>
|
||||
{/* 股票代码 & 名称 */}
|
||||
<HStack spacing={3} align="baseline">
|
||||
<Text
|
||||
fontSize="2xl"
|
||||
fontWeight="bold"
|
||||
color={THEME.gold}
|
||||
color={FUI_COLORS.gold[400]}
|
||||
letterSpacing="wider"
|
||||
textShadow={FUI_GLOW.text.gold}
|
||||
>
|
||||
{stockCode}
|
||||
</Text>
|
||||
{stockName && (
|
||||
<Text fontSize="xl" fontWeight="medium" color={THEME.textPrimary}>
|
||||
<Text
|
||||
fontSize="xl"
|
||||
fontWeight="medium"
|
||||
color={FUI_COLORS.text.primary}
|
||||
letterSpacing="wide"
|
||||
>
|
||||
{stockName}
|
||||
</Text>
|
||||
)}
|
||||
</HStack>
|
||||
|
||||
{/* 价格 & 涨跌幅 */}
|
||||
{price !== null && price !== undefined && (
|
||||
<HStack spacing={3} mt={1}>
|
||||
<Text fontSize="lg" fontWeight="bold" color={THEME.textPrimary}>
|
||||
¥{price.toFixed(2)}
|
||||
</Text>
|
||||
{change !== null && change !== undefined && (
|
||||
<Badge
|
||||
px={2}
|
||||
py={0.5}
|
||||
borderRadius="md"
|
||||
bg={change >= 0 ? THEME.positiveBg : THEME.negativeBg}
|
||||
color={change >= 0 ? THEME.positive : THEME.negative}
|
||||
fontSize="sm"
|
||||
fontWeight="bold"
|
||||
<HStack spacing={4} mt={1}>
|
||||
{/* 价格 */}
|
||||
<HStack spacing={1}>
|
||||
<Text
|
||||
fontSize="xs"
|
||||
color={FUI_COLORS.text.muted}
|
||||
textTransform="uppercase"
|
||||
letterSpacing="wider"
|
||||
>
|
||||
{change >= 0 ? '+' : ''}{change.toFixed(2)}%
|
||||
</Badge>
|
||||
Price
|
||||
</Text>
|
||||
<Text
|
||||
fontSize="xl"
|
||||
fontWeight="bold"
|
||||
color={FUI_COLORS.text.primary}
|
||||
fontFamily="mono"
|
||||
>
|
||||
¥{price.toFixed(2)}
|
||||
</Text>
|
||||
</HStack>
|
||||
|
||||
{/* 涨跌幅 Badge */}
|
||||
{change !== null && change !== undefined && (
|
||||
<Box
|
||||
display="inline-flex"
|
||||
alignItems="center"
|
||||
gap={1}
|
||||
px={3}
|
||||
py={1}
|
||||
borderRadius="full"
|
||||
bg={isPositive ? 'rgba(239, 68, 68, 0.15)' : 'rgba(34, 197, 94, 0.15)'}
|
||||
border="1px solid"
|
||||
borderColor={isPositive ? 'rgba(239, 68, 68, 0.3)' : 'rgba(34, 197, 94, 0.3)'}
|
||||
boxShadow={isPositive
|
||||
? '0 0 12px rgba(239, 68, 68, 0.2)'
|
||||
: '0 0 12px rgba(34, 197, 94, 0.2)'
|
||||
}
|
||||
>
|
||||
<Icon
|
||||
as={TrendIcon}
|
||||
boxSize={3.5}
|
||||
color={isPositive ? FUI_COLORS.status.positive : FUI_COLORS.status.negative}
|
||||
/>
|
||||
<Text
|
||||
fontSize="sm"
|
||||
fontWeight="bold"
|
||||
fontFamily="mono"
|
||||
color={isPositive ? FUI_COLORS.status.positive : FUI_COLORS.status.negative}
|
||||
>
|
||||
{isPositive ? '+' : ''}{change.toFixed(2)}%
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
</HStack>
|
||||
)}
|
||||
@@ -154,8 +212,36 @@ const SearchActions = memo<{
|
||||
|
||||
return (
|
||||
<HStack spacing={3}>
|
||||
{/* 搜索框 */}
|
||||
<Box sx={getSearchBoxStyles(THEME)}>
|
||||
{/* 搜索框 - FUI 风格 */}
|
||||
<Box
|
||||
sx={{
|
||||
...getSearchBoxStyles(THEME),
|
||||
'.ant-select-selector': {
|
||||
backgroundColor: `${FUI_COLORS.bg.primary} !important`,
|
||||
borderColor: `${FUI_COLORS.line.default} !important`,
|
||||
borderRadius: '10px !important',
|
||||
height: '42px !important',
|
||||
backdropFilter: FUI_GLASS.blur.sm,
|
||||
transition: `all ${FUI_ANIMATION.duration.fast} ${FUI_ANIMATION.easing.default}`,
|
||||
'&:hover': {
|
||||
borderColor: `${FUI_COLORS.line.emphasis} !important`,
|
||||
boxShadow: FUI_GLOW.gold.sm,
|
||||
},
|
||||
'&:focus-within': {
|
||||
borderColor: `${FUI_COLORS.gold[400]} !important`,
|
||||
boxShadow: FUI_GLOW.gold.md,
|
||||
},
|
||||
},
|
||||
'.ant-select-selection-search-input': {
|
||||
color: `${FUI_COLORS.text.primary} !important`,
|
||||
height: '40px !important',
|
||||
fontFamily: 'inherit',
|
||||
},
|
||||
'.ant-select-selection-placeholder': {
|
||||
color: `${FUI_COLORS.text.muted} !important`,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<AutoComplete
|
||||
value={inputCode}
|
||||
options={stockOptions}
|
||||
@@ -163,36 +249,57 @@ const SearchActions = memo<{
|
||||
onSelect={handleSelect}
|
||||
onChange={onInputChange}
|
||||
placeholder="输入代码、名称或拼音"
|
||||
style={{ width: 220 }}
|
||||
style={{ width: 240 }}
|
||||
notFoundContent={isSearching ? <Spin size="small" /> : null}
|
||||
onKeyDown={handleKeyDown}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* 搜索按钮 */}
|
||||
{/* 搜索按钮 - 发光效果 */}
|
||||
<Button
|
||||
bg={THEME.gold}
|
||||
color={THEME.bg}
|
||||
_hover={{ bg: THEME.goldLight }}
|
||||
_active={{ bg: THEME.goldDark }}
|
||||
bg={`linear-gradient(135deg, ${FUI_COLORS.gold[500]} 0%, ${FUI_COLORS.gold[400]} 100%)`}
|
||||
color={FUI_COLORS.bg.deep}
|
||||
_hover={{
|
||||
bg: `linear-gradient(135deg, ${FUI_COLORS.gold[400]} 0%, ${FUI_COLORS.gold[300]} 100%)`,
|
||||
boxShadow: FUI_GLOW.gold.md,
|
||||
transform: 'translateY(-1px)',
|
||||
}}
|
||||
_active={{
|
||||
bg: FUI_COLORS.gold[600],
|
||||
transform: 'translateY(0)',
|
||||
}}
|
||||
size="md"
|
||||
h="42px"
|
||||
px={5}
|
||||
onClick={onSearch}
|
||||
leftIcon={<Icon as={Search} boxSize={4} />}
|
||||
fontWeight="bold"
|
||||
borderRadius="10px"
|
||||
transition={`all ${FUI_ANIMATION.duration.fast} ${FUI_ANIMATION.easing.default}`}
|
||||
>
|
||||
查询
|
||||
</Button>
|
||||
|
||||
{/* 自选按钮 */}
|
||||
{/* 自选按钮 - FUI 风格 */}
|
||||
<Button
|
||||
variant={isInWatchlist ? 'solid' : 'outline'}
|
||||
bg={isInWatchlist ? THEME.gold : 'transparent'}
|
||||
color={isInWatchlist ? THEME.bg : THEME.gold}
|
||||
borderColor={THEME.gold}
|
||||
variant="outline"
|
||||
bg={isInWatchlist ? 'rgba(212, 175, 55, 0.2)' : 'transparent'}
|
||||
color={FUI_COLORS.gold[400]}
|
||||
borderColor={isInWatchlist ? FUI_COLORS.gold[400] : FUI_COLORS.line.emphasis}
|
||||
borderWidth="1px"
|
||||
_hover={{
|
||||
bg: isInWatchlist ? THEME.goldLight : 'rgba(212, 175, 55, 0.1)',
|
||||
bg: 'rgba(212, 175, 55, 0.15)',
|
||||
borderColor: FUI_COLORS.gold[400],
|
||||
boxShadow: FUI_GLOW.gold.sm,
|
||||
transform: 'translateY(-1px)',
|
||||
}}
|
||||
_active={{
|
||||
bg: 'rgba(212, 175, 55, 0.25)',
|
||||
transform: 'translateY(0)',
|
||||
}}
|
||||
size="md"
|
||||
h="42px"
|
||||
px={5}
|
||||
onClick={onWatchlistToggle}
|
||||
isLoading={watchlistLoading}
|
||||
leftIcon={
|
||||
@@ -200,9 +307,13 @@ const SearchActions = memo<{
|
||||
as={Star}
|
||||
boxSize={4}
|
||||
fill={isInWatchlist ? 'currentColor' : 'none'}
|
||||
filter={isInWatchlist ? 'drop-shadow(0 0 4px rgba(212, 175, 55, 0.6))' : 'none'}
|
||||
/>
|
||||
}
|
||||
fontWeight="bold"
|
||||
borderRadius="10px"
|
||||
transition={`all ${FUI_ANIMATION.duration.fast} ${FUI_ANIMATION.easing.default}`}
|
||||
sx={isInWatchlist ? { animation: `${glowPulse} 3s ease-in-out infinite` } : undefined}
|
||||
>
|
||||
{isInWatchlist ? '已自选' : '自选'}
|
||||
</Button>
|
||||
@@ -248,13 +359,42 @@ const CompanyHeader: React.FC<CompanyHeaderProps> = memo(({
|
||||
|
||||
return (
|
||||
<Box
|
||||
bg={THEME.cardBg}
|
||||
position="relative"
|
||||
bg={`linear-gradient(180deg, ${FUI_COLORS.bg.elevated} 0%, ${FUI_COLORS.bg.primary} 100%)`}
|
||||
borderBottom="1px solid"
|
||||
borderColor={THEME.border}
|
||||
borderColor={FUI_COLORS.line.default}
|
||||
px={6}
|
||||
py={4}
|
||||
py={5}
|
||||
backdropFilter={FUI_GLASS.blur.md}
|
||||
overflow="hidden"
|
||||
>
|
||||
{/* 环境光效果 - James Turrell 风格 */}
|
||||
<Box
|
||||
position="absolute"
|
||||
top={0}
|
||||
left={0}
|
||||
right={0}
|
||||
bottom={0}
|
||||
pointerEvents="none"
|
||||
bg={`radial-gradient(ellipse 80% 50% at 20% 40%, ${FUI_COLORS.ambient.warm}, transparent),
|
||||
radial-gradient(ellipse 60% 40% at 80% 60%, ${FUI_COLORS.ambient.cool}, transparent)`}
|
||||
opacity={0.6}
|
||||
/>
|
||||
|
||||
{/* 顶部发光线 */}
|
||||
<Box
|
||||
position="absolute"
|
||||
top={0}
|
||||
left="10%"
|
||||
right="10%"
|
||||
h="1px"
|
||||
bg={`linear-gradient(90deg, transparent 0%, ${FUI_COLORS.gold[400]} 50%, transparent 100%)`}
|
||||
opacity={0.4}
|
||||
/>
|
||||
|
||||
<Flex
|
||||
position="relative"
|
||||
zIndex={1}
|
||||
maxW="container.xl"
|
||||
mx="auto"
|
||||
justify="space-between"
|
||||
|
||||
Reference in New Issue
Block a user