更新Company页面的UI为FUI风格
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user