更新Company页面的UI为FUI风格

This commit is contained in:
2025-12-17 23:20:33 +08:00
parent 8acae9c93c
commit 6cb2742cf6
2 changed files with 977 additions and 287 deletions

View File

@@ -1,5 +1,5 @@
// src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/KLineModule.tsx // src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/KLineModule.tsx
// K线模块 - 日K线/分钟K线切换展示(黑金主题) // K线模块 - 日K线/分时图切换展示(黑金主题 + 专业技术指标
import React, { useState } from 'react'; import React, { useState } from 'react';
import { import {
@@ -14,13 +14,24 @@ import {
Spinner, Spinner,
Icon, Icon,
Select, Select,
Menu,
MenuButton,
MenuList,
MenuItem,
MenuDivider,
Tooltip,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { RepeatIcon, InfoIcon } from '@chakra-ui/icons'; import { RepeatIcon, InfoIcon, ChevronDownIcon } from '@chakra-ui/icons';
import { BarChart2, Clock, TrendingUp, Calendar } from 'lucide-react'; import { BarChart2, Clock, TrendingUp, Calendar, LineChart, Activity } from 'lucide-react';
import ReactECharts from 'echarts-for-react'; import ReactECharts from 'echarts-for-react';
import { darkGoldTheme, PERIOD_OPTIONS } from '../../../constants'; import { darkGoldTheme, PERIOD_OPTIONS } from '../../../constants';
import { getKLineDarkGoldOption, getMinuteKLineDarkGoldOption } from '../../../utils/chartOptions'; import {
getKLineDarkGoldOption,
getMinuteKLineDarkGoldOption,
type IndicatorType,
type MainIndicatorType,
} from '../../../utils/chartOptions';
import type { KLineModuleProps } from '../../../types'; import type { KLineModuleProps } from '../../../types';
// 空状态组件(内联) // 空状态组件(内联)
@@ -41,6 +52,21 @@ export type { KLineModuleProps } from '../../../types';
type ChartMode = 'daily' | 'minute'; type ChartMode = 'daily' | 'minute';
// 副图指标选项
const SUB_INDICATOR_OPTIONS: { value: IndicatorType; label: string; description: string }[] = [
{ value: 'MACD', label: 'MACD', description: '平滑异同移动平均线' },
{ value: 'KDJ', label: 'KDJ', description: '随机指标' },
{ value: 'RSI', label: 'RSI', description: '相对强弱指标' },
{ value: 'VOL', label: '仅成交量', description: '不显示副图指标' },
];
// 主图指标选项
const MAIN_INDICATOR_OPTIONS: { value: MainIndicatorType; label: string; description: string }[] = [
{ value: 'MA', label: 'MA均线', description: 'MA5/MA10/MA20' },
{ value: 'BOLL', label: '布林带', description: '布林通道指标' },
{ value: 'NONE', label: '无', description: '不显示主图指标' },
];
const KLineModule: React.FC<KLineModuleProps> = ({ const KLineModule: React.FC<KLineModuleProps> = ({
theme, theme,
tradeData, tradeData,
@@ -53,9 +79,11 @@ const KLineModule: React.FC<KLineModuleProps> = ({
onPeriodChange, onPeriodChange,
}) => { }) => {
const [mode, setMode] = useState<ChartMode>('daily'); const [mode, setMode] = useState<ChartMode>('daily');
const [subIndicator, setSubIndicator] = useState<IndicatorType>('MACD');
const [mainIndicator, setMainIndicator] = useState<MainIndicatorType>('MA');
const hasMinuteData = minuteData && minuteData.data && minuteData.data.length > 0; const hasMinuteData = minuteData && minuteData.data && minuteData.data.length > 0;
// 切换到分模式时自动加载数据 // 切换到分模式时自动加载数据
const handleModeChange = (newMode: ChartMode) => { const handleModeChange = (newMode: ChartMode) => {
setMode(newMode); setMode(newMode);
if (newMode === 'minute' && !hasMinuteData && !minuteLoading) { if (newMode === 'minute' && !hasMinuteData && !minuteLoading) {
@@ -91,14 +119,18 @@ const KLineModule: React.FC<KLineModuleProps> = ({
> >
{/* 卡片头部 */} {/* 卡片头部 */}
<Box py={4} borderBottom="1px solid" borderColor={darkGoldTheme.border}> <Box py={4} borderBottom="1px solid" borderColor={darkGoldTheme.border}>
<HStack justify="space-between" align="center"> <HStack justify="space-between" align="center" flexWrap="wrap" gap={2}>
<HStack spacing={3}> <HStack spacing={3}>
<Box <Box
p={2} p={2}
borderRadius="lg" borderRadius="lg"
bg={darkGoldTheme.tagBg} bg={darkGoldTheme.tagBg}
> >
{mode === 'daily' ? (
<TrendingUp size={20} color={darkGoldTheme.gold} /> <TrendingUp size={20} color={darkGoldTheme.gold} />
) : (
<LineChart size={20} color={darkGoldTheme.gold} />
)}
</Box> </Box>
<Text <Text
fontSize="lg" fontSize="lg"
@@ -106,7 +138,7 @@ const KLineModule: React.FC<KLineModuleProps> = ({
bgGradient={`linear(to-r, ${darkGoldTheme.gold}, ${darkGoldTheme.orange})`} bgGradient={`linear(to-r, ${darkGoldTheme.gold}, ${darkGoldTheme.orange})`}
bgClip="text" bgClip="text"
> >
{mode === 'daily' ? '日K线图' : '分钟K线图'} {mode === 'daily' ? '日K线图' : '分时走势'}
</Text> </Text>
{mode === 'minute' && minuteData?.trade_date && ( {mode === 'minute' && minuteData?.trade_date && (
<Badge <Badge
@@ -122,10 +154,13 @@ const KLineModule: React.FC<KLineModuleProps> = ({
)} )}
</HStack> </HStack>
<HStack spacing={3}> <HStack spacing={2} flexWrap="wrap">
{/* 日K模式下显示时间范围选择器 */} {/* 日K模式下显示时间范围选择器和指标选择 */}
{mode === 'daily' && onPeriodChange && ( {mode === 'daily' && (
<HStack spacing={2}> <>
{/* 时间范围选择器 */}
{onPeriodChange && (
<HStack spacing={1}>
<Icon as={Calendar} boxSize={4} color={darkGoldTheme.textMuted} /> <Icon as={Calendar} boxSize={4} color={darkGoldTheme.textMuted} />
<Select <Select
size="sm" size="sm"
@@ -134,7 +169,7 @@ const KLineModule: React.FC<KLineModuleProps> = ({
bg="transparent" bg="transparent"
borderColor={darkGoldTheme.border} borderColor={darkGoldTheme.border}
color={darkGoldTheme.textPrimary} color={darkGoldTheme.textPrimary}
maxW="100px" maxW="85px"
_hover={{ borderColor: darkGoldTheme.gold }} _hover={{ borderColor: darkGoldTheme.gold }}
_focus={{ borderColor: darkGoldTheme.gold, boxShadow: 'none' }} _focus={{ borderColor: darkGoldTheme.gold, boxShadow: 'none' }}
sx={{ sx={{
@@ -153,7 +188,90 @@ const KLineModule: React.FC<KLineModuleProps> = ({
</HStack> </HStack>
)} )}
{/* 分钟模式下的刷新按钮 */} {/* 主图指标选择 */}
<Menu>
<Tooltip label="主图指标" placement="top" hasArrow>
<MenuButton
as={Button}
size="sm"
variant="outline"
rightIcon={<ChevronDownIcon />}
{...inactiveButtonStyle}
minW="90px"
>
{MAIN_INDICATOR_OPTIONS.find(o => o.value === mainIndicator)?.label || 'MA均线'}
</MenuButton>
</Tooltip>
<MenuList
bg="#1a1a2e"
borderColor={darkGoldTheme.border}
boxShadow="0 4px 20px rgba(0,0,0,0.5)"
>
{MAIN_INDICATOR_OPTIONS.map((option) => (
<MenuItem
key={option.value}
onClick={() => setMainIndicator(option.value)}
bg={mainIndicator === option.value ? 'rgba(212, 175, 55, 0.2)' : 'transparent'}
color={mainIndicator === option.value ? darkGoldTheme.gold : darkGoldTheme.textPrimary}
_hover={{ bg: 'rgba(212, 175, 55, 0.1)' }}
>
<VStack align="start" spacing={0}>
<Text fontSize="sm" fontWeight={mainIndicator === option.value ? 'bold' : 'normal'}>
{option.label}
</Text>
<Text fontSize="xs" color={darkGoldTheme.textMuted}>
{option.description}
</Text>
</VStack>
</MenuItem>
))}
</MenuList>
</Menu>
{/* 副图指标选择 */}
<Menu>
<Tooltip label="副图指标" placement="top" hasArrow>
<MenuButton
as={Button}
size="sm"
variant="outline"
rightIcon={<ChevronDownIcon />}
leftIcon={<Activity size={14} />}
{...inactiveButtonStyle}
minW="100px"
>
{SUB_INDICATOR_OPTIONS.find(o => o.value === subIndicator)?.label || 'MACD'}
</MenuButton>
</Tooltip>
<MenuList
bg="#1a1a2e"
borderColor={darkGoldTheme.border}
boxShadow="0 4px 20px rgba(0,0,0,0.5)"
>
{SUB_INDICATOR_OPTIONS.map((option) => (
<MenuItem
key={option.value}
onClick={() => setSubIndicator(option.value)}
bg={subIndicator === option.value ? 'rgba(212, 175, 55, 0.2)' : 'transparent'}
color={subIndicator === option.value ? darkGoldTheme.gold : darkGoldTheme.textPrimary}
_hover={{ bg: 'rgba(212, 175, 55, 0.1)' }}
>
<VStack align="start" spacing={0}>
<Text fontSize="sm" fontWeight={subIndicator === option.value ? 'bold' : 'normal'}>
{option.label}
</Text>
<Text fontSize="xs" color={darkGoldTheme.textMuted}>
{option.description}
</Text>
</VStack>
</MenuItem>
))}
</MenuList>
</Menu>
</>
)}
{/* 分时模式下的刷新按钮 */}
{mode === 'minute' && ( {mode === 'minute' && (
<Button <Button
leftIcon={<RepeatIcon />} leftIcon={<RepeatIcon />}
@@ -178,11 +296,11 @@ const KLineModule: React.FC<KLineModuleProps> = ({
K K
</Button> </Button>
<Button <Button
leftIcon={<Clock size={14} />} leftIcon={<LineChart size={14} />}
onClick={() => handleModeChange('minute')} onClick={() => handleModeChange('minute')}
{...(mode === 'minute' ? activeButtonStyle : inactiveButtonStyle)} {...(mode === 'minute' ? activeButtonStyle : inactiveButtonStyle)}
> >
</Button> </Button>
</ButtonGroup> </ButtonGroup>
</HStack> </HStack>
@@ -192,23 +310,24 @@ const KLineModule: React.FC<KLineModuleProps> = ({
{/* 卡片内容 */} {/* 卡片内容 */}
<Box pt={4}> <Box pt={4}>
{mode === 'daily' ? ( {mode === 'daily' ? (
// 日K线图 // 日K线图(带技术指标)
tradeData.length > 0 ? ( tradeData.length > 0 ? (
<Box h="600px"> <Box h="650px">
<ReactECharts <ReactECharts
option={getKLineDarkGoldOption(tradeData, analysisMap)} option={getKLineDarkGoldOption(tradeData, analysisMap, subIndicator, mainIndicator)}
style={{ height: '100%', width: '100%' }} style={{ height: '100%', width: '100%' }}
theme="dark" theme="dark"
onEvents={{ click: onChartClick }} onEvents={{ click: onChartClick }}
opts={{ renderer: 'canvas' }}
/> />
</Box> </Box>
) : ( ) : (
<EmptyState title="暂无日K线数据" description="该股票暂无交易数据" /> <EmptyState title="暂无日K线数据" description="该股票暂无交易数据" />
) )
) : ( ) : (
// 分钟K线 // 分时走势
minuteLoading ? ( minuteLoading ? (
<Center h="500px"> <Center h="450px">
<VStack spacing={4}> <VStack spacing={4}>
<Spinner <Spinner
thickness="4px" thickness="4px"
@@ -218,20 +337,21 @@ const KLineModule: React.FC<KLineModuleProps> = ({
size="lg" size="lg"
/> />
<Text color={darkGoldTheme.textMuted} fontSize="sm"> <Text color={darkGoldTheme.textMuted} fontSize="sm">
... ...
</Text> </Text>
</VStack> </VStack>
</Center> </Center>
) : hasMinuteData ? ( ) : hasMinuteData ? (
<Box h="500px"> <Box h="450px">
<ReactECharts <ReactECharts
option={getMinuteKLineDarkGoldOption(minuteData)} option={getMinuteKLineDarkGoldOption(minuteData)}
style={{ height: '100%', width: '100%' }} style={{ height: '100%', width: '100%' }}
theme="dark" theme="dark"
opts={{ renderer: 'canvas' }}
/> />
</Box> </Box>
) : ( ) : (
<EmptyState title="暂无分数据" description="点击刷新按钮获取当日分钟频数据" /> <EmptyState title="暂无分数据" description="点击刷新按钮获取当日分数据" />
) )
)} )}
</Box> </Box>