更新Company页面的UI为FUI风格
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
// src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/KLineModule.tsx
|
||||
// K线模块 - 日K线/分钟K线切换展示(黑金主题)
|
||||
// K线模块 - 日K线/分时图切换展示(黑金主题 + 专业技术指标)
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
@@ -14,13 +14,24 @@ import {
|
||||
Spinner,
|
||||
Icon,
|
||||
Select,
|
||||
Menu,
|
||||
MenuButton,
|
||||
MenuList,
|
||||
MenuItem,
|
||||
MenuDivider,
|
||||
Tooltip,
|
||||
} from '@chakra-ui/react';
|
||||
import { RepeatIcon, InfoIcon } from '@chakra-ui/icons';
|
||||
import { BarChart2, Clock, TrendingUp, Calendar } from 'lucide-react';
|
||||
import { RepeatIcon, InfoIcon, ChevronDownIcon } from '@chakra-ui/icons';
|
||||
import { BarChart2, Clock, TrendingUp, Calendar, LineChart, Activity } from 'lucide-react';
|
||||
import ReactECharts from 'echarts-for-react';
|
||||
|
||||
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';
|
||||
|
||||
// 空状态组件(内联)
|
||||
@@ -41,6 +52,21 @@ export type { KLineModuleProps } from '../../../types';
|
||||
|
||||
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> = ({
|
||||
theme,
|
||||
tradeData,
|
||||
@@ -53,9 +79,11 @@ const KLineModule: React.FC<KLineModuleProps> = ({
|
||||
onPeriodChange,
|
||||
}) => {
|
||||
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 handleModeChange = (newMode: ChartMode) => {
|
||||
setMode(newMode);
|
||||
if (newMode === 'minute' && !hasMinuteData && !minuteLoading) {
|
||||
@@ -91,14 +119,18 @@ const KLineModule: React.FC<KLineModuleProps> = ({
|
||||
>
|
||||
{/* 卡片头部 */}
|
||||
<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}>
|
||||
<Box
|
||||
p={2}
|
||||
borderRadius="lg"
|
||||
bg={darkGoldTheme.tagBg}
|
||||
>
|
||||
<TrendingUp size={20} color={darkGoldTheme.gold} />
|
||||
{mode === 'daily' ? (
|
||||
<TrendingUp size={20} color={darkGoldTheme.gold} />
|
||||
) : (
|
||||
<LineChart size={20} color={darkGoldTheme.gold} />
|
||||
)}
|
||||
</Box>
|
||||
<Text
|
||||
fontSize="lg"
|
||||
@@ -106,7 +138,7 @@ const KLineModule: React.FC<KLineModuleProps> = ({
|
||||
bgGradient={`linear(to-r, ${darkGoldTheme.gold}, ${darkGoldTheme.orange})`}
|
||||
bgClip="text"
|
||||
>
|
||||
{mode === 'daily' ? '日K线图' : '分钟K线图'}
|
||||
{mode === 'daily' ? '日K线图' : '分时走势'}
|
||||
</Text>
|
||||
{mode === 'minute' && minuteData?.trade_date && (
|
||||
<Badge
|
||||
@@ -122,38 +154,124 @@ const KLineModule: React.FC<KLineModuleProps> = ({
|
||||
)}
|
||||
</HStack>
|
||||
|
||||
<HStack spacing={3}>
|
||||
{/* 日K模式下显示时间范围选择器 */}
|
||||
{mode === 'daily' && onPeriodChange && (
|
||||
<HStack spacing={2}>
|
||||
<Icon as={Calendar} boxSize={4} color={darkGoldTheme.textMuted} />
|
||||
<Select
|
||||
size="sm"
|
||||
value={selectedPeriod}
|
||||
onChange={(e) => onPeriodChange(Number(e.target.value))}
|
||||
bg="transparent"
|
||||
borderColor={darkGoldTheme.border}
|
||||
color={darkGoldTheme.textPrimary}
|
||||
maxW="100px"
|
||||
_hover={{ borderColor: darkGoldTheme.gold }}
|
||||
_focus={{ borderColor: darkGoldTheme.gold, boxShadow: 'none' }}
|
||||
sx={{
|
||||
option: {
|
||||
background: '#1a1a2e',
|
||||
color: darkGoldTheme.textPrimary,
|
||||
},
|
||||
}}
|
||||
>
|
||||
{PERIOD_OPTIONS.map((option) => (
|
||||
<option key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
</HStack>
|
||||
<HStack spacing={2} flexWrap="wrap">
|
||||
{/* 日K模式下显示时间范围选择器和指标选择 */}
|
||||
{mode === 'daily' && (
|
||||
<>
|
||||
{/* 时间范围选择器 */}
|
||||
{onPeriodChange && (
|
||||
<HStack spacing={1}>
|
||||
<Icon as={Calendar} boxSize={4} color={darkGoldTheme.textMuted} />
|
||||
<Select
|
||||
size="sm"
|
||||
value={selectedPeriod}
|
||||
onChange={(e) => onPeriodChange(Number(e.target.value))}
|
||||
bg="transparent"
|
||||
borderColor={darkGoldTheme.border}
|
||||
color={darkGoldTheme.textPrimary}
|
||||
maxW="85px"
|
||||
_hover={{ borderColor: darkGoldTheme.gold }}
|
||||
_focus={{ borderColor: darkGoldTheme.gold, boxShadow: 'none' }}
|
||||
sx={{
|
||||
option: {
|
||||
background: '#1a1a2e',
|
||||
color: darkGoldTheme.textPrimary,
|
||||
},
|
||||
}}
|
||||
>
|
||||
{PERIOD_OPTIONS.map((option) => (
|
||||
<option key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
</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' && (
|
||||
<Button
|
||||
leftIcon={<RepeatIcon />}
|
||||
@@ -178,11 +296,11 @@ const KLineModule: React.FC<KLineModuleProps> = ({
|
||||
日K
|
||||
</Button>
|
||||
<Button
|
||||
leftIcon={<Clock size={14} />}
|
||||
leftIcon={<LineChart size={14} />}
|
||||
onClick={() => handleModeChange('minute')}
|
||||
{...(mode === 'minute' ? activeButtonStyle : inactiveButtonStyle)}
|
||||
>
|
||||
分钟
|
||||
分时
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</HStack>
|
||||
@@ -192,23 +310,24 @@ const KLineModule: React.FC<KLineModuleProps> = ({
|
||||
{/* 卡片内容 */}
|
||||
<Box pt={4}>
|
||||
{mode === 'daily' ? (
|
||||
// 日K线图
|
||||
// 日K线图(带技术指标)
|
||||
tradeData.length > 0 ? (
|
||||
<Box h="600px">
|
||||
<Box h="650px">
|
||||
<ReactECharts
|
||||
option={getKLineDarkGoldOption(tradeData, analysisMap)}
|
||||
option={getKLineDarkGoldOption(tradeData, analysisMap, subIndicator, mainIndicator)}
|
||||
style={{ height: '100%', width: '100%' }}
|
||||
theme="dark"
|
||||
onEvents={{ click: onChartClick }}
|
||||
opts={{ renderer: 'canvas' }}
|
||||
/>
|
||||
</Box>
|
||||
) : (
|
||||
<EmptyState title="暂无日K线数据" description="该股票暂无交易数据" />
|
||||
)
|
||||
) : (
|
||||
// 分钟K线图
|
||||
// 分时走势图
|
||||
minuteLoading ? (
|
||||
<Center h="500px">
|
||||
<Center h="450px">
|
||||
<VStack spacing={4}>
|
||||
<Spinner
|
||||
thickness="4px"
|
||||
@@ -218,20 +337,21 @@ const KLineModule: React.FC<KLineModuleProps> = ({
|
||||
size="lg"
|
||||
/>
|
||||
<Text color={darkGoldTheme.textMuted} fontSize="sm">
|
||||
加载分钟频数据中...
|
||||
加载分时数据中...
|
||||
</Text>
|
||||
</VStack>
|
||||
</Center>
|
||||
) : hasMinuteData ? (
|
||||
<Box h="500px">
|
||||
<Box h="450px">
|
||||
<ReactECharts
|
||||
option={getMinuteKLineDarkGoldOption(minuteData)}
|
||||
style={{ height: '100%', width: '100%' }}
|
||||
theme="dark"
|
||||
opts={{ renderer: 'canvas' }}
|
||||
/>
|
||||
</Box>
|
||||
) : (
|
||||
<EmptyState title="暂无分钟数据" description="点击刷新按钮获取当日分钟频数据" />
|
||||
<EmptyState title="暂无分时数据" description="点击刷新按钮获取当日分时数据" />
|
||||
)
|
||||
)}
|
||||
</Box>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user