更新Company页面的UI为FUI风格

This commit is contained in:
2025-12-18 08:23:04 +08:00
parent 26548c7036
commit 87ddc79252
18 changed files with 224 additions and 68 deletions

View File

@@ -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="点击刷新按钮获取当日分时数据" />
)

View File

@@ -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);

View File

@@ -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}
/>
);
};

View File

@@ -153,6 +153,7 @@ const MarketDataView: React.FC<MarketDataViewProps> = ({ stockCode: propStockCod
onChartClick={handleChartClick}
selectedPeriod={selectedPeriod}
onPeriodChange={setSelectedPeriod}
stockCode={stockCode}
/>
)}

View File

@@ -299,6 +299,7 @@ export interface KLineModuleProps {
onChartClick: (params: { seriesName?: string; data?: [number, number] }) => void;
selectedPeriod?: number;
onPeriodChange?: (period: number) => void;
stockCode?: string; // 股票代码,用于获取实时盘口数据
}
/**