更新Company页面的UI为FUI风格
This commit is contained in:
@@ -87,3 +87,55 @@ select::-webkit-scrollbar-thumb {
|
||||
select::-webkit-scrollbar-thumb:hover {
|
||||
background: #FFC107;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ant Design AutoComplete 下拉框样式 (FUI 主题)
|
||||
*/
|
||||
.fui-autocomplete-dropdown {
|
||||
background-color: #1a1a2e !important;
|
||||
border: 1px solid rgba(212, 175, 55, 0.3) !important;
|
||||
border-radius: 10px !important;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5) !important;
|
||||
}
|
||||
|
||||
.fui-autocomplete-dropdown .ant-select-item {
|
||||
color: #ffffff !important;
|
||||
padding: 10px 12px !important;
|
||||
border-bottom: 1px solid rgba(212, 175, 55, 0.1);
|
||||
}
|
||||
|
||||
.fui-autocomplete-dropdown .ant-select-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.fui-autocomplete-dropdown .ant-select-item-option-active,
|
||||
.fui-autocomplete-dropdown .ant-select-item:hover {
|
||||
background-color: rgba(212, 175, 55, 0.15) !important;
|
||||
}
|
||||
|
||||
.fui-autocomplete-dropdown .ant-select-item-option-selected {
|
||||
background-color: rgba(212, 175, 55, 0.25) !important;
|
||||
}
|
||||
|
||||
.fui-autocomplete-dropdown .ant-select-item-empty {
|
||||
color: rgba(255, 255, 255, 0.5) !important;
|
||||
}
|
||||
|
||||
/* AutoComplete 下拉框滚动条 */
|
||||
.fui-autocomplete-dropdown::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.fui-autocomplete-dropdown::-webkit-scrollbar-track {
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.fui-autocomplete-dropdown::-webkit-scrollbar-thumb {
|
||||
background: rgba(212, 175, 55, 0.4);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.fui-autocomplete-dropdown::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(212, 175, 55, 0.6);
|
||||
}
|
||||
|
||||
@@ -236,6 +236,7 @@ const SearchActions = memo<{
|
||||
}}
|
||||
>
|
||||
<AutoComplete
|
||||
popupClassName="fui-autocomplete-dropdown"
|
||||
value={inputCode}
|
||||
options={stockOptions}
|
||||
onSearch={doSearch}
|
||||
@@ -243,6 +244,12 @@ const SearchActions = memo<{
|
||||
onChange={onInputChange}
|
||||
placeholder="输入代码、名称或拼音"
|
||||
style={{ width: 240 }}
|
||||
dropdownStyle={{
|
||||
backgroundColor: FUI_COLORS.bg.elevated,
|
||||
borderRadius: '10px',
|
||||
border: `1px solid ${FUI_COLORS.line.emphasis}`,
|
||||
boxShadow: '0 4px 20px rgba(0, 0, 0, 0.5)',
|
||||
}}
|
||||
notFoundContent={isSearching ? <Spin size="small" /> : null}
|
||||
onKeyDown={handleKeyDown}
|
||||
/>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/KLineModule.tsx
|
||||
// K线模块 - 日K线/分时图切换展示(黑金主题 + 专业技术指标)
|
||||
// K线模块 - 日K线/分时图切换展示(黑金主题 + 专业技术指标 + 商品数据叠加)
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import React, { useState, useMemo, useCallback } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Text,
|
||||
@@ -33,7 +33,8 @@ import {
|
||||
type MainIndicatorType,
|
||||
type DrawingType,
|
||||
} from '../../../utils/chartOptions';
|
||||
import type { KLineModuleProps } from '../../../types';
|
||||
import type { KLineModuleProps, OverlayMetricData } from '../../../types';
|
||||
import MetricOverlaySearch from './MetricOverlaySearch';
|
||||
|
||||
// 空状态组件(内联)
|
||||
const EmptyState: React.FC<{ title: string; description: string }> = ({ title, description }) => (
|
||||
@@ -95,8 +96,28 @@ const KLineModule: React.FC<KLineModuleProps> = ({
|
||||
const [mainIndicator, setMainIndicator] = useState<MainIndicatorType>('MA');
|
||||
const [showAnalysis, setShowAnalysis] = useState<boolean>(true);
|
||||
const [drawingType, setDrawingType] = useState<DrawingType>('NONE');
|
||||
const [overlayMetrics, setOverlayMetrics] = useState<OverlayMetricData[]>([]);
|
||||
const hasMinuteData = minuteData && minuteData.data && minuteData.data.length > 0;
|
||||
|
||||
// 计算股票数据的日期范围(用于查询商品数据)
|
||||
const stockDateRange = useMemo(() => {
|
||||
if (tradeData.length === 0) return undefined;
|
||||
return {
|
||||
startDate: tradeData[0].date.substring(0, 10),
|
||||
endDate: tradeData[tradeData.length - 1].date.substring(0, 10),
|
||||
};
|
||||
}, [tradeData]);
|
||||
|
||||
// 添加叠加指标
|
||||
const handleAddOverlayMetric = useCallback((metric: OverlayMetricData) => {
|
||||
setOverlayMetrics(prev => [...prev, metric]);
|
||||
}, []);
|
||||
|
||||
// 移除叠加指标
|
||||
const handleRemoveOverlayMetric = useCallback((metricId: string) => {
|
||||
setOverlayMetrics(prev => prev.filter(m => m.metric_id !== metricId));
|
||||
}, []);
|
||||
|
||||
// 切换到分时模式时自动加载数据
|
||||
const handleModeChange = (newMode: ChartMode) => {
|
||||
setMode(newMode);
|
||||
@@ -337,6 +358,14 @@ const KLineModule: React.FC<KLineModuleProps> = ({
|
||||
))}
|
||||
</MenuList>
|
||||
</Menu>
|
||||
|
||||
{/* 商品数据叠加搜索 */}
|
||||
<MetricOverlaySearch
|
||||
overlayMetrics={overlayMetrics}
|
||||
onAddMetric={handleAddOverlayMetric}
|
||||
onRemoveMetric={handleRemoveOverlayMetric}
|
||||
stockDateRange={stockDateRange}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -383,7 +412,7 @@ const KLineModule: React.FC<KLineModuleProps> = ({
|
||||
tradeData.length > 0 ? (
|
||||
<Box h="650px">
|
||||
<ReactECharts
|
||||
option={getKLineDarkGoldOption(tradeData, analysisMap, subIndicator, mainIndicator, showAnalysis, drawingType)}
|
||||
option={getKLineDarkGoldOption(tradeData, analysisMap, subIndicator, mainIndicator, showAnalysis, drawingType, overlayMetrics)}
|
||||
style={{ height: '100%', width: '100%' }}
|
||||
theme="dark"
|
||||
notMerge={true}
|
||||
|
||||
@@ -0,0 +1,350 @@
|
||||
// src/views/Company/components/MarketDataView/components/panels/TradeDataPanel/MetricOverlaySearch.tsx
|
||||
// Metric overlay search component - search metrics and overlay on K-line chart
|
||||
|
||||
import React, { useState, useCallback, useMemo, useEffect, useRef } from 'react';
|
||||
import {
|
||||
Box,
|
||||
HStack,
|
||||
VStack,
|
||||
Text,
|
||||
Input,
|
||||
InputGroup,
|
||||
InputLeftElement,
|
||||
InputRightElement,
|
||||
IconButton,
|
||||
Tag,
|
||||
TagLabel,
|
||||
TagCloseButton,
|
||||
Spinner,
|
||||
Popover,
|
||||
PopoverTrigger,
|
||||
PopoverContent,
|
||||
PopoverBody,
|
||||
List,
|
||||
ListItem,
|
||||
Button,
|
||||
Tooltip,
|
||||
Badge,
|
||||
Flex,
|
||||
useDisclosure,
|
||||
} from '@chakra-ui/react';
|
||||
import { SearchIcon, CloseIcon, AddIcon } from '@chakra-ui/icons';
|
||||
import { Database, TrendingUp } from 'lucide-react';
|
||||
import debounce from 'lodash/debounce';
|
||||
|
||||
import { searchMetrics, fetchMetricData, type MetricSearchResult } from '@services/categoryService';
|
||||
import { darkGoldTheme } from '../../../constants';
|
||||
import type { OverlayMetricData } from '../../../types';
|
||||
|
||||
// Available overlay colors
|
||||
const OVERLAY_COLORS = [
|
||||
'#00D4FF', // Cyan
|
||||
'#A855F7', // Purple
|
||||
'#FF6B6B', // Pink-red
|
||||
'#4ADE80', // Green
|
||||
'#F97316', // Orange
|
||||
'#EC4899', // Pink
|
||||
];
|
||||
|
||||
interface MetricOverlaySearchProps {
|
||||
overlayMetrics: OverlayMetricData[];
|
||||
onAddMetric: (metric: OverlayMetricData) => void;
|
||||
onRemoveMetric: (metricId: string) => void;
|
||||
stockDateRange?: { startDate: string; endDate: string };
|
||||
}
|
||||
|
||||
/**
|
||||
* Metric overlay search component
|
||||
*/
|
||||
const MetricOverlaySearch: React.FC<MetricOverlaySearchProps> = ({
|
||||
overlayMetrics,
|
||||
onAddMetric,
|
||||
onRemoveMetric,
|
||||
stockDateRange,
|
||||
}) => {
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [searchResults, setSearchResults] = useState<MetricSearchResult[]>([]);
|
||||
const [isSearching, setIsSearching] = useState(false);
|
||||
const [isLoadingData, setIsLoadingData] = useState(false);
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
// Get next available color
|
||||
const getNextColor = useCallback(() => {
|
||||
const usedColors = overlayMetrics.map(m => m.color);
|
||||
const availableColor = OVERLAY_COLORS.find(c => !usedColors.includes(c));
|
||||
return availableColor || OVERLAY_COLORS[overlayMetrics.length % OVERLAY_COLORS.length];
|
||||
}, [overlayMetrics]);
|
||||
|
||||
// Search metrics
|
||||
const doSearch = useCallback(async (query: string) => {
|
||||
if (!query.trim()) {
|
||||
setSearchResults([]);
|
||||
return;
|
||||
}
|
||||
|
||||
setIsSearching(true);
|
||||
try {
|
||||
const response = await searchMetrics(query, undefined, undefined, 20);
|
||||
setSearchResults(response.results || []);
|
||||
} catch (error) {
|
||||
console.error('Search metrics failed:', error);
|
||||
setSearchResults([]);
|
||||
} finally {
|
||||
setIsSearching(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Debounced search
|
||||
const debouncedSearch = useMemo(
|
||||
() => debounce(doSearch, 300),
|
||||
[doSearch]
|
||||
);
|
||||
|
||||
// Handle input change
|
||||
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const value = e.target.value;
|
||||
setSearchQuery(value);
|
||||
debouncedSearch(value);
|
||||
if (value && !isOpen) {
|
||||
onOpen();
|
||||
}
|
||||
};
|
||||
|
||||
// Select metric
|
||||
const handleSelectMetric = async (metric: MetricSearchResult) => {
|
||||
// Check if already added
|
||||
if (overlayMetrics.some(m => m.metric_id === metric.metric_id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Max 3 overlays
|
||||
if (overlayMetrics.length >= 3) {
|
||||
alert('Max 3 overlay metrics allowed');
|
||||
return;
|
||||
}
|
||||
|
||||
setIsLoadingData(true);
|
||||
try {
|
||||
// Fetch metric data
|
||||
const data = await fetchMetricData(
|
||||
metric.metric_id,
|
||||
stockDateRange?.startDate,
|
||||
stockDateRange?.endDate,
|
||||
500
|
||||
);
|
||||
|
||||
if (data && data.data && data.data.length > 0) {
|
||||
const overlayData: OverlayMetricData = {
|
||||
metric_id: metric.metric_id,
|
||||
metric_name: metric.metric_name,
|
||||
source: metric.source,
|
||||
unit: metric.unit,
|
||||
data: data.data,
|
||||
color: getNextColor(),
|
||||
};
|
||||
|
||||
onAddMetric(overlayData);
|
||||
setSearchQuery('');
|
||||
setSearchResults([]);
|
||||
onClose();
|
||||
} else {
|
||||
alert('No data available for this metric');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fetch metric data failed:', error);
|
||||
alert('Failed to fetch metric data');
|
||||
} finally {
|
||||
setIsLoadingData(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Cleanup debounce
|
||||
useEffect(() => {
|
||||
return () => debouncedSearch.cancel?.();
|
||||
}, [debouncedSearch]);
|
||||
|
||||
return (
|
||||
<HStack spacing={2} align="center">
|
||||
{/* Added overlay metrics tags */}
|
||||
{overlayMetrics.map((metric) => (
|
||||
<Tag
|
||||
key={metric.metric_id}
|
||||
size="sm"
|
||||
borderRadius="full"
|
||||
variant="outline"
|
||||
borderColor={metric.color}
|
||||
color={metric.color}
|
||||
bg="transparent"
|
||||
>
|
||||
<Box
|
||||
w="8px"
|
||||
h="8px"
|
||||
borderRadius="full"
|
||||
bg={metric.color}
|
||||
mr={1}
|
||||
/>
|
||||
<TagLabel fontSize="xs" maxW="100px" isTruncated>
|
||||
{metric.metric_name}
|
||||
</TagLabel>
|
||||
<TagCloseButton onClick={() => onRemoveMetric(metric.metric_id)} />
|
||||
</Tag>
|
||||
))}
|
||||
|
||||
{/* Search button and dropdown */}
|
||||
<Popover
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
placement="bottom-start"
|
||||
closeOnBlur={true}
|
||||
initialFocusRef={inputRef}
|
||||
>
|
||||
<PopoverTrigger>
|
||||
<Box>
|
||||
<Tooltip label="Overlay commodity data" placement="top" hasArrow>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
leftIcon={<Database size={14} />}
|
||||
onClick={onOpen}
|
||||
color={darkGoldTheme.textMuted}
|
||||
borderColor={darkGoldTheme.border}
|
||||
_hover={{
|
||||
bg: 'rgba(212, 175, 55, 0.1)',
|
||||
borderColor: darkGoldTheme.gold,
|
||||
color: darkGoldTheme.gold,
|
||||
}}
|
||||
minW="100px"
|
||||
>
|
||||
Data Search
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
</PopoverTrigger>
|
||||
|
||||
<PopoverContent
|
||||
bg="#1a1a2e"
|
||||
borderColor={darkGoldTheme.border}
|
||||
boxShadow="0 4px 20px rgba(0, 0, 0, 0.5)"
|
||||
w="350px"
|
||||
_focus={{ outline: 'none' }}
|
||||
>
|
||||
<PopoverBody p={3}>
|
||||
{/* Search input */}
|
||||
<InputGroup size="sm" mb={3}>
|
||||
<InputLeftElement pointerEvents="none">
|
||||
<SearchIcon color={darkGoldTheme.textMuted} />
|
||||
</InputLeftElement>
|
||||
<Input
|
||||
ref={inputRef}
|
||||
placeholder="Search commodity metrics..."
|
||||
value={searchQuery}
|
||||
onChange={handleInputChange}
|
||||
bg="rgba(255,255,255,0.05)"
|
||||
borderColor={darkGoldTheme.border}
|
||||
color={darkGoldTheme.textPrimary}
|
||||
_placeholder={{ color: darkGoldTheme.textMuted }}
|
||||
_hover={{ borderColor: darkGoldTheme.gold }}
|
||||
_focus={{ borderColor: darkGoldTheme.gold, boxShadow: 'none' }}
|
||||
/>
|
||||
{(searchQuery || isSearching) && (
|
||||
<InputRightElement>
|
||||
{isSearching || isLoadingData ? (
|
||||
<Spinner size="xs" color={darkGoldTheme.gold} />
|
||||
) : (
|
||||
<IconButton
|
||||
size="xs"
|
||||
variant="ghost"
|
||||
icon={<CloseIcon w="8px" h="8px" />}
|
||||
onClick={() => {
|
||||
setSearchQuery('');
|
||||
setSearchResults([]);
|
||||
}}
|
||||
aria-label="Clear"
|
||||
_hover={{ bg: 'transparent' }}
|
||||
/>
|
||||
)}
|
||||
</InputRightElement>
|
||||
)}
|
||||
</InputGroup>
|
||||
|
||||
{/* Search results */}
|
||||
{searchResults.length > 0 ? (
|
||||
<List spacing={0} maxH="300px" overflowY="auto">
|
||||
{searchResults.map((metric) => {
|
||||
const isAdded = overlayMetrics.some(m => m.metric_id === metric.metric_id);
|
||||
return (
|
||||
<ListItem
|
||||
key={metric.metric_id}
|
||||
px={3}
|
||||
py={2}
|
||||
cursor={isAdded ? 'default' : 'pointer'}
|
||||
opacity={isAdded ? 0.5 : 1}
|
||||
_hover={isAdded ? {} : { bg: 'rgba(212, 175, 55, 0.1)' }}
|
||||
onClick={() => !isAdded && handleSelectMetric(metric)}
|
||||
borderRadius="md"
|
||||
>
|
||||
<Flex justify="space-between" align="center">
|
||||
<VStack align="start" spacing={0} flex={1}>
|
||||
<Text
|
||||
fontSize="sm"
|
||||
color={darkGoldTheme.gold}
|
||||
fontWeight="medium"
|
||||
noOfLines={1}
|
||||
>
|
||||
{metric.metric_name}
|
||||
</Text>
|
||||
<HStack spacing={2}>
|
||||
<Text fontSize="xs" color={darkGoldTheme.textMuted}>
|
||||
{metric.source}
|
||||
</Text>
|
||||
{metric.frequency && (
|
||||
<Badge
|
||||
size="sm"
|
||||
bg="rgba(212, 175, 55, 0.1)"
|
||||
color={darkGoldTheme.gold}
|
||||
fontSize="10px"
|
||||
>
|
||||
{metric.frequency}
|
||||
</Badge>
|
||||
)}
|
||||
{metric.unit && (
|
||||
<Text fontSize="xs" color={darkGoldTheme.textMuted}>
|
||||
{metric.unit}
|
||||
</Text>
|
||||
)}
|
||||
</HStack>
|
||||
</VStack>
|
||||
{isAdded ? (
|
||||
<Badge colorScheme="green" fontSize="10px">Added</Badge>
|
||||
) : (
|
||||
<AddIcon boxSize={3} color={darkGoldTheme.textMuted} />
|
||||
)}
|
||||
</Flex>
|
||||
</ListItem>
|
||||
);
|
||||
})}
|
||||
</List>
|
||||
) : searchQuery && !isSearching ? (
|
||||
<Text color={darkGoldTheme.textMuted} fontSize="sm" textAlign="center" py={4}>
|
||||
No metrics found
|
||||
</Text>
|
||||
) : !searchQuery ? (
|
||||
<VStack spacing={2} py={4}>
|
||||
<TrendingUp size={24} color={darkGoldTheme.textMuted} />
|
||||
<Text color={darkGoldTheme.textMuted} fontSize="sm" textAlign="center">
|
||||
Enter keywords to search
|
||||
</Text>
|
||||
<Text color={darkGoldTheme.textMuted} fontSize="xs" textAlign="center">
|
||||
Supports SMM and Mysteel data sources
|
||||
</Text>
|
||||
</VStack>
|
||||
) : null}
|
||||
</PopoverBody>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</HStack>
|
||||
);
|
||||
};
|
||||
|
||||
export default MetricOverlaySearch;
|
||||
@@ -351,6 +351,18 @@ export interface AnalysisModalContentProps {
|
||||
theme: Theme;
|
||||
}
|
||||
|
||||
/**
|
||||
* 叠加指标数据
|
||||
*/
|
||||
export interface OverlayMetricData {
|
||||
metric_id: string;
|
||||
metric_name: string;
|
||||
source: string;
|
||||
unit: string;
|
||||
data: { date: string; value: number | null }[];
|
||||
color?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* useMarketData Hook 返回值
|
||||
*/
|
||||
|
||||
@@ -9,6 +9,7 @@ import type {
|
||||
FundingDayData,
|
||||
PledgeData,
|
||||
RiseAnalysis,
|
||||
OverlayMetricData,
|
||||
} from '../types';
|
||||
import { formatNumber } from './formatUtils';
|
||||
|
||||
@@ -856,6 +857,7 @@ export const calculateTrendLine = (
|
||||
* @param mainIndicator 主图指标 (默认MA)
|
||||
* @param showAnalysis 是否显示涨幅分析标记 (默认true)
|
||||
* @param drawingType 绘图工具类型 (默认NONE)
|
||||
* @param overlayMetrics 叠加的商品指标数据 (可选)
|
||||
*/
|
||||
export const getKLineDarkGoldOption = (
|
||||
tradeData: TradeDayData[],
|
||||
@@ -863,7 +865,8 @@ export const getKLineDarkGoldOption = (
|
||||
subIndicator: IndicatorType = 'MACD',
|
||||
mainIndicator: MainIndicatorType = 'MA',
|
||||
showAnalysis: boolean = true,
|
||||
drawingType: DrawingType = 'NONE'
|
||||
drawingType: DrawingType = 'NONE',
|
||||
overlayMetrics: OverlayMetricData[] = []
|
||||
): EChartsOption => {
|
||||
if (!tradeData || tradeData.length === 0) return {};
|
||||
|
||||
@@ -1034,6 +1037,65 @@ export const getKLineDarkGoldOption = (
|
||||
});
|
||||
}
|
||||
|
||||
// 处理叠加指标数据 - 使用右侧Y轴
|
||||
const hasOverlayMetrics = overlayMetrics && overlayMetrics.length > 0;
|
||||
const overlaySeriesData: { name: string; data: (number | null)[]; color: string; unit: string }[] = [];
|
||||
|
||||
if (hasOverlayMetrics) {
|
||||
// 创建日期到索引的映射(股票数据日期)
|
||||
const fullDates = tradeData.map((item) => item.date.substring(0, 10));
|
||||
const dateToIndex: Record<string, number> = {};
|
||||
fullDates.forEach((date, idx) => {
|
||||
dateToIndex[date] = idx;
|
||||
});
|
||||
|
||||
// 为每个叠加指标处理数据
|
||||
overlayMetrics.forEach((metric) => {
|
||||
const seriesData: (number | null)[] = new Array(tradeData.length).fill(null);
|
||||
|
||||
metric.data.forEach((point) => {
|
||||
if (point.value !== null && point.value !== undefined) {
|
||||
const pointDate = point.date.substring(0, 10);
|
||||
const idx = dateToIndex[pointDate];
|
||||
if (idx !== undefined) {
|
||||
seriesData[idx] = point.value;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
overlaySeriesData.push({
|
||||
name: metric.metric_name,
|
||||
data: seriesData,
|
||||
color: metric.color || '#00D4FF',
|
||||
unit: metric.unit,
|
||||
});
|
||||
});
|
||||
|
||||
// 为叠加指标添加右侧Y轴(只添加一个,所有叠加指标共用)
|
||||
yAxes[0] = {
|
||||
...yAxes[0],
|
||||
position: 'left',
|
||||
};
|
||||
|
||||
// 右侧Y轴用于显示叠加指标
|
||||
const overlayYAxisIndex = hasSubIndicator ? 3 : 2;
|
||||
yAxes.push({
|
||||
scale: true,
|
||||
position: 'right',
|
||||
axisLine: { lineStyle: { color: overlaySeriesData[0]?.color || '#00D4FF' } },
|
||||
axisLabel: {
|
||||
color: overlaySeriesData[0]?.color || '#00D4FF',
|
||||
fontSize: 10,
|
||||
formatter: (value: number) => {
|
||||
if (Math.abs(value) >= 10000) return (value / 10000).toFixed(1) + '万';
|
||||
if (Math.abs(value) >= 1000) return (value / 1000).toFixed(1) + 'k';
|
||||
return value.toFixed(1);
|
||||
}
|
||||
},
|
||||
splitLine: { show: false },
|
||||
});
|
||||
}
|
||||
|
||||
// 图例数据
|
||||
const legendData = ['K线'];
|
||||
if (mainIndicator === 'MA') {
|
||||
@@ -1458,6 +1520,33 @@ export const getKLineDarkGoldOption = (
|
||||
legendData.push('BIAS6', 'BIAS12', 'BIAS24');
|
||||
}
|
||||
|
||||
// 添加叠加指标的 series(使用右侧Y轴)
|
||||
if (hasOverlayMetrics && overlaySeriesData.length > 0) {
|
||||
const overlayYAxisIndex = hasSubIndicator ? 3 : 2;
|
||||
overlaySeriesData.forEach((overlayItem) => {
|
||||
series.push({
|
||||
name: overlayItem.name,
|
||||
type: 'line',
|
||||
yAxisIndex: overlayYAxisIndex,
|
||||
data: overlayItem.data,
|
||||
smooth: true,
|
||||
symbol: 'none',
|
||||
lineStyle: {
|
||||
color: overlayItem.color,
|
||||
width: 2,
|
||||
type: 'solid',
|
||||
},
|
||||
emphasis: {
|
||||
lineStyle: {
|
||||
width: 3,
|
||||
},
|
||||
},
|
||||
z: 90, // 确保在K线上方显示
|
||||
});
|
||||
legendData.push(overlayItem.name);
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
backgroundColor: 'transparent',
|
||||
animation: true,
|
||||
@@ -1500,7 +1589,14 @@ export const getKLineDarkGoldOption = (
|
||||
// 跳过涨幅分析标记
|
||||
} else if (typeof value === 'number' || (value !== null && !isNaN(Number(value)))) {
|
||||
// MA、BOLL、MACD、KDJ、RSI 等指标
|
||||
result += `${marker} ${seriesName}:${formatPrice(Number(value))}<br/>`;
|
||||
// 检查是否是叠加指标
|
||||
const overlayItem = overlaySeriesData.find(o => o.name === seriesName);
|
||||
if (overlayItem) {
|
||||
// 叠加指标,显示单位
|
||||
result += `${marker} <span style="color: ${overlayItem.color}">${seriesName}:${formatPrice(Number(value))}${overlayItem.unit ? ' ' + overlayItem.unit : ''}</span><br/>`;
|
||||
} else {
|
||||
result += `${marker} ${seriesName}:${formatPrice(Number(value))}<br/>`;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user