更新Company页面的UI为FUI风格
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
// src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/KLineModule.tsx
|
||||
// K线模块 - 日K线/分时图切换展示(黑金主题 + 专业技术指标 + 商品数据叠加)
|
||||
// K线模块 - 日K线/分时图切换展示(黑金主题 + 专业技术指标 + 商品数据叠加 + 分时盘口)
|
||||
|
||||
import React, { useState, useMemo, useCallback } from 'react';
|
||||
import React, { useState, useMemo, useCallback, useEffect } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Text,
|
||||
@@ -20,11 +20,17 @@ import {
|
||||
MenuItem,
|
||||
MenuDivider,
|
||||
Tooltip,
|
||||
Grid,
|
||||
GridItem,
|
||||
} from '@chakra-ui/react';
|
||||
import { RepeatIcon, InfoIcon, ChevronDownIcon, ViewIcon, ViewOffIcon } from '@chakra-ui/icons';
|
||||
import { BarChart2, Clock, TrendingUp, Calendar, LineChart, Activity, Pencil } from 'lucide-react';
|
||||
import ReactECharts from 'echarts-for-react';
|
||||
|
||||
// 导入实时行情 Hook 和五档盘口组件
|
||||
import { useRealtimeQuote } from '@views/StockOverview/components/FlexScreen/hooks';
|
||||
import OrderBookPanel from '@views/StockOverview/components/FlexScreen/components/OrderBookPanel';
|
||||
|
||||
import { darkGoldTheme, PERIOD_OPTIONS } from '../../../constants';
|
||||
import {
|
||||
getKLineDarkGoldOption,
|
||||
@@ -90,6 +96,7 @@ const KLineModule: React.FC<KLineModuleProps> = ({
|
||||
onChartClick,
|
||||
selectedPeriod,
|
||||
onPeriodChange,
|
||||
stockCode,
|
||||
}) => {
|
||||
const [mode, setMode] = useState<ChartMode>('daily');
|
||||
const [subIndicator, setSubIndicator] = useState<IndicatorType>('MACD');
|
||||
@@ -97,8 +104,34 @@ const KLineModule: React.FC<KLineModuleProps> = ({
|
||||
const [showAnalysis, setShowAnalysis] = useState<boolean>(true);
|
||||
const [drawingType, setDrawingType] = useState<DrawingType>('NONE');
|
||||
const [overlayMetrics, setOverlayMetrics] = useState<OverlayMetricData[]>([]);
|
||||
const [showOrderBook, setShowOrderBook] = useState<boolean>(true);
|
||||
const hasMinuteData = minuteData && minuteData.data && minuteData.data.length > 0;
|
||||
|
||||
// 实时行情数据(用于五档盘口)
|
||||
const subscribedCodes = useMemo(() => {
|
||||
if (!stockCode || mode !== 'minute') return [];
|
||||
return [stockCode];
|
||||
}, [stockCode, mode]);
|
||||
|
||||
const { quotes, connected } = useRealtimeQuote(subscribedCodes);
|
||||
|
||||
// 获取当前股票的行情数据
|
||||
const currentQuote = useMemo(() => {
|
||||
if (!stockCode) return null;
|
||||
// 尝试不同的代码格式
|
||||
return quotes[stockCode] || quotes[`${stockCode}.SH`] || quotes[`${stockCode}.SZ`] || null;
|
||||
}, [quotes, stockCode]);
|
||||
|
||||
// 判断是否在交易时间
|
||||
const isInTradingHours = useMemo(() => {
|
||||
const now = new Date();
|
||||
const hours = now.getHours();
|
||||
const minutes = now.getMinutes();
|
||||
const totalMinutes = hours * 60 + minutes;
|
||||
// 9:15-11:30 或 13:00-15:00
|
||||
return (totalMinutes >= 555 && totalMinutes <= 690) || (totalMinutes >= 780 && totalMinutes <= 900);
|
||||
}, []);
|
||||
|
||||
// 计算股票数据的日期范围(用于查询商品数据)
|
||||
const stockDateRange = useMemo(() => {
|
||||
if (tradeData.length === 0) return undefined;
|
||||
@@ -369,19 +402,35 @@ const KLineModule: React.FC<KLineModuleProps> = ({
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* 分时模式下的刷新按钮 */}
|
||||
{/* 分时模式下的控制按钮 */}
|
||||
{mode === 'minute' && (
|
||||
<Button
|
||||
leftIcon={<RepeatIcon />}
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={onLoadMinuteData}
|
||||
isLoading={minuteLoading}
|
||||
loadingText="获取中"
|
||||
{...inactiveButtonStyle}
|
||||
>
|
||||
刷新
|
||||
</Button>
|
||||
<>
|
||||
{/* 显示/隐藏盘口 */}
|
||||
<Tooltip label={showOrderBook ? '隐藏盘口' : '显示盘口'} placement="top" hasArrow>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => setShowOrderBook(!showOrderBook)}
|
||||
{...(showOrderBook ? activeButtonStyle : inactiveButtonStyle)}
|
||||
minW="80px"
|
||||
>
|
||||
{showOrderBook ? '隐藏盘口' : '显示盘口'}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
|
||||
{/* 刷新按钮 */}
|
||||
<Button
|
||||
leftIcon={<RepeatIcon />}
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={onLoadMinuteData}
|
||||
isLoading={minuteLoading}
|
||||
loadingText="获取中"
|
||||
{...inactiveButtonStyle}
|
||||
>
|
||||
刷新
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* 模式切换按钮组 */}
|
||||
@@ -424,7 +473,7 @@ const KLineModule: React.FC<KLineModuleProps> = ({
|
||||
<EmptyState title="暂无日K线数据" description="该股票暂无交易数据" />
|
||||
)
|
||||
) : (
|
||||
// 分时走势图
|
||||
// 分时走势图 + 五档盘口
|
||||
minuteLoading ? (
|
||||
<Center h="450px">
|
||||
<VStack spacing={4}>
|
||||
@@ -441,15 +490,115 @@ const KLineModule: React.FC<KLineModuleProps> = ({
|
||||
</VStack>
|
||||
</Center>
|
||||
) : hasMinuteData ? (
|
||||
<Box h="450px">
|
||||
<ReactECharts
|
||||
option={getMinuteKLineDarkGoldOption(minuteData)}
|
||||
style={{ height: '100%', width: '100%' }}
|
||||
theme="dark"
|
||||
notMerge={true}
|
||||
opts={{ renderer: 'canvas' }}
|
||||
/>
|
||||
</Box>
|
||||
<Grid templateColumns={showOrderBook ? '1fr 220px' : '1fr'} gap={4} h="450px">
|
||||
{/* 分时图表 */}
|
||||
<GridItem>
|
||||
<Box h="100%">
|
||||
<ReactECharts
|
||||
option={getMinuteKLineDarkGoldOption(minuteData)}
|
||||
style={{ height: '100%', width: '100%' }}
|
||||
theme="dark"
|
||||
notMerge={true}
|
||||
opts={{ renderer: 'canvas' }}
|
||||
/>
|
||||
</Box>
|
||||
</GridItem>
|
||||
|
||||
{/* 五档盘口 */}
|
||||
{showOrderBook && (
|
||||
<GridItem>
|
||||
<Box
|
||||
h="100%"
|
||||
bg="rgba(0, 0, 0, 0.3)"
|
||||
borderRadius="lg"
|
||||
border="1px solid"
|
||||
borderColor={darkGoldTheme.border}
|
||||
p={3}
|
||||
overflowY="auto"
|
||||
>
|
||||
{/* 盘口标题 */}
|
||||
<HStack justify="space-between" mb={3}>
|
||||
<Text fontSize="sm" fontWeight="bold" color={darkGoldTheme.gold}>
|
||||
五档盘口
|
||||
</Text>
|
||||
{/* 连接状态指示 */}
|
||||
<HStack spacing={1}>
|
||||
{isInTradingHours && (
|
||||
<Badge
|
||||
bg={connected.SSE || connected.SZSE ? 'green.500' : 'gray.500'}
|
||||
color="white"
|
||||
fontSize="2xs"
|
||||
px={1}
|
||||
>
|
||||
{connected.SSE || connected.SZSE ? '实时' : '离线'}
|
||||
</Badge>
|
||||
)}
|
||||
</HStack>
|
||||
</HStack>
|
||||
|
||||
{/* 当前价格信息 */}
|
||||
{currentQuote && (
|
||||
<VStack spacing={1} mb={3} align="stretch">
|
||||
<HStack justify="space-between">
|
||||
<Text fontSize="xs" color={darkGoldTheme.textMuted}>当前价</Text>
|
||||
<Text
|
||||
fontSize="lg"
|
||||
fontWeight="bold"
|
||||
color={
|
||||
currentQuote.changePct > 0 ? '#ff4d4d' :
|
||||
currentQuote.changePct < 0 ? '#22c55e' :
|
||||
darkGoldTheme.textPrimary
|
||||
}
|
||||
>
|
||||
{currentQuote.price?.toFixed(2) || '-'}
|
||||
</Text>
|
||||
</HStack>
|
||||
<HStack justify="space-between">
|
||||
<Text fontSize="xs" color={darkGoldTheme.textMuted}>涨跌幅</Text>
|
||||
<Text
|
||||
fontSize="sm"
|
||||
color={
|
||||
currentQuote.changePct > 0 ? '#ff4d4d' :
|
||||
currentQuote.changePct < 0 ? '#22c55e' :
|
||||
darkGoldTheme.textMuted
|
||||
}
|
||||
>
|
||||
{currentQuote.changePct > 0 ? '+' : ''}{currentQuote.changePct?.toFixed(2) || '0.00'}%
|
||||
</Text>
|
||||
</HStack>
|
||||
</VStack>
|
||||
)}
|
||||
|
||||
{/* 五档盘口面板 */}
|
||||
{currentQuote && (currentQuote.bidPrices?.length > 0 || currentQuote.askPrices?.length > 0) ? (
|
||||
<OrderBookPanel
|
||||
bidPrices={currentQuote.bidPrices || []}
|
||||
bidVolumes={currentQuote.bidVolumes || []}
|
||||
askPrices={currentQuote.askPrices || []}
|
||||
askVolumes={currentQuote.askVolumes || []}
|
||||
prevClose={currentQuote.prevClose}
|
||||
upperLimit={'upperLimit' in currentQuote ? currentQuote.upperLimit : undefined}
|
||||
lowerLimit={'lowerLimit' in currentQuote ? currentQuote.lowerLimit : undefined}
|
||||
defaultLevels={5}
|
||||
/>
|
||||
) : (
|
||||
<Center h="200px">
|
||||
<VStack spacing={2}>
|
||||
<Text fontSize="xs" color={darkGoldTheme.textMuted}>
|
||||
{isInTradingHours ? '获取盘口数据中...' : '非交易时间'}
|
||||
</Text>
|
||||
{!isInTradingHours && (
|
||||
<Text fontSize="2xs" color={darkGoldTheme.textMuted}>
|
||||
交易时间: 9:30-11:30, 13:00-15:00
|
||||
</Text>
|
||||
)}
|
||||
</VStack>
|
||||
</Center>
|
||||
)}
|
||||
</Box>
|
||||
</GridItem>
|
||||
)}
|
||||
</Grid>
|
||||
) : (
|
||||
<EmptyState title="暂无分时数据" description="点击刷新按钮获取当日分时数据" />
|
||||
)
|
||||
|
||||
@@ -85,7 +85,7 @@ const MetricOverlaySearch: React.FC<MetricOverlaySearchProps> = ({
|
||||
|
||||
setIsSearching(true);
|
||||
try {
|
||||
const response = await searchMetrics(query, undefined, undefined, 20);
|
||||
const response = await searchMetrics(query, undefined, '日', 20);
|
||||
setSearchResults(response.results || []);
|
||||
} catch (error) {
|
||||
console.error('Search metrics failed:', error);
|
||||
|
||||
@@ -16,6 +16,7 @@ export interface TradeDataPanelProps {
|
||||
onChartClick: (params: { seriesName?: string; data?: [number, number] }) => void;
|
||||
selectedPeriod?: number;
|
||||
onPeriodChange?: (period: number) => void;
|
||||
stockCode?: string;
|
||||
}
|
||||
|
||||
const TradeDataPanel: React.FC<TradeDataPanelProps> = ({
|
||||
@@ -28,6 +29,7 @@ const TradeDataPanel: React.FC<TradeDataPanelProps> = ({
|
||||
onChartClick,
|
||||
selectedPeriod,
|
||||
onPeriodChange,
|
||||
stockCode,
|
||||
}) => {
|
||||
return (
|
||||
<KLineModule
|
||||
@@ -40,6 +42,7 @@ const TradeDataPanel: React.FC<TradeDataPanelProps> = ({
|
||||
onChartClick={onChartClick}
|
||||
selectedPeriod={selectedPeriod}
|
||||
onPeriodChange={onPeriodChange}
|
||||
stockCode={stockCode}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -153,6 +153,7 @@ const MarketDataView: React.FC<MarketDataViewProps> = ({ stockCode: propStockCod
|
||||
onChartClick={handleChartClick}
|
||||
selectedPeriod={selectedPeriod}
|
||||
onPeriodChange={setSelectedPeriod}
|
||||
stockCode={stockCode}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@@ -299,6 +299,7 @@ export interface KLineModuleProps {
|
||||
onChartClick: (params: { seriesName?: string; data?: [number, number] }) => void;
|
||||
selectedPeriod?: number;
|
||||
onPeriodChange?: (period: number) => void;
|
||||
stockCode?: string; // 股票代码,用于获取实时盘口数据
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user