feat(MarketDashboard): 新增投资仪表盘组件
- 指数卡片组件(带迷你面积图) - 成交额柱状图、涨跌分布图组件 - 热门板块排行组件 - 毛玻璃背景,黑金配色 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,50 @@
|
|||||||
|
// 市场概览仪表盘主组件 - 投资仪表盘
|
||||||
|
import React from 'react';
|
||||||
|
import { Box, Text, HStack, Icon } from '@chakra-ui/react';
|
||||||
|
import { TrendingUp } from 'lucide-react';
|
||||||
|
import GlassCard from '@components/GlassCard';
|
||||||
|
import { MarketOverview } from './components';
|
||||||
|
import { MOCK_INDICES, MOCK_MARKET_STATS } from './constants';
|
||||||
|
|
||||||
|
const MarketDashboard = ({
|
||||||
|
indices = MOCK_INDICES,
|
||||||
|
marketStats = MOCK_MARKET_STATS,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<GlassCard
|
||||||
|
variant="transparent"
|
||||||
|
rounded="2xl"
|
||||||
|
padding="md"
|
||||||
|
hoverable={false}
|
||||||
|
cornerDecor
|
||||||
|
>
|
||||||
|
{/* 标题栏 */}
|
||||||
|
<HStack mb={4} spacing={2}>
|
||||||
|
<Icon
|
||||||
|
as={TrendingUp}
|
||||||
|
boxSize={5}
|
||||||
|
color="rgba(212, 175, 55, 0.9)"
|
||||||
|
/>
|
||||||
|
<Text
|
||||||
|
fontSize="lg"
|
||||||
|
fontWeight="bold"
|
||||||
|
color="rgba(255, 255, 255, 0.95)"
|
||||||
|
letterSpacing="wide"
|
||||||
|
>
|
||||||
|
投资仪表盘
|
||||||
|
</Text>
|
||||||
|
<Box
|
||||||
|
h="1px"
|
||||||
|
flex={1}
|
||||||
|
bgGradient="linear(to-r, rgba(212, 175, 55, 0.4), transparent)"
|
||||||
|
ml={2}
|
||||||
|
/>
|
||||||
|
</HStack>
|
||||||
|
|
||||||
|
{/* 市场概况:指数卡片 + 成交额 + 涨跌分布 + 热门板块 */}
|
||||||
|
<MarketOverview indices={indices} marketStats={marketStats} />
|
||||||
|
</GlassCard>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MarketDashboard;
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
// 热点概念组件
|
||||||
|
import React from 'react';
|
||||||
|
import { Box, Text, VStack, SimpleGrid, HStack, Icon } from '@chakra-ui/react';
|
||||||
|
import { Flame } from 'lucide-react';
|
||||||
|
import { ConceptItem } from './atoms';
|
||||||
|
import { THEME } from '../constants';
|
||||||
|
|
||||||
|
const HotConcepts = ({ concepts = [], onConceptClick }) => {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
borderRadius="xl"
|
||||||
|
p={4}
|
||||||
|
h="100%"
|
||||||
|
>
|
||||||
|
<VStack align="stretch" spacing={3}>
|
||||||
|
{/* 标题 */}
|
||||||
|
<HStack spacing={2}>
|
||||||
|
<Icon as={Flame} boxSize={4} color={THEME.status.up} />
|
||||||
|
<Text
|
||||||
|
fontSize="sm"
|
||||||
|
fontWeight="bold"
|
||||||
|
color="rgba(255, 255, 255, 0.9)"
|
||||||
|
>
|
||||||
|
热点概念
|
||||||
|
</Text>
|
||||||
|
</HStack>
|
||||||
|
|
||||||
|
{/* 概念列表 */}
|
||||||
|
<SimpleGrid columns={{ base: 1, sm: 2 }} spacing={2}>
|
||||||
|
{concepts.map((concept) => (
|
||||||
|
<ConceptItem
|
||||||
|
key={concept.id}
|
||||||
|
name={concept.name}
|
||||||
|
change={concept.change}
|
||||||
|
trend={concept.trend}
|
||||||
|
onClick={() => onConceptClick?.(concept)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</SimpleGrid>
|
||||||
|
</VStack>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default HotConcepts;
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
// 市场概况组件 - 顶部横条(匹配设计图布局)
|
||||||
|
import React from 'react';
|
||||||
|
import { Box, SimpleGrid } from '@chakra-ui/react';
|
||||||
|
import {
|
||||||
|
IndexChartCard,
|
||||||
|
TurnoverChart,
|
||||||
|
RiseFallChart,
|
||||||
|
HotSectorsRanking,
|
||||||
|
} from './atoms';
|
||||||
|
|
||||||
|
const MarketOverview = ({ indices = [], marketStats = {} }) => {
|
||||||
|
// 默认指数数据(带图表数据)
|
||||||
|
const defaultIndices = [
|
||||||
|
{
|
||||||
|
code: 'sh000001',
|
||||||
|
name: '上证指数',
|
||||||
|
value: 3391.88,
|
||||||
|
change: 1.23,
|
||||||
|
chartData: [3350, 3360, 3355, 3370, 3365, 3380, 3375, 3390, 3385, 3392],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'sz399001',
|
||||||
|
name: '深证成指',
|
||||||
|
value: 10728.54,
|
||||||
|
change: 0.86,
|
||||||
|
chartData: [10650, 10680, 10660, 10700, 10690, 10720, 10710, 10730, 10720, 10728],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: 'sz399006',
|
||||||
|
name: '创业板指',
|
||||||
|
value: 2156.32,
|
||||||
|
change: -0.45,
|
||||||
|
chartData: [2180, 2175, 2170, 2165, 2168, 2160, 2165, 2158, 2160, 2156],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const displayIndices = indices.length > 0 ? indices : defaultIndices;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box borderRadius="xl">
|
||||||
|
{/* 6列网格布局:3个指数卡片 + 成交额 + 涨跌分布 + 热门板块 */}
|
||||||
|
<SimpleGrid
|
||||||
|
columns={{ base: 2, md: 3, lg: 6 }}
|
||||||
|
spacing={3}
|
||||||
|
>
|
||||||
|
{/* 指数卡片(带迷你图表) */}
|
||||||
|
{displayIndices.map((index) => (
|
||||||
|
<IndexChartCard
|
||||||
|
key={index.code}
|
||||||
|
name={index.name}
|
||||||
|
value={index.value}
|
||||||
|
change={index.change}
|
||||||
|
chartData={index.chartData || []}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{/* 成交额柱状图 */}
|
||||||
|
<TurnoverChart
|
||||||
|
data={marketStats.turnoverData || []}
|
||||||
|
title="成交额"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 涨跌分布图 */}
|
||||||
|
<RiseFallChart
|
||||||
|
riseCount={marketStats.riseCount || 2156}
|
||||||
|
fallCount={marketStats.fallCount || 2034}
|
||||||
|
flatCount={marketStats.flatCount || 312}
|
||||||
|
title="涨跌分布"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 热门板块排行 */}
|
||||||
|
<HotSectorsRanking
|
||||||
|
sectors={marketStats.hotSectors || []}
|
||||||
|
title="热门板块"
|
||||||
|
/>
|
||||||
|
</SimpleGrid>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MarketOverview;
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
// AI平台能力统计组件 - 底部横条
|
||||||
|
import React from 'react';
|
||||||
|
import { Box, HStack, Divider } from '@chakra-ui/react';
|
||||||
|
import { StatItem } from './atoms';
|
||||||
|
import { THEME } from '../constants';
|
||||||
|
|
||||||
|
const PlatformStats = ({ stats = [] }) => {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
borderRadius="xl"
|
||||||
|
py={4}
|
||||||
|
px={6}
|
||||||
|
>
|
||||||
|
<HStack justify="space-around" divider={
|
||||||
|
<Divider
|
||||||
|
orientation="vertical"
|
||||||
|
h="40px"
|
||||||
|
borderColor="rgba(212, 175, 55, 0.2)"
|
||||||
|
/>
|
||||||
|
}>
|
||||||
|
{stats.map((stat, index) => (
|
||||||
|
<StatItem
|
||||||
|
key={index}
|
||||||
|
icon={stat.icon}
|
||||||
|
value={stat.value}
|
||||||
|
label={stat.label}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</HStack>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PlatformStats;
|
||||||
@@ -0,0 +1,179 @@
|
|||||||
|
// 交易日历组件
|
||||||
|
import React, { useState, useMemo } from 'react';
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Text,
|
||||||
|
VStack,
|
||||||
|
HStack,
|
||||||
|
Grid,
|
||||||
|
GridItem,
|
||||||
|
IconButton,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
|
import { ChevronLeft, ChevronRight, Calendar } from 'lucide-react';
|
||||||
|
import { DayCell } from './atoms';
|
||||||
|
import { THEME, WEEKDAY_LABELS } from '../constants';
|
||||||
|
|
||||||
|
const TradingCalendar = ({ tradingDays = [] }) => {
|
||||||
|
const [currentDate, setCurrentDate] = useState(new Date());
|
||||||
|
|
||||||
|
const calendarData = useMemo(() => {
|
||||||
|
const year = currentDate.getFullYear();
|
||||||
|
const month = currentDate.getMonth();
|
||||||
|
|
||||||
|
// 当月第一天
|
||||||
|
const firstDay = new Date(year, month, 1);
|
||||||
|
// 当月最后一天
|
||||||
|
const lastDay = new Date(year, month + 1, 0);
|
||||||
|
// 第一天是星期几
|
||||||
|
const startWeekday = firstDay.getDay();
|
||||||
|
// 当月天数
|
||||||
|
const daysInMonth = lastDay.getDate();
|
||||||
|
|
||||||
|
// 上月最后几天
|
||||||
|
const prevMonthLastDay = new Date(year, month, 0).getDate();
|
||||||
|
|
||||||
|
const days = [];
|
||||||
|
|
||||||
|
// 填充上月日期
|
||||||
|
for (let i = startWeekday - 1; i >= 0; i--) {
|
||||||
|
days.push({
|
||||||
|
day: prevMonthLastDay - i,
|
||||||
|
isCurrentMonth: false,
|
||||||
|
isWeekend: false,
|
||||||
|
isTrading: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 填充当月日期
|
||||||
|
const today = new Date();
|
||||||
|
for (let day = 1; day <= daysInMonth; day++) {
|
||||||
|
const date = new Date(year, month, day);
|
||||||
|
const weekday = date.getDay();
|
||||||
|
const isWeekend = weekday === 0 || weekday === 6;
|
||||||
|
const isToday =
|
||||||
|
day === today.getDate() &&
|
||||||
|
month === today.getMonth() &&
|
||||||
|
year === today.getFullYear();
|
||||||
|
|
||||||
|
// 检查是否为交易日(简化逻辑:非周末即交易日)
|
||||||
|
// 实际应用中应该从 tradingDays 数组判断
|
||||||
|
const dateStr = `${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
|
||||||
|
const isTrading = tradingDays.length > 0
|
||||||
|
? tradingDays.includes(dateStr)
|
||||||
|
: !isWeekend;
|
||||||
|
|
||||||
|
days.push({
|
||||||
|
day,
|
||||||
|
isCurrentMonth: true,
|
||||||
|
isWeekend,
|
||||||
|
isTrading,
|
||||||
|
isToday,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 填充下月日期(补满 6 行 * 7 天 = 42 格)
|
||||||
|
const remaining = 42 - days.length;
|
||||||
|
for (let day = 1; day <= remaining; day++) {
|
||||||
|
days.push({
|
||||||
|
day,
|
||||||
|
isCurrentMonth: false,
|
||||||
|
isWeekend: false,
|
||||||
|
isTrading: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return days;
|
||||||
|
}, [currentDate, tradingDays]);
|
||||||
|
|
||||||
|
const handlePrevMonth = () => {
|
||||||
|
setCurrentDate(new Date(currentDate.getFullYear(), currentDate.getMonth() - 1, 1));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleNextMonth = () => {
|
||||||
|
setCurrentDate(new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, 1));
|
||||||
|
};
|
||||||
|
|
||||||
|
const monthText = `${currentDate.getFullYear()}年${currentDate.getMonth() + 1}月`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
borderRadius="xl"
|
||||||
|
p={4}
|
||||||
|
h="100%"
|
||||||
|
>
|
||||||
|
<VStack align="stretch" spacing={3}>
|
||||||
|
{/* 日历头部 */}
|
||||||
|
<HStack justify="space-between">
|
||||||
|
<HStack spacing={2}>
|
||||||
|
<Calendar size={16} color={THEME.text.gold} />
|
||||||
|
<Text fontSize="sm" fontWeight="bold" color="rgba(255, 255, 255, 0.9)">
|
||||||
|
交易日历
|
||||||
|
</Text>
|
||||||
|
</HStack>
|
||||||
|
<HStack spacing={1}>
|
||||||
|
<IconButton
|
||||||
|
icon={<ChevronLeft size={16} />}
|
||||||
|
size="xs"
|
||||||
|
variant="ghost"
|
||||||
|
color="rgba(255, 255, 255, 0.6)"
|
||||||
|
onClick={handlePrevMonth}
|
||||||
|
aria-label="上月"
|
||||||
|
_hover={{ bg: 'rgba(212, 175, 55, 0.15)' }}
|
||||||
|
/>
|
||||||
|
<Text fontSize="xs" color="rgba(255, 255, 255, 0.9)" minW="70px" textAlign="center">
|
||||||
|
{monthText}
|
||||||
|
</Text>
|
||||||
|
<IconButton
|
||||||
|
icon={<ChevronRight size={16} />}
|
||||||
|
size="xs"
|
||||||
|
variant="ghost"
|
||||||
|
color="rgba(255, 255, 255, 0.6)"
|
||||||
|
onClick={handleNextMonth}
|
||||||
|
aria-label="下月"
|
||||||
|
_hover={{ bg: 'rgba(212, 175, 55, 0.15)' }}
|
||||||
|
/>
|
||||||
|
</HStack>
|
||||||
|
</HStack>
|
||||||
|
|
||||||
|
{/* 星期标题 */}
|
||||||
|
<Grid templateColumns="repeat(7, 1fr)" gap={0}>
|
||||||
|
{WEEKDAY_LABELS.map((label, index) => (
|
||||||
|
<GridItem key={label}>
|
||||||
|
<Text
|
||||||
|
fontSize="xs"
|
||||||
|
color="rgba(255, 255, 255, 0.5)"
|
||||||
|
textAlign="center"
|
||||||
|
py={1}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</Text>
|
||||||
|
</GridItem>
|
||||||
|
))}
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
{/* 日期网格 */}
|
||||||
|
<Grid templateColumns="repeat(7, 1fr)" gap={0}>
|
||||||
|
{calendarData.map((dayData, index) => (
|
||||||
|
<GridItem
|
||||||
|
key={index}
|
||||||
|
display="flex"
|
||||||
|
justifyContent="center"
|
||||||
|
alignItems="center"
|
||||||
|
py={0.5}
|
||||||
|
>
|
||||||
|
<DayCell
|
||||||
|
day={dayData.day}
|
||||||
|
isTrading={dayData.isTrading}
|
||||||
|
isToday={dayData.isToday || false}
|
||||||
|
isWeekend={dayData.isWeekend}
|
||||||
|
isCurrentMonth={dayData.isCurrentMonth}
|
||||||
|
/>
|
||||||
|
</GridItem>
|
||||||
|
))}
|
||||||
|
</Grid>
|
||||||
|
</VStack>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TradingCalendar;
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
// 概念项组件
|
||||||
|
import React from 'react';
|
||||||
|
import { Box, Text, HStack } from '@chakra-ui/react';
|
||||||
|
import { THEME } from '../../constants';
|
||||||
|
import MiniTrendLine from './MiniTrendLine';
|
||||||
|
|
||||||
|
const ConceptItem = ({ name, change, trend = [], onClick }) => {
|
||||||
|
const isUp = change >= 0;
|
||||||
|
const changeColor = isUp ? THEME.status.up : THEME.status.down;
|
||||||
|
const changeText = isUp ? `+${change.toFixed(2)}%` : `${change.toFixed(2)}%`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
bg="rgba(26, 26, 46, 0.5)"
|
||||||
|
borderRadius="md"
|
||||||
|
px={3}
|
||||||
|
py={2}
|
||||||
|
cursor={onClick ? 'pointer' : 'default'}
|
||||||
|
transition="all 0.2s"
|
||||||
|
border="1px solid"
|
||||||
|
borderColor="rgba(212, 175, 55, 0.1)"
|
||||||
|
backdropFilter="blur(8px)"
|
||||||
|
_hover={{
|
||||||
|
bg: 'rgba(37, 37, 64, 0.6)',
|
||||||
|
borderColor: 'rgba(212, 175, 55, 0.25)',
|
||||||
|
}}
|
||||||
|
onClick={onClick}
|
||||||
|
>
|
||||||
|
<HStack justify="space-between">
|
||||||
|
<Text fontSize="sm" color="rgba(255, 255, 255, 0.9)" fontWeight="medium">
|
||||||
|
{name}
|
||||||
|
</Text>
|
||||||
|
<HStack spacing={2}>
|
||||||
|
{trend.length > 0 && (
|
||||||
|
<MiniTrendLine
|
||||||
|
data={trend}
|
||||||
|
color={isUp ? 'red' : 'green'}
|
||||||
|
width={36}
|
||||||
|
height={16}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Text
|
||||||
|
fontSize="sm"
|
||||||
|
color={changeColor}
|
||||||
|
fontWeight="medium"
|
||||||
|
minW="55px"
|
||||||
|
textAlign="right"
|
||||||
|
>
|
||||||
|
{changeText}
|
||||||
|
</Text>
|
||||||
|
</HStack>
|
||||||
|
</HStack>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ConceptItem;
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
// 日期单元格组件
|
||||||
|
import React from 'react';
|
||||||
|
import { Box, Text } from '@chakra-ui/react';
|
||||||
|
import { THEME } from '../../constants';
|
||||||
|
|
||||||
|
const DayCell = ({
|
||||||
|
day,
|
||||||
|
isTrading = true,
|
||||||
|
isToday = false,
|
||||||
|
isWeekend = false,
|
||||||
|
isCurrentMonth = true,
|
||||||
|
}) => {
|
||||||
|
// 今天的样式(金色背景)
|
||||||
|
if (isToday) {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
w="28px"
|
||||||
|
h="28px"
|
||||||
|
display="flex"
|
||||||
|
alignItems="center"
|
||||||
|
justifyContent="center"
|
||||||
|
borderRadius="full"
|
||||||
|
bg={THEME.text.gold}
|
||||||
|
boxShadow="0 0 8px rgba(212, 175, 55, 0.5)"
|
||||||
|
>
|
||||||
|
<Text fontSize="xs" color="#000" fontWeight="bold">
|
||||||
|
{day}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 非当月日期
|
||||||
|
if (!isCurrentMonth) {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
w="28px"
|
||||||
|
h="28px"
|
||||||
|
display="flex"
|
||||||
|
alignItems="center"
|
||||||
|
justifyContent="center"
|
||||||
|
>
|
||||||
|
<Text fontSize="xs" color="rgba(139, 149, 165, 0.3)">
|
||||||
|
{day}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 周末(非交易日)
|
||||||
|
if (isWeekend || !isTrading) {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
w="28px"
|
||||||
|
h="28px"
|
||||||
|
display="flex"
|
||||||
|
alignItems="center"
|
||||||
|
justifyContent="center"
|
||||||
|
>
|
||||||
|
<Text fontSize="xs" color="rgba(255, 255, 255, 0.3)">
|
||||||
|
{day}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 普通交易日
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
w="28px"
|
||||||
|
h="28px"
|
||||||
|
display="flex"
|
||||||
|
alignItems="center"
|
||||||
|
justifyContent="center"
|
||||||
|
borderRadius="md"
|
||||||
|
cursor="pointer"
|
||||||
|
transition="all 0.15s"
|
||||||
|
_hover={{ bg: 'rgba(212, 175, 55, 0.15)' }}
|
||||||
|
>
|
||||||
|
<Text fontSize="xs" color="rgba(255, 255, 255, 0.8)">
|
||||||
|
{day}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DayCell;
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
// 热门板块排行组件
|
||||||
|
import React from 'react';
|
||||||
|
import { Box, Text, VStack, HStack } from '@chakra-ui/react';
|
||||||
|
import MiniTrendLine from './MiniTrendLine';
|
||||||
|
|
||||||
|
const HotSectorsRanking = ({ sectors = [], title = '热门板块排行' }) => {
|
||||||
|
// 默认数据
|
||||||
|
const defaultSectors = [
|
||||||
|
{ rank: 1, name: '人工智能', change: 3.2, trend: [100, 102, 101, 104, 103, 106] },
|
||||||
|
{ rank: 2, name: '新能源车', change: 1.8, trend: [100, 99, 101, 102, 101, 103] },
|
||||||
|
{ rank: 3, name: '生物医药', change: 1.3, trend: [100, 101, 100, 102, 101, 102] },
|
||||||
|
{ rank: 4, name: '消费科技', change: 1.2, trend: [100, 100, 101, 100, 102, 102] },
|
||||||
|
{ rank: 5, name: '其他', change: 0.4, trend: [100, 100, 100, 101, 100, 101] },
|
||||||
|
];
|
||||||
|
|
||||||
|
const data = sectors.length > 0 ? sectors : defaultSectors;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
bg="rgba(26, 26, 46, 0.7)"
|
||||||
|
borderRadius="lg"
|
||||||
|
p={3}
|
||||||
|
minW="180px"
|
||||||
|
border="1px solid"
|
||||||
|
borderColor="rgba(212, 175, 55, 0.15)"
|
||||||
|
backdropFilter="blur(8px)"
|
||||||
|
>
|
||||||
|
<VStack align="stretch" spacing={2}>
|
||||||
|
<Text fontSize="xs" color="rgba(255, 255, 255, 0.6)" fontWeight="medium">
|
||||||
|
{title}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
{/* 排行列表 */}
|
||||||
|
<VStack spacing={1.5} align="stretch">
|
||||||
|
{data.map((sector, index) => (
|
||||||
|
<HStack key={index} justify="space-between" fontSize="xs">
|
||||||
|
{/* 排名 */}
|
||||||
|
<HStack spacing={2} flex={1}>
|
||||||
|
<Text
|
||||||
|
color={index < 3 ? 'rgba(212, 175, 55, 0.9)' : 'rgba(255, 255, 255, 0.5)'}
|
||||||
|
fontWeight={index < 3 ? 'bold' : 'normal'}
|
||||||
|
w="16px"
|
||||||
|
>
|
||||||
|
{sector.rank}
|
||||||
|
</Text>
|
||||||
|
<Text color="rgba(255, 255, 255, 0.85)" noOfLines={1}>
|
||||||
|
{sector.name}
|
||||||
|
</Text>
|
||||||
|
</HStack>
|
||||||
|
|
||||||
|
{/* 趋势线 */}
|
||||||
|
<Box w="40px">
|
||||||
|
<MiniTrendLine
|
||||||
|
data={sector.trend}
|
||||||
|
color={sector.change >= 0 ? 'red' : 'green'}
|
||||||
|
width={40}
|
||||||
|
height={14}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* 涨跌幅 */}
|
||||||
|
<Text
|
||||||
|
color={sector.change >= 0 ? '#EF4444' : '#22C55E'}
|
||||||
|
fontWeight="medium"
|
||||||
|
w="50px"
|
||||||
|
textAlign="right"
|
||||||
|
>
|
||||||
|
{sector.change >= 0 ? '+' : ''}{sector.change.toFixed(1)}%
|
||||||
|
</Text>
|
||||||
|
</HStack>
|
||||||
|
))}
|
||||||
|
</VStack>
|
||||||
|
</VStack>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default HotSectorsRanking;
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
// 指数卡片组件
|
||||||
|
import React from 'react';
|
||||||
|
import { Box, Text, HStack, VStack } from '@chakra-ui/react';
|
||||||
|
import { THEME } from '../../constants';
|
||||||
|
import MiniTrendLine from './MiniTrendLine';
|
||||||
|
|
||||||
|
const IndexCard = ({ name, value, change, trend = [] }) => {
|
||||||
|
const isUp = change >= 0;
|
||||||
|
const changeColor = isUp ? THEME.status.up : THEME.status.down;
|
||||||
|
const changeText = isUp ? `+${change.toFixed(2)}%` : `${change.toFixed(2)}%`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
bg="rgba(26, 26, 46, 0.6)"
|
||||||
|
borderRadius="lg"
|
||||||
|
p={3}
|
||||||
|
minW="120px"
|
||||||
|
transition="all 0.2s"
|
||||||
|
border="1px solid"
|
||||||
|
borderColor="rgba(212, 175, 55, 0.15)"
|
||||||
|
backdropFilter="blur(8px)"
|
||||||
|
_hover={{
|
||||||
|
bg: 'rgba(37, 37, 64, 0.7)',
|
||||||
|
borderColor: 'rgba(212, 175, 55, 0.3)',
|
||||||
|
boxShadow: '0 0 12px rgba(212, 175, 55, 0.2)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<VStack align="flex-start" spacing={1}>
|
||||||
|
<Text fontSize="xs" color="rgba(255, 255, 255, 0.6)">
|
||||||
|
{name}
|
||||||
|
</Text>
|
||||||
|
<HStack justify="space-between" w="100%" align="flex-end">
|
||||||
|
<VStack align="flex-start" spacing={0}>
|
||||||
|
<Text fontSize="lg" fontWeight="bold" color="rgba(255, 255, 255, 0.95)">
|
||||||
|
{value.toLocaleString()}
|
||||||
|
</Text>
|
||||||
|
<Text fontSize="xs" color={changeColor} fontWeight="medium">
|
||||||
|
{changeText}
|
||||||
|
</Text>
|
||||||
|
</VStack>
|
||||||
|
{trend.length > 0 && (
|
||||||
|
<MiniTrendLine
|
||||||
|
data={trend}
|
||||||
|
color={isUp ? 'red' : 'green'}
|
||||||
|
width={40}
|
||||||
|
height={20}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</HStack>
|
||||||
|
</VStack>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default IndexCard;
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
// 指数图表卡片 - 带迷你K线图
|
||||||
|
import React, { useMemo } from 'react';
|
||||||
|
import { Box, Text, VStack, HStack } from '@chakra-ui/react';
|
||||||
|
|
||||||
|
const IndexChartCard = ({ name, value, change, chartData = [] }) => {
|
||||||
|
const isUp = change >= 0;
|
||||||
|
const changeColor = isUp ? '#EF4444' : '#22C55E';
|
||||||
|
const changeText = isUp ? `+${change.toFixed(2)}%` : `${change.toFixed(2)}%`;
|
||||||
|
|
||||||
|
// 生成迷你图表路径
|
||||||
|
const chartPath = useMemo(() => {
|
||||||
|
if (!chartData || chartData.length < 2) return '';
|
||||||
|
|
||||||
|
const width = 120;
|
||||||
|
const height = 40;
|
||||||
|
const padding = 4;
|
||||||
|
|
||||||
|
const min = Math.min(...chartData);
|
||||||
|
const max = Math.max(...chartData);
|
||||||
|
const range = max - min || 1;
|
||||||
|
|
||||||
|
const points = chartData.map((val, i) => {
|
||||||
|
const x = padding + (i / (chartData.length - 1)) * (width - padding * 2);
|
||||||
|
const y = height - padding - ((val - min) / range) * (height - padding * 2);
|
||||||
|
return `${x},${y}`;
|
||||||
|
});
|
||||||
|
|
||||||
|
return `M ${points.join(' L ')}`;
|
||||||
|
}, [chartData]);
|
||||||
|
|
||||||
|
// 生成填充区域
|
||||||
|
const areaPath = useMemo(() => {
|
||||||
|
if (!chartPath) return '';
|
||||||
|
const width = 120;
|
||||||
|
const height = 40;
|
||||||
|
return `${chartPath} L ${width - 4},${height - 4} L 4,${height - 4} Z`;
|
||||||
|
}, [chartPath]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
bg="rgba(26, 26, 46, 0.7)"
|
||||||
|
borderRadius="lg"
|
||||||
|
p={3}
|
||||||
|
minW="160px"
|
||||||
|
border="1px solid"
|
||||||
|
borderColor="rgba(212, 175, 55, 0.15)"
|
||||||
|
backdropFilter="blur(8px)"
|
||||||
|
transition="all 0.2s"
|
||||||
|
_hover={{
|
||||||
|
borderColor: 'rgba(212, 175, 55, 0.3)',
|
||||||
|
boxShadow: '0 0 12px rgba(212, 175, 55, 0.15)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<VStack align="stretch" spacing={2}>
|
||||||
|
{/* 标题 */}
|
||||||
|
<Text fontSize="xs" color="rgba(255, 255, 255, 0.6)" fontWeight="medium">
|
||||||
|
{name}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
{/* 数值和涨跌幅 */}
|
||||||
|
<HStack justify="space-between" align="baseline">
|
||||||
|
<Text fontSize="lg" fontWeight="bold" color={changeColor}>
|
||||||
|
{typeof value === 'number' ? value.toLocaleString(undefined, { minimumFractionDigits: 2 }) : value}
|
||||||
|
</Text>
|
||||||
|
<Text fontSize="sm" color={changeColor} fontWeight="medium">
|
||||||
|
{changeText}
|
||||||
|
</Text>
|
||||||
|
</HStack>
|
||||||
|
|
||||||
|
{/* 迷你图表 */}
|
||||||
|
{chartData.length > 0 && (
|
||||||
|
<Box h="40px" w="100%">
|
||||||
|
<svg width="100%" height="40" viewBox="0 0 120 40" preserveAspectRatio="none">
|
||||||
|
{/* 填充区域 */}
|
||||||
|
<path
|
||||||
|
d={areaPath}
|
||||||
|
fill={isUp ? 'rgba(239, 68, 68, 0.1)' : 'rgba(34, 197, 94, 0.1)'}
|
||||||
|
/>
|
||||||
|
{/* 线条 */}
|
||||||
|
<path
|
||||||
|
d={chartPath}
|
||||||
|
fill="none"
|
||||||
|
stroke={changeColor}
|
||||||
|
strokeWidth="1.5"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</VStack>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default IndexChartCard;
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
// 迷你趋势线组件 - 基于 SVG
|
||||||
|
import React, { useMemo } from 'react';
|
||||||
|
import { Box } from '@chakra-ui/react';
|
||||||
|
import { THEME } from '../../constants';
|
||||||
|
|
||||||
|
const MiniTrendLine = ({
|
||||||
|
data = [],
|
||||||
|
color = 'green',
|
||||||
|
width = 60,
|
||||||
|
height = 24,
|
||||||
|
}) => {
|
||||||
|
const pathD = useMemo(() => {
|
||||||
|
if (!data || data.length < 2) return '';
|
||||||
|
|
||||||
|
const min = Math.min(...data);
|
||||||
|
const max = Math.max(...data);
|
||||||
|
const range = max - min || 1;
|
||||||
|
|
||||||
|
const points = data.map((value, index) => {
|
||||||
|
const x = (index / (data.length - 1)) * width;
|
||||||
|
const y = height - ((value - min) / range) * height * 0.8 - height * 0.1;
|
||||||
|
return `${x},${y}`;
|
||||||
|
});
|
||||||
|
|
||||||
|
return `M ${points.join(' L ')}`;
|
||||||
|
}, [data, width, height]);
|
||||||
|
|
||||||
|
const strokeColor = color === 'red' ? THEME.status.up : THEME.status.down;
|
||||||
|
|
||||||
|
if (!data || data.length < 2) {
|
||||||
|
return <Box w={`${width}px`} h={`${height}px`} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box w={`${width}px`} h={`${height}px`}>
|
||||||
|
<svg width={width} height={height} viewBox={`0 0 ${width} ${height}`}>
|
||||||
|
<path
|
||||||
|
d={pathD}
|
||||||
|
fill="none"
|
||||||
|
stroke={strokeColor}
|
||||||
|
strokeWidth="1.5"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MiniTrendLine;
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
// 涨跌分布图组件
|
||||||
|
import React from 'react';
|
||||||
|
import { Box, Text, VStack, HStack } from '@chakra-ui/react';
|
||||||
|
|
||||||
|
const RiseFallChart = ({
|
||||||
|
riseCount = 2156,
|
||||||
|
fallCount = 2034,
|
||||||
|
flatCount = 312,
|
||||||
|
title = '涨跌分布'
|
||||||
|
}) => {
|
||||||
|
const total = riseCount + fallCount + flatCount;
|
||||||
|
const risePercent = ((riseCount / total) * 100).toFixed(1);
|
||||||
|
const fallPercent = ((fallCount / total) * 100).toFixed(1);
|
||||||
|
const flatPercent = ((flatCount / total) * 100).toFixed(1);
|
||||||
|
|
||||||
|
// 分布数据 - 模拟不同涨跌幅区间
|
||||||
|
const distribution = [
|
||||||
|
{ range: '>7%', rise: 86, fall: 12, label: '涨停' },
|
||||||
|
{ range: '3-7%', rise: 420, fall: 180 },
|
||||||
|
{ range: '0-3%', rise: 1650, fall: 0 },
|
||||||
|
{ range: '-3-0%', rise: 0, fall: 1542 },
|
||||||
|
{ range: '-7--3%', rise: 0, fall: 280 },
|
||||||
|
{ range: '<-7%', rise: 0, fall: 20, label: '跌停' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const maxCount = Math.max(...distribution.map(d => Math.max(d.rise, d.fall)));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
bg="rgba(26, 26, 46, 0.7)"
|
||||||
|
borderRadius="lg"
|
||||||
|
p={3}
|
||||||
|
minW="160px"
|
||||||
|
border="1px solid"
|
||||||
|
borderColor="rgba(212, 175, 55, 0.15)"
|
||||||
|
backdropFilter="blur(8px)"
|
||||||
|
>
|
||||||
|
<VStack align="stretch" spacing={2}>
|
||||||
|
<Text fontSize="xs" color="rgba(255, 255, 255, 0.6)" fontWeight="medium">
|
||||||
|
{title}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
{/* 水平柱状图 */}
|
||||||
|
<VStack spacing={1} align="stretch">
|
||||||
|
{distribution.map((item, index) => (
|
||||||
|
<HStack key={index} spacing={1} h="12px">
|
||||||
|
{/* 涨(红色,向右) */}
|
||||||
|
<Box flex={1} display="flex" justifyContent="flex-end">
|
||||||
|
{item.rise > 0 && (
|
||||||
|
<Box
|
||||||
|
h="10px"
|
||||||
|
w={`${(item.rise / maxCount) * 100}%`}
|
||||||
|
bg="linear-gradient(90deg, rgba(239, 68, 68, 0.4) 0%, #EF4444 100%)"
|
||||||
|
borderRadius="sm"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
{/* 中心线 */}
|
||||||
|
<Box w="1px" h="10px" bg="rgba(255, 255, 255, 0.2)" />
|
||||||
|
{/* 跌(绿色,向左显示但实际向右) */}
|
||||||
|
<Box flex={1}>
|
||||||
|
{item.fall > 0 && (
|
||||||
|
<Box
|
||||||
|
h="10px"
|
||||||
|
w={`${(item.fall / maxCount) * 100}%`}
|
||||||
|
bg="linear-gradient(90deg, #22C55E 0%, rgba(34, 197, 94, 0.4) 100%)"
|
||||||
|
borderRadius="sm"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</HStack>
|
||||||
|
))}
|
||||||
|
</VStack>
|
||||||
|
|
||||||
|
{/* 统计数字 */}
|
||||||
|
<HStack justify="space-between" fontSize="xs">
|
||||||
|
<Text color="#EF4444" fontWeight="medium">
|
||||||
|
涨 {riseCount}
|
||||||
|
</Text>
|
||||||
|
<Text color="rgba(255, 255, 255, 0.5)">
|
||||||
|
平 {flatCount}
|
||||||
|
</Text>
|
||||||
|
<Text color="#22C55E" fontWeight="medium">
|
||||||
|
跌 {fallCount}
|
||||||
|
</Text>
|
||||||
|
</HStack>
|
||||||
|
</VStack>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RiseFallChart;
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
// 统计卡片组件(涨停/跌停/成交额)
|
||||||
|
import React from 'react';
|
||||||
|
import { Box, Text, VStack } from '@chakra-ui/react';
|
||||||
|
import { THEME } from '../../constants';
|
||||||
|
|
||||||
|
const StatCard = ({ label, value, subLabel, valueColor }) => {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
bg="rgba(26, 26, 46, 0.6)"
|
||||||
|
borderRadius="lg"
|
||||||
|
p={3}
|
||||||
|
minW="80px"
|
||||||
|
textAlign="center"
|
||||||
|
transition="all 0.2s"
|
||||||
|
border="1px solid"
|
||||||
|
borderColor="rgba(212, 175, 55, 0.15)"
|
||||||
|
backdropFilter="blur(8px)"
|
||||||
|
_hover={{
|
||||||
|
bg: 'rgba(37, 37, 64, 0.7)',
|
||||||
|
borderColor: 'rgba(212, 175, 55, 0.3)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<VStack spacing={0}>
|
||||||
|
<Text fontSize="xs" color="rgba(255, 255, 255, 0.6)">
|
||||||
|
{label}
|
||||||
|
</Text>
|
||||||
|
<Text
|
||||||
|
fontSize="xl"
|
||||||
|
fontWeight="bold"
|
||||||
|
color={valueColor || 'rgba(255, 255, 255, 0.95)'}
|
||||||
|
>
|
||||||
|
{value}
|
||||||
|
</Text>
|
||||||
|
{subLabel && (
|
||||||
|
<Text fontSize="xs" color="rgba(255, 255, 255, 0.5)">
|
||||||
|
{subLabel}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</VStack>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default StatCard;
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
// AI平台能力统计项组件
|
||||||
|
import React from 'react';
|
||||||
|
import { Box, Text, VStack, Icon } from '@chakra-ui/react';
|
||||||
|
import {
|
||||||
|
Building2,
|
||||||
|
BarChart3,
|
||||||
|
Calendar,
|
||||||
|
Bot,
|
||||||
|
TrendingUp,
|
||||||
|
Database,
|
||||||
|
} from 'lucide-react';
|
||||||
|
import { THEME } from '../../constants';
|
||||||
|
|
||||||
|
// 图标映射
|
||||||
|
const iconMap = {
|
||||||
|
building: Building2,
|
||||||
|
chart: BarChart3,
|
||||||
|
calendar: Calendar,
|
||||||
|
robot: Bot,
|
||||||
|
trending: TrendingUp,
|
||||||
|
database: Database,
|
||||||
|
};
|
||||||
|
|
||||||
|
const StatItem = ({ icon, value, label }) => {
|
||||||
|
const IconComponent = iconMap[icon] || Database;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box textAlign="center" px={4}>
|
||||||
|
<VStack spacing={1}>
|
||||||
|
<Icon
|
||||||
|
as={IconComponent}
|
||||||
|
boxSize={5}
|
||||||
|
color={THEME.text.gold}
|
||||||
|
mb={1}
|
||||||
|
/>
|
||||||
|
<Text fontSize="lg" fontWeight="bold" color="rgba(255, 255, 255, 0.95)">
|
||||||
|
{value}
|
||||||
|
</Text>
|
||||||
|
<Text fontSize="xs" color="rgba(255, 255, 255, 0.6)">
|
||||||
|
{label}
|
||||||
|
</Text>
|
||||||
|
</VStack>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default StatItem;
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
// 成交额柱状图组件
|
||||||
|
import React from 'react';
|
||||||
|
import { Box, Text, VStack, HStack } from '@chakra-ui/react';
|
||||||
|
|
||||||
|
const TurnoverChart = ({ data = [], title = '成交额' }) => {
|
||||||
|
// 默认数据
|
||||||
|
const chartData = data.length > 0 ? data : [
|
||||||
|
{ time: '10:30', value: 0.85 },
|
||||||
|
{ time: '11:00', value: 0.92 },
|
||||||
|
{ time: '11:15', value: 0.78 },
|
||||||
|
{ time: '13:00', value: 1.05 },
|
||||||
|
{ time: '13:30', value: 1.12 },
|
||||||
|
{ time: '14:00', value: 0.95 },
|
||||||
|
];
|
||||||
|
|
||||||
|
const maxValue = Math.max(...chartData.map(d => d.value));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
bg="rgba(26, 26, 46, 0.7)"
|
||||||
|
borderRadius="lg"
|
||||||
|
p={3}
|
||||||
|
minW="140px"
|
||||||
|
border="1px solid"
|
||||||
|
borderColor="rgba(212, 175, 55, 0.15)"
|
||||||
|
backdropFilter="blur(8px)"
|
||||||
|
>
|
||||||
|
<VStack align="stretch" spacing={2}>
|
||||||
|
<Text fontSize="xs" color="rgba(255, 255, 255, 0.6)" fontWeight="medium">
|
||||||
|
{title}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
{/* 柱状图 */}
|
||||||
|
<HStack spacing={1} align="flex-end" h="50px">
|
||||||
|
{chartData.map((item, index) => (
|
||||||
|
<Box
|
||||||
|
key={index}
|
||||||
|
flex={1}
|
||||||
|
h={`${(item.value / maxValue) * 100}%`}
|
||||||
|
bg="linear-gradient(180deg, rgba(212, 175, 55, 0.8) 0%, rgba(212, 175, 55, 0.4) 100%)"
|
||||||
|
borderRadius="sm"
|
||||||
|
minH="4px"
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</HStack>
|
||||||
|
|
||||||
|
{/* 当前值 */}
|
||||||
|
<Text fontSize="sm" fontWeight="bold" color="rgba(212, 175, 55, 0.9)">
|
||||||
|
1.25亿
|
||||||
|
</Text>
|
||||||
|
</VStack>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TurnoverChart;
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
// 原子组件导出
|
||||||
|
export { default as MiniTrendLine } from './MiniTrendLine';
|
||||||
|
export { default as IndexCard } from './IndexCard';
|
||||||
|
export { default as StatCard } from './StatCard';
|
||||||
|
export { default as ConceptItem } from './ConceptItem';
|
||||||
|
export { default as DayCell } from './DayCell';
|
||||||
|
export { default as StatItem } from './StatItem';
|
||||||
|
export { default as IndexChartCard } from './IndexChartCard';
|
||||||
|
export { default as TurnoverChart } from './TurnoverChart';
|
||||||
|
export { default as RiseFallChart } from './RiseFallChart';
|
||||||
|
export { default as HotSectorsRanking } from './HotSectorsRanking';
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
// 组件导出
|
||||||
|
export { default as MarketOverview } from './MarketOverview';
|
||||||
|
export { default as HotConcepts } from './HotConcepts';
|
||||||
|
export { default as TradingCalendar } from './TradingCalendar';
|
||||||
|
export { default as PlatformStats } from './PlatformStats';
|
||||||
|
export * from './atoms';
|
||||||
108
src/views/Profile/components/MarketDashboard/constants.ts
Normal file
108
src/views/Profile/components/MarketDashboard/constants.ts
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
// MarketDashboard 常量定义
|
||||||
|
|
||||||
|
// 黑金主题配色
|
||||||
|
export const THEME = {
|
||||||
|
bg: {
|
||||||
|
primary: '#0A0A0A', // 纯黑背景
|
||||||
|
card: '#141414', // 卡片背景
|
||||||
|
cardHover: '#1A1A1A', // 卡片悬停
|
||||||
|
gradient: 'linear-gradient(135deg, #1a1a1a 0%, #0a0a0a 100%)',
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
primary: '#FFFFFF', // 主文字(白色)
|
||||||
|
secondary: '#8B8B8B', // 次要文字(灰色)
|
||||||
|
accent: '#D4AF37', // 强调色(金色)
|
||||||
|
gold: '#D4AF37', // 金色
|
||||||
|
goldLight: '#F0D78C', // 浅金色
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
up: '#EF4444', // 上涨(红色)
|
||||||
|
down: '#22C55E', // 下跌(绿色)
|
||||||
|
},
|
||||||
|
border: 'rgba(212, 175, 55, 0.2)', // 金色边框
|
||||||
|
borderGold: 'rgba(212, 175, 55, 0.4)',
|
||||||
|
shadow: '0 4px 20px rgba(212, 175, 55, 0.1)',
|
||||||
|
};
|
||||||
|
|
||||||
|
// 模拟数据(后续替换为真实 API)
|
||||||
|
export const MOCK_INDICES: Array<{
|
||||||
|
name: string;
|
||||||
|
code: string;
|
||||||
|
value: number;
|
||||||
|
change: number;
|
||||||
|
chartData: number[];
|
||||||
|
}> = [
|
||||||
|
{
|
||||||
|
name: '上证指数',
|
||||||
|
code: '000001.SH',
|
||||||
|
value: 3391.88,
|
||||||
|
change: 0.52,
|
||||||
|
chartData: [3350, 3360, 3355, 3370, 3365, 3380, 3375, 3390, 3385, 3392],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '深证成指',
|
||||||
|
code: '399001.SZ',
|
||||||
|
value: 10723.49,
|
||||||
|
change: 0.68,
|
||||||
|
chartData: [10650, 10680, 10660, 10700, 10690, 10720, 10710, 10730, 10720, 10723],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '创业板指',
|
||||||
|
code: '399006.SZ',
|
||||||
|
value: 2156.78,
|
||||||
|
change: 1.23,
|
||||||
|
chartData: [2130, 2140, 2135, 2150, 2145, 2155, 2150, 2160, 2155, 2157],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const MOCK_MARKET_STATS = {
|
||||||
|
limitUp: 86,
|
||||||
|
limitDown: 12,
|
||||||
|
turnover: '1.2万亿',
|
||||||
|
riseCount: 2156,
|
||||||
|
fallCount: 2034,
|
||||||
|
flatCount: 312,
|
||||||
|
turnoverData: [
|
||||||
|
{ time: '10:30', value: 0.85 },
|
||||||
|
{ time: '11:00', value: 0.92 },
|
||||||
|
{ time: '11:15', value: 0.78 },
|
||||||
|
{ time: '13:00', value: 1.05 },
|
||||||
|
{ time: '13:30', value: 1.12 },
|
||||||
|
{ time: '14:00', value: 0.95 },
|
||||||
|
],
|
||||||
|
hotSectors: [
|
||||||
|
{ rank: 1, name: '人工智能', change: 3.2, trend: [100, 102, 101, 104, 103, 106] },
|
||||||
|
{ rank: 2, name: '新能源车', change: 1.8, trend: [100, 99, 101, 102, 101, 103] },
|
||||||
|
{ rank: 3, name: '生物医药', change: 1.3, trend: [100, 101, 100, 102, 101, 102] },
|
||||||
|
{ rank: 4, name: '消费科技', change: 1.2, trend: [100, 100, 101, 100, 102, 102] },
|
||||||
|
{ rank: 5, name: '半导体', change: 0.9, trend: [100, 100, 100, 101, 100, 101] },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const MOCK_HOT_CONCEPTS: Array<{
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
change: number;
|
||||||
|
trend: number[];
|
||||||
|
}> = [
|
||||||
|
{ id: '1', name: '人工智能', change: 3.25, trend: [100, 102, 101, 103, 105, 108] },
|
||||||
|
{ id: '2', name: '芯片概念', change: 2.87, trend: [100, 99, 101, 102, 104, 106] },
|
||||||
|
{ id: '3', name: '新能源车', change: 2.15, trend: [100, 101, 100, 102, 103, 105] },
|
||||||
|
{ id: '4', name: '光伏', change: 1.92, trend: [100, 99, 100, 101, 102, 104] },
|
||||||
|
{ id: '5', name: '医药生物', change: 1.56, trend: [100, 100, 101, 101, 102, 103] },
|
||||||
|
{ id: '6', name: '消费电子', change: 1.33, trend: [100, 101, 100, 101, 102, 103] },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const MOCK_PLATFORM_STATS: Array<{
|
||||||
|
icon: string;
|
||||||
|
value: string;
|
||||||
|
label: string;
|
||||||
|
}> = [
|
||||||
|
{ icon: 'building', value: '4300+', label: '上市公司' },
|
||||||
|
{ icon: 'chart', value: '500+', label: '概念板块' },
|
||||||
|
{ icon: 'calendar', value: '10年+', label: '历史数据' },
|
||||||
|
{ icon: 'robot', value: '24/7', label: 'AI分析' },
|
||||||
|
];
|
||||||
|
|
||||||
|
// 星期标题
|
||||||
|
export const WEEKDAY_LABELS = ['日', '一', '二', '三', '四', '五', '六'];
|
||||||
3
src/views/Profile/components/MarketDashboard/index.js
Normal file
3
src/views/Profile/components/MarketDashboard/index.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
// MarketDashboard 组件导出
|
||||||
|
export { default } from './MarketDashboard';
|
||||||
|
export { default as MarketDashboard } from './MarketDashboard';
|
||||||
88
src/views/Profile/components/MarketDashboard/types.ts
Normal file
88
src/views/Profile/components/MarketDashboard/types.ts
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
// MarketDashboard 类型定义
|
||||||
|
|
||||||
|
// 迷你趋势线
|
||||||
|
export interface MiniTrendLineProps {
|
||||||
|
data: number[];
|
||||||
|
color?: 'green' | 'red';
|
||||||
|
width?: number;
|
||||||
|
height?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 指数卡片
|
||||||
|
export interface IndexCardProps {
|
||||||
|
name: string;
|
||||||
|
value: number;
|
||||||
|
change: number;
|
||||||
|
trend?: number[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 统计卡片(涨停/成交额)
|
||||||
|
export interface StatCardProps {
|
||||||
|
label: string;
|
||||||
|
value: string | number;
|
||||||
|
subLabel?: string;
|
||||||
|
icon?: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 概念项
|
||||||
|
export interface ConceptItemProps {
|
||||||
|
name: string;
|
||||||
|
change: number;
|
||||||
|
trend?: number[];
|
||||||
|
onClick?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 日期单元格
|
||||||
|
export interface DayCellProps {
|
||||||
|
day: number;
|
||||||
|
isTrading: boolean;
|
||||||
|
isToday: boolean;
|
||||||
|
isWeekend: boolean;
|
||||||
|
isCurrentMonth?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 统计项(AI平台能力)
|
||||||
|
export interface StatItemProps {
|
||||||
|
icon: React.ReactNode;
|
||||||
|
value: string;
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 指数数据
|
||||||
|
export interface IndexData {
|
||||||
|
name: string;
|
||||||
|
code: string;
|
||||||
|
value: number;
|
||||||
|
change: number;
|
||||||
|
trend: number[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 概念数据
|
||||||
|
export interface ConceptData {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
change: number;
|
||||||
|
trend: number[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 市场统计数据
|
||||||
|
export interface MarketStats {
|
||||||
|
limitUp: number;
|
||||||
|
limitDown: number;
|
||||||
|
turnover: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 平台能力数据
|
||||||
|
export interface PlatformStat {
|
||||||
|
icon: string;
|
||||||
|
value: string;
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 完整仪表盘数据
|
||||||
|
export interface MarketDashboardData {
|
||||||
|
indices: IndexData[];
|
||||||
|
marketStats: MarketStats;
|
||||||
|
hotConcepts: ConceptData[];
|
||||||
|
platformStats: PlatformStat[];
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user