feat: UI调整

This commit is contained in:
zdl
2025-12-10 10:09:24 +08:00
parent 9423094af2
commit 3382dd1036
9 changed files with 200 additions and 108 deletions

View File

@@ -0,0 +1,84 @@
/**
* FavoriteButton - 通用关注/收藏按钮组件(图标按钮)
*/
import React from 'react';
import { IconButton, Tooltip, Spinner } from '@chakra-ui/react';
import { Star } from 'lucide-react';
export interface FavoriteButtonProps {
/** 是否已关注 */
isFavorite: boolean;
/** 加载状态 */
isLoading?: boolean;
/** 点击回调 */
onClick: () => void;
/** 按钮大小 */
size?: 'sm' | 'md' | 'lg';
/** 颜色主题 */
colorScheme?: 'gold' | 'default';
/** 是否显示 tooltip */
showTooltip?: boolean;
}
// 颜色配置
const COLORS = {
gold: {
active: '#F4D03F', // 已关注 - 亮金色
inactive: '#C9A961', // 未关注 - 暗金色
hoverBg: 'whiteAlpha.100',
},
default: {
active: 'yellow.400',
inactive: 'gray.400',
hoverBg: 'gray.100',
},
};
const FavoriteButton: React.FC<FavoriteButtonProps> = ({
isFavorite,
isLoading = false,
onClick,
size = 'sm',
colorScheme = 'gold',
showTooltip = true,
}) => {
const colors = COLORS[colorScheme];
const currentColor = isFavorite ? colors.active : colors.inactive;
const label = isFavorite ? '取消关注' : '加入自选';
const iconButton = (
<IconButton
aria-label={label}
icon={
isLoading ? (
<Spinner size="sm" color={currentColor} />
) : (
<Star
size={size === 'sm' ? 18 : size === 'md' ? 20 : 24}
fill={isFavorite ? currentColor : 'none'}
stroke={currentColor}
/>
)
}
variant="ghost"
color={currentColor}
size={size}
onClick={onClick}
isDisabled={isLoading}
_hover={{ bg: colors.hoverBg }}
/>
);
if (showTooltip) {
return (
<Tooltip label={label} placement="top">
{iconButton}
</Tooltip>
);
}
return iconButton;
};
export default FavoriteButton;

View File

