Compare commits
3 Commits
4274341ed5
...
3382dd1036
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3382dd1036 | ||
|
|
9423094af2 | ||
|
|
4f38505a80 |
84
src/components/FavoriteButton/index.tsx
Normal file
84
src/components/FavoriteButton/index.tsx
Normal 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;
|
||||||
@@ -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="输入股票代码"
|
||||||
@@ -38,17 +38,27 @@ const SearchBar = ({
|
|||||||
onChange={(e) => onInputChange(e.target.value)}
|
onChange={(e) => onInputChange(e.target.value)}
|
||||||
onKeyPress={onKeyPress}
|
onKeyPress={onKeyPress}
|
||||||
borderRadius="md"
|
borderRadius="md"
|
||||||
|
color="white"
|
||||||
|
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>
|
||||||
<Button
|
<Button
|
||||||
colorScheme="blue"
|
|
||||||
size="lg"
|
size="lg"
|
||||||
onClick={onSearch}
|
onClick={onSearch}
|
||||||
leftIcon={<SearchIcon />}
|
leftIcon={<SearchIcon />}
|
||||||
|
bg="#1A202C"
|
||||||
|
color="#C9A961"
|
||||||
|
borderWidth="1px"
|
||||||
|
borderColor="#C9A961"
|
||||||
|
_hover={{ bg: '#1a1a1a', borderColor: '#F4D03F', color: '#F4D03F' }}
|
||||||
>
|
>
|
||||||
查询
|
查询
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -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;
|
|
||||||
@@ -9,57 +9,43 @@ 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">个股详情</Heading>
|
<Heading size="lg" color="#F4D03F">个股详情</Heading>
|
||||||
<Text color="gray.600" fontSize="sm">
|
<Text color="#C9A961" fontSize="sm">
|
||||||
查看股票实时行情、财务数据和盈利预测
|
查看股票实时行情、财务数据和盈利预测
|
||||||
</Text>
|
</Text>
|
||||||
</VStack>
|
</VStack>
|
||||||
|
|
||||||
{/* 操作区域 */}
|
|
||||||
<HStack spacing={3}>
|
|
||||||
{/* 搜索栏 */}
|
{/* 搜索栏 */}
|
||||||
<SearchBar
|
<SearchBar
|
||||||
inputCode={inputCode}
|
inputCode={inputCode}
|
||||||
@@ -67,24 +53,6 @@ const CompanyHeader = ({
|
|||||||
onSearch={onSearch}
|
onSearch={onSearch}
|
||||||
onKeyPress={onKeyPress}
|
onKeyPress={onKeyPress}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* 自选股按钮 */}
|
|
||||||
<WatchlistButton
|
|
||||||
isInWatchlist={isInWatchlist}
|
|
||||||
isLoading={isWatchlistLoading}
|
|
||||||
onClick={onWatchlistToggle}
|
|
||||||
/>
|
|
||||||
</HStack>
|
|
||||||
</HStack>
|
|
||||||
|
|
||||||
{/* 当前股票信息 */}
|
|
||||||
<HStack mt={4} spacing={4}>
|
|
||||||
<Badge colorScheme="blue" fontSize="md" px={3} py={1}>
|
|
||||||
股票代码: {stockCode}
|
|
||||||
</Badge>
|
|
||||||
<Text fontSize="sm" color="gray.600">
|
|
||||||
更新时间: {new Date().toLocaleString()}
|
|
||||||
</Text>
|
|
||||||
</HStack>
|
</HStack>
|
||||||
</CardBody>
|
</CardBody>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// src/views/Company/components/CompanyTabs/TabNavigation.js
|
// src/views/Company/components/CompanyTabs/TabNavigation.js
|
||||||
// Tab 导航组件 - 动态渲染 Tab 按钮
|
// Tab 导航组件 - 动态渲染 Tab 按钮(黑金主题)
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {
|
import {
|
||||||
@@ -10,31 +10,41 @@ import {
|
|||||||
Text,
|
Text,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
|
|
||||||
import { COMPANY_TABS, TAB_SELECTED_STYLE } from '../../constants';
|
import { COMPANY_TABS } from '../../constants';
|
||||||
|
|
||||||
|
// 黑金主题颜色配置
|
||||||
|
const THEME_COLORS = {
|
||||||
|
bg: '#1A202C', // 背景纯黑
|
||||||
|
selectedBg: '#C9A961', // 选中项金色背景
|
||||||
|
selectedText: '#FFFFFF', // 选中项白色文字
|
||||||
|
unselectedText: '#999999', // 未选中项深灰色
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tab 导航组件
|
* Tab 导航组件(黑金主题)
|
||||||
*
|
|
||||||
* @param {Object} props
|
|
||||||
* @param {string} props.tabBg - Tab 列表背景色
|
|
||||||
* @param {string} props.activeBg - 激活状态背景色
|
|
||||||
*/
|
*/
|
||||||
const TabNavigation = ({ tabBg, activeBg }) => {
|
const TabNavigation = () => {
|
||||||
return (
|
return (
|
||||||
<TabList p={4} bg={tabBg}>
|
<TabList py={4} bg={THEME_COLORS.bg} borderTopLeftRadius="16px" borderTopRightRadius="16px">
|
||||||
{COMPANY_TABS.map((tab, index) => (
|
{COMPANY_TABS.map((tab, index) => (
|
||||||
<Tab
|
<Tab
|
||||||
key={tab.key}
|
key={tab.key}
|
||||||
|
color={THEME_COLORS.unselectedText}
|
||||||
|
borderRadius="full"
|
||||||
|
px={4}
|
||||||
|
py={2}
|
||||||
_selected={{
|
_selected={{
|
||||||
bg: activeBg,
|
bg: THEME_COLORS.selectedBg,
|
||||||
color: 'white',
|
color: THEME_COLORS.selectedText,
|
||||||
...TAB_SELECTED_STYLE,
|
}}
|
||||||
|
_hover={{
|
||||||
|
color: THEME_COLORS.selectedText,
|
||||||
}}
|
}}
|
||||||
mr={index < COMPANY_TABS.length - 1 ? 2 : 0}
|
mr={index < COMPANY_TABS.length - 1 ? 2 : 0}
|
||||||
>
|
>
|
||||||
<HStack spacing={2}>
|
<HStack spacing={2}>
|
||||||
<Icon as={tab.icon} />
|
<Icon as={tab.icon} boxSize="18px" />
|
||||||
<Text>{tab.name}</Text>
|
<Text fontSize="15px">{tab.name}</Text>
|
||||||
</HStack>
|
</HStack>
|
||||||
</Tab>
|
</Tab>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -46,9 +46,8 @@ const TAB_COMPONENTS = {
|
|||||||
* @param {Object} props
|
* @param {Object} props
|
||||||
* @param {string} props.stockCode - 当前股票代码
|
* @param {string} props.stockCode - 当前股票代码
|
||||||
* @param {Function} props.onTabChange - Tab 变更回调 (index, tabName, prevIndex) => void
|
* @param {Function} props.onTabChange - Tab 变更回调 (index, tabName, prevIndex) => void
|
||||||
* @param {string} props.bgColor - 背景颜色
|
|
||||||
*/
|
*/
|
||||||
const CompanyTabs = ({ stockCode, onTabChange, bgColor }) => {
|
const CompanyTabs = ({ stockCode, onTabChange }) => {
|
||||||
const [currentIndex, setCurrentIndex] = useState(0);
|
const [currentIndex, setCurrentIndex] = useState(0);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -65,7 +64,7 @@ const CompanyTabs = ({ stockCode, onTabChange, bgColor }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card bg={bgColor} shadow="lg">
|
<Card shadow="lg" bg='#1A202C'>
|
||||||
<CardBody p={0}>
|
<CardBody p={0}>
|
||||||
<Tabs
|
<Tabs
|
||||||
variant="soft-rounded"
|
variant="soft-rounded"
|
||||||
@@ -74,8 +73,8 @@ const CompanyTabs = ({ stockCode, onTabChange, bgColor }) => {
|
|||||||
index={currentIndex}
|
index={currentIndex}
|
||||||
onChange={handleTabChange}
|
onChange={handleTabChange}
|
||||||
>
|
>
|
||||||
{/* Tab 导航 */}
|
{/* Tab 导航(黑金主题) */}
|
||||||
<TabNavigation tabBg="gray.50" activeBg="blue.500" />
|
<TabNavigation />
|
||||||
|
|
||||||
<Divider />
|
<Divider />
|
||||||
|
|
||||||
@@ -84,7 +83,7 @@ const CompanyTabs = ({ stockCode, onTabChange, bgColor }) => {
|
|||||||
{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>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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,17 +54,28 @@ 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 cardBg = 'white';
|
const handleShare = () => {
|
||||||
const borderColor = 'gray.200';
|
onShare?.();
|
||||||
const labelColor = 'gray.500';
|
};
|
||||||
const valueColor = 'gray.800';
|
|
||||||
const sectionTitleColor = 'gray.600';
|
|
||||||
|
|
||||||
// 涨跌颜色
|
// 黑金主题颜色配置
|
||||||
const priceColor = data.changePercent >= 0 ? 'green.500' : 'red.500';
|
const cardBg = '#1A202C';
|
||||||
const inflowColor = data.mainNetInflow >= 0 ? 'green.500' : 'red.500';
|
const borderColor = '#C9A961';
|
||||||
|
const labelColor = '#C9A961';
|
||||||
|
const valueColor = '#F4D03F';
|
||||||
|
const sectionTitleColor = '#F4D03F';
|
||||||
|
|
||||||
|
// 涨跌颜色(红涨绿跌)
|
||||||
|
const upColor = '#F44336'; // 涨 - 红色
|
||||||
|
const downColor = '#4CAF50'; // 跌 - 绿色
|
||||||
|
const priceColor = data.changePercent >= 0 ? upColor : downColor;
|
||||||
|
const inflowColor = data.mainNetInflow >= 0 ? upColor : downColor;
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
@@ -74,31 +89,48 @@ 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 py={4} px={6}>
|
<CardBody>
|
||||||
{/* 顶部:股票名称 + 更新时间 */}
|
{/* 顶部:股票名称 + 关注/分享按钮 + 更新时间 */}
|
||||||
<Flex justify="space-between" align="center" mb={4}>
|
<Flex justify="space-between" align="center" mb={4}>
|
||||||
<HStack spacing={3}>
|
{/* 左侧:名称(代码) | 指数标签 */}
|
||||||
<Text fontSize="xl" fontWeight="bold" color={valueColor}>
|
<HStack spacing={2}>
|
||||||
{data.name}
|
<Text fontSize="22px" fontWeight="bold" color={valueColor}>
|
||||||
|
{data.name}({data.code})
|
||||||
</Text>
|
</Text>
|
||||||
<Text fontSize="md" color={labelColor}>
|
{data.indexTags.length > 0 && (
|
||||||
({data.code})
|
<>
|
||||||
|
<Text color={labelColor} fontSize="22px" fontWeight="light">|</Text>
|
||||||
|
<Text fontSize="16px" color={labelColor}>
|
||||||
|
{data.indexTags.join('、')}
|
||||||
</Text>
|
</Text>
|
||||||
{data.indexTags.map((tag) => (
|
</>
|
||||||
<Badge
|
)}
|
||||||
key={tag}
|
|
||||||
variant="outline"
|
|
||||||
colorScheme="gray"
|
|
||||||
fontSize="xs"
|
|
||||||
px={2}
|
|
||||||
>
|
|
||||||
{tag}
|
|
||||||
</Badge>
|
|
||||||
))}
|
|
||||||
</HStack>
|
</HStack>
|
||||||
<Text fontSize="sm" color={labelColor}>
|
|
||||||
更新时间:{data.updateTime}
|
{/* 右侧:关注 + 分享 + 时间 */}
|
||||||
|
<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>
|
||||||
|
</HStack>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
{/* 三栏布局 */}
|
{/* 三栏布局 */}
|
||||||
@@ -106,66 +138,85 @@ const StockQuoteCard: React.FC<StockQuoteCardProps> = ({
|
|||||||
{/* 左栏:价格信息 */}
|
{/* 左栏:价格信息 */}
|
||||||
<Box flex="1">
|
<Box flex="1">
|
||||||
<HStack align="baseline" spacing={3} mb={3}>
|
<HStack align="baseline" spacing={3} mb={3}>
|
||||||
<Text fontSize="3xl" fontWeight="bold" color={priceColor}>
|
<Text fontSize="48px" fontWeight="bold" color={priceColor}>
|
||||||
{formatPrice(data.currentPrice)}
|
{formatPrice(data.currentPrice)}
|
||||||
</Text>
|
</Text>
|
||||||
<Badge
|
<Badge
|
||||||
colorScheme={data.changePercent >= 0 ? 'green' : 'red'}
|
bg={data.changePercent >= 0 ? upColor : downColor}
|
||||||
fontSize="md"
|
color="#FFFFFF"
|
||||||
px={2}
|
fontSize="20px"
|
||||||
py={0.5}
|
fontWeight="bold"
|
||||||
|
px={3}
|
||||||
|
py={1}
|
||||||
|
borderRadius="md"
|
||||||
>
|
>
|
||||||
{formatChangePercent(data.changePercent)}
|
{formatChangePercent(data.changePercent)}
|
||||||
</Badge>
|
</Badge>
|
||||||
</HStack>
|
</HStack>
|
||||||
<HStack spacing={6} color={labelColor} fontSize="sm">
|
{/* 次要行情:今开 | 昨收 | 最高 | 最低 */}
|
||||||
<Text>
|
<HStack spacing={4} fontSize="14px" flexWrap="wrap">
|
||||||
|
<Text color={labelColor}>
|
||||||
今开:
|
今开:
|
||||||
<Text as="span" color={valueColor} fontWeight="medium">
|
<Text as="span" color={valueColor} fontWeight="bold">
|
||||||
{formatPrice(data.todayOpen)}
|
{formatPrice(data.todayOpen)}
|
||||||
</Text>
|
</Text>
|
||||||
</Text>
|
</Text>
|
||||||
<Text>
|
<Text color={borderColor}>|</Text>
|
||||||
|
<Text color={labelColor}>
|
||||||
昨收:
|
昨收:
|
||||||
<Text as="span" color={valueColor} fontWeight="medium">
|
<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>
|
||||||
|
|
||||||
{/* 中栏:关键指标 */}
|
{/* 中栏:关键指标 */}
|
||||||
<Box flex="1" borderLeftWidth="1px" borderColor={borderColor} pl={8}>
|
<Box flex="1" borderLeftWidth="1px" borderColor={borderColor} pl={8}>
|
||||||
<Text
|
<Text
|
||||||
fontSize="sm"
|
fontSize="14px"
|
||||||
fontWeight="medium"
|
fontWeight="bold"
|
||||||
color={sectionTitleColor}
|
color={sectionTitleColor}
|
||||||
mb={3}
|
mb={3}
|
||||||
>
|
>
|
||||||
关键指标
|
关键指标
|
||||||
</Text>
|
</Text>
|
||||||
<VStack align="stretch" spacing={2} fontSize="sm">
|
<VStack align="stretch" spacing={2} fontSize="14px">
|
||||||
<HStack justify="space-between">
|
<HStack justify="space-between">
|
||||||
<Text color={labelColor}>市盈率(PE):</Text>
|
<Text color={labelColor}>市盈率(PE):</Text>
|
||||||
<Text color={valueColor} fontWeight="medium">
|
<Text color={valueColor} fontWeight="bold" fontSize="16px">
|
||||||
{data.pe.toFixed(2)}
|
{data.pe.toFixed(2)}
|
||||||
</Text>
|
</Text>
|
||||||
</HStack>
|
</HStack>
|
||||||
<HStack justify="space-between">
|
<HStack justify="space-between">
|
||||||
<Text color={labelColor}>市净率(PB):</Text>
|
<Text color={labelColor}>市净率(PB):</Text>
|
||||||
<Text color={valueColor} fontWeight="medium">
|
<Text color={valueColor} fontWeight="bold" fontSize="16px">
|
||||||
{data.pb.toFixed(2)}
|
{data.pb.toFixed(2)}
|
||||||
</Text>
|
</Text>
|
||||||
</HStack>
|
</HStack>
|
||||||
<HStack justify="space-between">
|
<HStack justify="space-between">
|
||||||
<Text color={labelColor}>流通市值:</Text>
|
<Text color={labelColor}>流通市值:</Text>
|
||||||
<Text color={valueColor} fontWeight="medium">
|
<Text color={valueColor} fontWeight="bold" fontSize="16px">
|
||||||
{data.marketCap}
|
{data.marketCap}
|
||||||
</Text>
|
</Text>
|
||||||
</HStack>
|
</HStack>
|
||||||
<HStack justify="space-between">
|
<HStack justify="space-between">
|
||||||
<Text color={labelColor}>52周波动:</Text>
|
<Text color={labelColor}>52周波动:</Text>
|
||||||
<Text color={valueColor} fontWeight="medium">
|
<Text color={valueColor} fontWeight="bold" fontSize="16px">
|
||||||
{formatPrice(data.week52Low)}-{formatPrice(data.week52High)}
|
{formatPrice(data.week52Low)}-{formatPrice(data.week52High)}
|
||||||
</Text>
|
</Text>
|
||||||
</HStack>
|
</HStack>
|
||||||
@@ -175,23 +226,23 @@ const StockQuoteCard: React.FC<StockQuoteCardProps> = ({
|
|||||||
{/* 右栏:主力动态 */}
|
{/* 右栏:主力动态 */}
|
||||||
<Box flex="1" borderLeftWidth="1px" borderColor={borderColor} pl={8}>
|
<Box flex="1" borderLeftWidth="1px" borderColor={borderColor} pl={8}>
|
||||||
<Text
|
<Text
|
||||||
fontSize="sm"
|
fontSize="14px"
|
||||||
fontWeight="medium"
|
fontWeight="bold"
|
||||||
color={sectionTitleColor}
|
color={sectionTitleColor}
|
||||||
mb={3}
|
mb={3}
|
||||||
>
|
>
|
||||||
主力动态
|
主力动态
|
||||||
</Text>
|
</Text>
|
||||||
<VStack align="stretch" spacing={2} fontSize="sm">
|
<VStack align="stretch" spacing={2} fontSize="14px">
|
||||||
<HStack justify="space-between">
|
<HStack justify="space-between">
|
||||||
<Text color={labelColor}>主力净流入:</Text>
|
<Text color={labelColor}>主力净流入:</Text>
|
||||||
<Text color={inflowColor} fontWeight="medium">
|
<Text color={inflowColor} fontWeight="bold" fontSize="16px">
|
||||||
{formatNetInflow(data.mainNetInflow)}
|
{formatNetInflow(data.mainNetInflow)}
|
||||||
</Text>
|
</Text>
|
||||||
</HStack>
|
</HStack>
|
||||||
<HStack justify="space-between">
|
<HStack justify="space-between">
|
||||||
<Text color={labelColor}>机构持仓:</Text>
|
<Text color={labelColor}>机构持仓:</Text>
|
||||||
<Text color={valueColor} fontWeight="medium">
|
<Text color={valueColor} fontWeight="bold" fontSize="16px">
|
||||||
{data.institutionHolding.toFixed(2)}%
|
{data.institutionHolding.toFixed(2)}%
|
||||||
</Text>
|
</Text>
|
||||||
</HStack>
|
</HStack>
|
||||||
@@ -200,13 +251,15 @@ const StockQuoteCard: React.FC<StockQuoteCardProps> = ({
|
|||||||
<Progress
|
<Progress
|
||||||
value={data.buyRatio}
|
value={data.buyRatio}
|
||||||
size="sm"
|
size="sm"
|
||||||
colorScheme="green"
|
sx={{
|
||||||
bg="red.400"
|
'& > div': { bg: upColor },
|
||||||
|
}}
|
||||||
|
bg={downColor}
|
||||||
borderRadius="full"
|
borderRadius="full"
|
||||||
/>
|
/>
|
||||||
<HStack justify="space-between" mt={1} fontSize="xs">
|
<HStack justify="space-between" mt={1} fontSize="14px">
|
||||||
<Text color="green.500">买入{data.buyRatio}%</Text>
|
<Text color={upColor}>买入{data.buyRatio}%</Text>
|
||||||
<Text color="red.500">卖出{data.sellRatio}%</Text>
|
<Text color={downColor}>卖出{data.sellRatio}%</Text>
|
||||||
</HStack>
|
</HStack>
|
||||||
</Box>
|
</Box>
|
||||||
</VStack>
|
</VStack>
|
||||||
|
|||||||
@@ -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,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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; // 分享回调
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,30 +64,26 @@ const CompanyIndex = () => {
|
|||||||
}, [stockCode, trackStockSearched]);
|
}, [stockCode, trackStockSearched]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container maxW="container.xl" py={5}>
|
<Container maxW="container.xl" py={0} bg='#1A202C'>
|
||||||
<VStack align="stretch" spacing={5}>
|
<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}
|
||||||
|
bgColor="#1A202C"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 股票行情卡片:价格、关键指标、主力动态、自选股按钮 */}
|
||||||
|
<StockQuoteCard
|
||||||
isInWatchlist={isInWatchlist}
|
isInWatchlist={isInWatchlist}
|
||||||
isWatchlistLoading={isWatchlistLoading}
|
isWatchlistLoading={isWatchlistLoading}
|
||||||
onWatchlistToggle={handleWatchlistToggle}
|
onWatchlistToggle={handleWatchlistToggle}
|
||||||
bgColor="white"
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* 股票行情卡片:价格、关键指标、主力动态 */}
|
|
||||||
<StockQuoteCard />
|
|
||||||
|
|
||||||
{/* Tab 切换区域:概览、行情、财务、预测 */}
|
{/* Tab 切换区域:概览、行情、财务、预测 */}
|
||||||
<CompanyTabs
|
<CompanyTabs stockCode={stockCode} onTabChange={trackTabChanged} bgColor="#1A202C"/>
|
||||||
stockCode={stockCode}
|
|
||||||
onTabChange={trackTabChanged}
|
|
||||||
bgColor="white"
|
|
||||||
/>
|
|
||||||
</VStack>
|
</VStack>
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user