feat: StockChartModal.tsx 替换 KLine 实现
This commit is contained in:
@@ -9,7 +9,8 @@ import type { Chart } from 'klinecharts';
|
||||
import type { ChartType, KLineDataPoint, RawDataPoint } from '../types';
|
||||
import { processChartData } from '../utils/dataAdapter';
|
||||
import { logger } from '@utils/logger';
|
||||
import { stockService } from '@services/stockService';
|
||||
import { stockService } from '@services/eventService';
|
||||
import { klineDataCache, getCacheKey } from '@views/Community/components/StockDetailPanel/utils/klineDataCache';
|
||||
|
||||
export interface UseKLineDataOptions {
|
||||
/** KLineChart 实例 */
|
||||
@@ -91,22 +92,38 @@ export const useKLineData = (
|
||||
eventTime,
|
||||
});
|
||||
|
||||
// 调用后端 API 获取数据
|
||||
const response = await stockService.getKlineData(
|
||||
stockCode,
|
||||
chartType,
|
||||
eventTime
|
||||
);
|
||||
// 1. 先检查缓存
|
||||
const cacheKey = getCacheKey(stockCode, eventTime, chartType);
|
||||
const cachedData = klineDataCache.get(cacheKey);
|
||||
|
||||
if (!response || !response.data) {
|
||||
throw new Error('后端返回数据为空');
|
||||
let rawDataList;
|
||||
|
||||
if (cachedData && cachedData.length > 0) {
|
||||
// 使用缓存数据
|
||||
rawDataList = cachedData;
|
||||
} else {
|
||||
// 2. 缓存没有数据,调用 API 请求
|
||||
const response = await stockService.getKlineData(
|
||||
stockCode,
|
||||
chartType,
|
||||
eventTime
|
||||
);
|
||||
|
||||
if (!response || !response.data) {
|
||||
throw new Error('后端返回数据为空');
|
||||
}
|
||||
|
||||
rawDataList = response.data;
|
||||
|
||||
// 3. 将数据写入缓存(避免下次重复请求)
|
||||
klineDataCache.set(cacheKey, rawDataList);
|
||||
}
|
||||
|
||||
const rawDataList = response.data;
|
||||
setRawData(rawDataList);
|
||||
|
||||
// 数据转换和处理
|
||||
const processedData = processChartData(rawDataList, chartType, eventTime);
|
||||
|
||||
setData(processedData);
|
||||
|
||||
logger.info('useKLineData', 'loadData', '数据加载成功', {
|
||||
@@ -130,7 +147,7 @@ export const useKLineData = (
|
||||
}, [stockCode, chartType, eventTime]);
|
||||
|
||||
/**
|
||||
* 更新图表数据(使用 DataLoader 模式)
|
||||
* 更新图表数据(使用 setDataLoader 方法)
|
||||
*/
|
||||
const updateChartData = useCallback(
|
||||
(klineData: KLineDataPoint[]) => {
|
||||
@@ -139,29 +156,120 @@ export const useKLineData = (
|
||||
}
|
||||
|
||||
try {
|
||||
// KLineChart 10.0: 使用 setDataLoader 方法
|
||||
chart.setDataLoader({
|
||||
getBars: (params) => {
|
||||
// 将数据传递给图表
|
||||
params.callback(klineData, { more: false });
|
||||
// 步骤 1: 设置 symbol(必需!getBars 调用的前置条件)
|
||||
(chart as any).setSymbol({
|
||||
ticker: stockCode || 'UNKNOWN', // 股票代码
|
||||
pricePrecision: 2, // 价格精度(2位小数)
|
||||
volumePrecision: 0 // 成交量精度(整数)
|
||||
});
|
||||
|
||||
logger.debug('useKLineData', 'updateChartData', 'DataLoader 回调', {
|
||||
dataCount: klineData.length,
|
||||
// 步骤 2: 设置 period(必需!getBars 调用的前置条件)
|
||||
const periodType = chartType === 'timeline' ? 'minute' : 'day';
|
||||
(chart as any).setPeriod({
|
||||
type: periodType, // 分时图=minute,日K=day
|
||||
span: 1 // 周期跨度(1分钟/1天)
|
||||
});
|
||||
|
||||
// 步骤 3: 设置 DataLoader(同步数据加载器)
|
||||
(chart as any).setDataLoader({
|
||||
getBars: (params: any) => {
|
||||
if (params.type === 'init') {
|
||||
// 初始化加载:返回完整数据
|
||||
params.callback(klineData, false); // false = 无更多数据可加载
|
||||
} else if (params.type === 'forward' || params.type === 'backward') {
|
||||
// 向前/向后加载:我们没有更多数据,返回空数组
|
||||
params.callback([], false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 步骤 4: 触发初始化加载(这会调用 getBars with type="init")
|
||||
(chart as any).resetData();
|
||||
|
||||
// 步骤 5: 根据数据量调整可见范围和柱子间距(让 K 线柱子填满图表区域)
|
||||
setTimeout(() => {
|
||||
try {
|
||||
const dataLength = klineData.length;
|
||||
|
||||
if (dataLength > 0) {
|
||||
// 获取图表容器宽度
|
||||
const chartDom = (chart as any).getDom();
|
||||
const chartWidth = chartDom?.clientWidth || 1200;
|
||||
|
||||
// 计算最优柱子间距
|
||||
// 公式:barSpace = (图表宽度 / 数据数量) * 0.7
|
||||
// 0.7 是为了留出一些间距,让图表不会太拥挤
|
||||
const optimalBarSpace = Math.max(8, Math.min(50, (chartWidth / dataLength) * 0.7));
|
||||
|
||||
(chart as any).setBarSpace(optimalBarSpace);
|
||||
|
||||
// 减少右侧空白(默认值可能是 100-200,调小会减少右侧空白)
|
||||
(chart as any).setOffsetRightDistance(50);
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error('useKLineData', 'updateChartData', err as Error, {
|
||||
step: '调整可见范围失败',
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
}, 100); // 延迟 100ms 确保数据已加载和渲染
|
||||
|
||||
logger.debug('useKLineData', 'updateChartData', '图表数据已更新', {
|
||||
dataCount: klineData.length,
|
||||
chartId: chart.id,
|
||||
});
|
||||
// ✅ 步骤 4: 分时图添加均价线(使用自定义 AVG 指标)
|
||||
if (chartType === 'timeline' && klineData.length > 0) {
|
||||
setTimeout(() => {
|
||||
try {
|
||||
// 在主图窗格创建 AVG 均价线指标
|
||||
(chart as any).createIndicator('AVG', true, {
|
||||
id: 'candle_pane', // 主图窗格
|
||||
});
|
||||
|
||||
console.log('[DEBUG] ✅ 均价线(AVG指标)添加成功');
|
||||
} catch (err) {
|
||||
console.error('[DEBUG] ❌ 均价线添加失败:', err);
|
||||
}
|
||||
}, 150); // 延迟 150ms,确保数据加载完成后再创建指标
|
||||
|
||||
// ✅ 步骤 5: 添加昨收价基准线(灰色虚线)
|
||||
setTimeout(() => {
|
||||
try {
|
||||
const prevClose = klineData[0]?.prev_close;
|
||||
if (prevClose && prevClose > 0) {
|
||||
// 创建水平线覆盖层
|
||||
(chart as any).createOverlay({
|
||||
name: 'horizontalStraightLine',
|
||||
id: 'prev_close_line',
|
||||
points: [{ value: prevClose }],
|
||||
styles: {
|
||||
line: {
|
||||
style: 'dashed',
|
||||
dashValue: [4, 2],
|
||||
size: 1,
|
||||
color: '#888888', // 灰色虚线
|
||||
},
|
||||
},
|
||||
extendData: {
|
||||
label: `昨收: ${prevClose.toFixed(2)}`,
|
||||
},
|
||||
});
|
||||
|
||||
console.log('[DEBUG] ✅ 昨收价基准线添加成功:', prevClose);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[DEBUG] ❌ 昨收价基准线添加失败:', err);
|
||||
}
|
||||
}, 200); // 延迟 200ms,确保均价线创建完成后再添加
|
||||
}
|
||||
|
||||
logger.debug(
|
||||
'useKLineData',
|
||||
`updateChartData - ${stockCode} (${chartType}) - ${klineData.length}条数据加载成功`
|
||||
);
|
||||
} catch (err) {
|
||||
logger.error('useKLineData', 'updateChartData', err as Error, {
|
||||
dataCount: klineData.length,
|
||||
});
|
||||
}
|
||||
},
|
||||
[chart]
|
||||
[chart, stockCode, chartType]
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -172,9 +280,10 @@ export const useKLineData = (
|
||||
setData(newData);
|
||||
updateChartData(newData);
|
||||
|
||||
logger.debug('useKLineData', 'updateData', '手动更新数据', {
|
||||
newDataCount: newData.length,
|
||||
});
|
||||
logger.debug(
|
||||
'useKLineData',
|
||||
`updateData - ${stockCode} (${chartType}) - ${newData.length}条数据手动更新`
|
||||
);
|
||||
},
|
||||
[updateChartData]
|
||||
);
|
||||
@@ -189,9 +298,7 @@ export const useKLineData = (
|
||||
|
||||
if (chart) {
|
||||
chart.resetData();
|
||||
logger.debug('useKLineData', 'clearData', '清空数据', {
|
||||
chartId: chart.id,
|
||||
});
|
||||
logger.debug('useKLineData', `clearData - chartId: ${(chart as any).id}`);
|
||||
}
|
||||
}, [chart]);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user