@@ -1,5 +1,5 @@
// src/views/Company/components/CompanyHeader/SearchBar.js // src/views/Company/components/CompanyHeader/SearchBar.js
// 股票搜索栏组件 // 股票搜索栏组件 - 金色主题
import React from 'react'; import React from 'react';
import { import {
@@ -30,7 +30,7 @@ const SearchBar = ({
<HStack spacing={3}> <HStack spacing={3}>
<InputGroup size="lg" maxW="300px"> <InputGroup size="lg" maxW="300px">
<InputLeftElement pointerEvents="none"> <InputLeftElement pointerEvents="none">
<SearchIcon color="gray.400" /> <SearchIcon color="#C9A961" />
</InputLeftElement> </InputLeftElement>
<Input <Input
placeholder="输入股票代码" placeholder="输入股票代码"
@@ -39,10 +39,14 @@ const SearchBar = ({
onKeyPress={onKeyPress} onKeyPress={onKeyPress}
borderRadius="md" borderRadius="md"
color="white" color="white"
_placeholder={{ color: 'whiteAlpha.600' }} borderColor="#C9A961"
_placeholder={{ color: '#C9A961' }}
_focus={{ _focus={{
borderColor: 'blue.500', borderColor: '#F4D03F',
boxShadow: '0 0 0 1px #3182ce', boxShadow: '0 0 0 1px #F4D03F',
}}
_hover={{
borderColor: '#F4D03F',
}} }}
/> />
</InputGroup> </InputGroup>
@@ -54,7 +58,7 @@ const SearchBar = ({
color="#C9A961" color="#C9A961"
borderWidth="1px" borderWidth="1px"
borderColor="#C9A961" borderColor="#C9A961"
_hover={{ bg: '#1a1a1a' }} _hover={{ bg: '#1a1a1a', borderColor: '#F4D03F', color: '#F4D03F' }}
> >
查询 查询
</Button> </Button>

View File

@@ -1,35 +0,0 @@
// src/views/Company/components/CompanyHeader/WatchlistButton.js
// 自选股按钮组件
import React from 'react';
import { Button } from '@chakra-ui/react';
import { StarIcon } from '@chakra-ui/icons';
/**
* 自选股按钮组件
*
* @param {Object} props
* @param {boolean} props.isInWatchlist - 是否已在自选股中
* @param {boolean} props.isLoading - 是否正在加载
* @param {Function} props.onClick - 点击回调
*/
const WatchlistButton = ({
isInWatchlist,
isLoading,
onClick,
}) => {
return (
<Button
colorScheme={isInWatchlist ? 'yellow' : 'teal'}
variant={isInWatchlist ? 'solid' : 'outline'}
size="lg"
onClick={onClick}
leftIcon={<StarIcon />}
isLoading={isLoading}
>
{isInWatchlist ? '已关注' : '关注'}
</Button>
);
};
export default WatchlistButton;

View File

@@ -9,72 +9,50 @@ import {
VStack, VStack,
Heading, Heading,
Text, Text,
Badge,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import SearchBar from './SearchBar'; import SearchBar from './SearchBar';
import WatchlistButton from './WatchlistButton';
/** /**
* 公司详情页面头部区域组件 * 公司详情页面头部区域组件
* *
* 包含: * 包含:
* - 页面标题和描述 * - 页面标题和描述(金色主题)
* - 股票搜索栏 * - 股票搜索栏
* - 自选股按钮
* - 当前股票代码显示
* *
* @param {Object} props * @param {Object} props
* @param {string} props.stockCode - 当前股票代码
* @param {string} props.inputCode - 搜索输入框值 * @param {string} props.inputCode - 搜索输入框值
* @param {Function} props.onInputChange - 输入变化回调 * @param {Function} props.onInputChange - 输入变化回调
* @param {Function} props.onSearch - 搜索回调 * @param {Function} props.onSearch - 搜索回调
* @param {Function} props.onKeyPress - 键盘事件回调 * @param {Function} props.onKeyPress - 键盘事件回调
* @param {boolean} props.isInWatchlist - 是否在自选股中
* @param {boolean} props.isWatchlistLoading - 自选股操作加载中
* @param {Function} props.onWatchlistToggle - 自选股切换回调
* @param {string} props.bgColor - 背景颜色 * @param {string} props.bgColor - 背景颜色
*/ */
const CompanyHeader = ({ const CompanyHeader = ({
stockCode,
inputCode, inputCode,
onInputChange, onInputChange,
onSearch, onSearch,
onKeyPress, onKeyPress,
isInWatchlist,
isWatchlistLoading,
onWatchlistToggle,
bgColor, bgColor,
}) => { }) => {
return ( return (
<Card bg={bgColor} shadow="md"> <Card bg={bgColor} shadow="md">
<CardBody> <CardBody>
<HStack justify="space-between" align="center"> <HStack justify="space-between" align="center">
{/* 标题区域 */} {/* 标题区域 - 金色主题 */}
<VStack align="start" spacing={1}> <VStack align="start" spacing={1}>
<Heading size="lg" color="white">个股详情</Heading> <Heading size="lg" color="#F4D03F">个股详情</Heading>
<Text color="white" fontSize="sm"> <Text color="#C9A961" fontSize="sm">
查看股票实时行情财务数据和盈利预测 查看股票实时行情财务数据和盈利预测
</Text> </Text>
</VStack> </VStack>
{/* 操作区域 */} {/* 搜索栏 */}
<HStack spacing={3}> <SearchBar
{/* 搜索栏 */} inputCode={inputCode}
<SearchBar onInputChange={onInputChange}
inputCode={inputCode} onSearch={onSearch}
onInputChange={onInputChange} onKeyPress={onKeyPress}
onSearch={onSearch} />
onKeyPress={onKeyPress}
/>
{/* 自选股按钮 */}
<WatchlistButton
isInWatchlist={isInWatchlist}
isLoading={isWatchlistLoading}
onClick={onWatchlistToggle}
/>
</HStack>
</HStack> </HStack>
</CardBody> </CardBody>
</Card> </Card>

View File

@@ -83,7 +83,7 @@ const CompanyTabs = ({ stockCode, onTabChange }) => {
{COMPANY_TABS.map((tab) => { {COMPANY_TABS.map((tab) => {
const Component = TAB_COMPONENTS[tab.key]; const Component = TAB_COMPONENTS[tab.key];
return ( return (
<TabPanel key={tab.key} p={6}> <TabPanel key={tab.key}>
<Component stockCode={stockCode} /> <Component stockCode={stockCode} />
</TabPanel> </TabPanel>
); );

View File

@@ -16,8 +16,12 @@ import {
Badge, Badge,
Progress, Progress,
Skeleton, Skeleton,
IconButton,
Tooltip,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { Share2 } from 'lucide-react';
import FavoriteButton from '@components/FavoriteButton';
import type { StockQuoteCardProps } from './types'; import type { StockQuoteCardProps } from './types';
import { mockStockQuoteData } from './mockData'; import { mockStockQuoteData } from './mockData';
@@ -50,7 +54,16 @@ const formatNetInflow = (value: number): string => {
const StockQuoteCard: React.FC<StockQuoteCardProps> = ({ const StockQuoteCard: React.FC<StockQuoteCardProps> = ({
data = mockStockQuoteData, data = mockStockQuoteData,
isLoading = false, isLoading = false,
isInWatchlist = false,
isWatchlistLoading = false,
onWatchlistToggle,
onShare,
}) => { }) => {
// 处理分享点击
const handleShare = () => {
onShare?.();
};
// 黑金主题颜色配置 // 黑金主题颜色配置
const cardBg = '#1A202C'; const cardBg = '#1A202C';
const borderColor = '#C9A961'; const borderColor = '#C9A961';
@@ -77,31 +90,47 @@ const StockQuoteCard: React.FC<StockQuoteCardProps> = ({
return ( return (
<Card bg={cardBg} shadow="sm" borderWidth="1px" borderColor={borderColor}> <Card bg={cardBg} shadow="sm" borderWidth="1px" borderColor={borderColor}>
<CardBody> <CardBody>
{/* 顶部:股票名称 + 更新时间 */} {/* 顶部:股票名称 + 关注/分享按钮 + 更新时间 */}
<Flex justify="space-between" align="center"> <Flex justify="space-between" align="center" mb={4}>
<HStack spacing={3}> {/* 左侧:名称(代码) | 指数标签 */}
<Text fontSize="24px" fontWeight="bold" color={valueColor}> <HStack spacing={2}>
{data.name} <Text fontSize="22px" fontWeight="bold" color={valueColor}>
{data.name}{data.code}
</Text>
{data.indexTags.length > 0 && (
<>
<Text color={labelColor} fontSize="22px" fontWeight="light">|</Text>
<Text fontSize="16px" color={labelColor}>
{data.indexTags.join('、')}
</Text>
</>
)}
</HStack>
{/* 右侧:关注 + 分享 + 时间 */}
<HStack spacing={3}>
<FavoriteButton
isFavorite={isInWatchlist}
isLoading={isWatchlistLoading}
onClick={onWatchlistToggle || (() => {})}
colorScheme="gold"
size="sm"
/>
<Tooltip label="分享" placement="top">
<IconButton
aria-label="分享"
icon={<Share2 size={18} />}
variant="ghost"
color={labelColor}
size="sm"
onClick={handleShare}
_hover={{ bg: 'whiteAlpha.100' }}
/>
</Tooltip>
<Text fontSize="14px" color={labelColor}>
{data.updateTime.split(' ')[1]}
</Text> </Text>
<Text fontSize="24px" fontWeight="bold" color={labelColor}>
({data.code})
</Text>
{data.indexTags.map((tag) => (
<Badge
key={tag}
variant="outline"
borderColor={borderColor}
color={labelColor}
fontSize="14px"
px={2}
>
{tag}
</Badge>
))}
</HStack> </HStack>
<Text fontSize="14px" color={labelColor}>
{data.updateTime}
</Text>
</Flex> </Flex>
{/* 三栏布局 */} {/* 三栏布局 */}
@@ -124,19 +153,35 @@ const StockQuoteCard: React.FC<StockQuoteCardProps> = ({
{formatChangePercent(data.changePercent)} {formatChangePercent(data.changePercent)}
</Badge> </Badge>
</HStack> </HStack>
<HStack spacing={6} fontSize="14px"> {/* 次要行情:今开 | 昨收 | 最高 | 最低 */}
<HStack spacing={4} fontSize="14px" flexWrap="wrap">
<Text color={labelColor}> <Text color={labelColor}>
<Text as="span" color={valueColor} fontWeight="bold" fontSize="16px"> <Text as="span" color={valueColor} fontWeight="bold">
{formatPrice(data.todayOpen)} {formatPrice(data.todayOpen)}
</Text> </Text>
</Text> </Text>
<Text color={borderColor}>|</Text>
<Text color={labelColor}> <Text color={labelColor}>
<Text as="span" color={valueColor} fontWeight="bold" fontSize="16px"> <Text as="span" color={valueColor} fontWeight="bold">
{formatPrice(data.yesterdayClose)} {formatPrice(data.yesterdayClose)}
</Text> </Text>
</Text> </Text>
<Text color={borderColor}>|</Text>
<Text color={labelColor}>
<Text as="span" color={upColor} fontWeight="bold">
{formatPrice(data.todayHigh)}
</Text>
</Text>
<Text color={borderColor}>|</Text>
<Text color={labelColor}>
<Text as="span" color={downColor} fontWeight="bold">
{formatPrice(data.todayLow)}
</Text>
</Text>
</HStack> </HStack>
</Box> </Box>

View File

@@ -14,6 +14,8 @@ export const mockStockQuoteData: StockQuoteCardData = {
changePercent: 3.65, changePercent: 3.65,
todayOpen: 2156.0, todayOpen: 2156.0,
yesterdayClose: 2101.0, yesterdayClose: 2101.0,
todayHigh: 2185.0,
todayLow: 2150.0,
// 关键指标 // 关键指标
pe: 38.62, pe: 38.62,
@@ -30,4 +32,7 @@ export const mockStockQuoteData: StockQuoteCardData = {
// 更新时间 // 更新时间
updateTime: '2025-12-03 14:30:25', updateTime: '2025-12-03 14:30:25',
// 自选状态
isFavorite: false,
}; };

View File

@@ -16,6 +16,8 @@ export interface StockQuoteCardData {
changePercent: number; // 涨跌幅(百分比,如 3.65 表示 +3.65% changePercent: number; // 涨跌幅(百分比,如 3.65 表示 +3.65%
todayOpen: number; // 今开 todayOpen: number; // 今开
yesterdayClose: number; // 昨收 yesterdayClose: number; // 昨收
todayHigh: number; // 今日最高
todayLow: number; // 今日最低
// 关键指标 // 关键指标
pe: number; // 市盈率 pe: number; // 市盈率
@@ -32,6 +34,9 @@ export interface StockQuoteCardData {
// 更新时间 // 更新时间
updateTime: string; // 格式YYYY-MM-DD HH:mm:ss updateTime: string; // 格式YYYY-MM-DD HH:mm:ss
// 自选状态
isFavorite?: boolean; // 是否已加入自选
} }
/** /**
@@ -40,4 +45,10 @@ export interface StockQuoteCardData {
export interface StockQuoteCardProps { export interface StockQuoteCardProps {
data?: StockQuoteCardData; data?: StockQuoteCardData;
isLoading?: boolean; isLoading?: boolean;
// 自选股相关(与 WatchlistButton 接口保持一致)
isInWatchlist?: boolean; // 是否在自选股中
isWatchlistLoading?: boolean; // 自选股操作加载中
onWatchlistToggle?: () => void; // 自选股切换回调
// 分享
onShare?: () => void; // 分享回调
} }

View File

@@ -66,21 +66,21 @@ const CompanyIndex = () => {
return ( return (
<Container maxW="container.xl" py={0} bg='#1A202C'> <Container maxW="container.xl" py={0} bg='#1A202C'>
<VStack align="stretch" spacing={0}> <VStack align="stretch" spacing={0}>
{/* 页面头部:标题、搜索、自选股按钮 */} {/* 页面头部:标题、搜索 */}
<CompanyHeader <CompanyHeader
stockCode={stockCode}
inputCode={inputCode} inputCode={inputCode}
onInputChange={setInputCode} onInputChange={setInputCode}
onSearch={handleSearch} onSearch={handleSearch}
onKeyPress={handleKeyPress} onKeyPress={handleKeyPress}
isInWatchlist={isInWatchlist}
isWatchlistLoading={isWatchlistLoading}
onWatchlistToggle={handleWatchlistToggle}
bgColor="#1A202C" bgColor="#1A202C"
/> />
{/* 股票行情卡片:价格、关键指标、主力动态 */} {/* 股票行情卡片:价格、关键指标、主力动态、自选股按钮 */}
<StockQuoteCard /> <StockQuoteCard
isInWatchlist={isInWatchlist}
isWatchlistLoading={isWatchlistLoading}
onWatchlistToggle={handleWatchlistToggle}
/>
{/* Tab 切换区域:概览、行情、财务、预测 */} {/* Tab 切换区域:概览、行情、财务、预测 */}
<CompanyTabs stockCode={stockCode} onTabChange={trackTabChanged} bgColor="#1A202C"/> <CompanyTabs stockCode={stockCode} onTabChange={trackTabChanged} bgColor="#1A202C"/>