feat: StockChartModal.tsx 替换 KLine 实现

This commit is contained in:
zdl
2025-11-24 13:59:29 +08:00
parent b4dcbd1db9
commit 2f125a9207
10 changed files with 768 additions and 630 deletions

View File

@@ -38,6 +38,7 @@ export const convertToKLineData = (
close: Number(item.close) || 0,
volume: Number(item.volume) || 0,
turnover: item.turnover ? Number(item.turnover) : undefined,
prev_close: item.prev_close ? Number(item.prev_close) : undefined, // ✅ 新增:昨收价(用于百分比计算和基准线)
};
});
} catch (error) {
@@ -171,10 +172,66 @@ export const deduplicateData = (data: KLineDataPoint[]): KLineDataPoint[] => {
return Array.from(map.values());
};
/**
* 根据事件时间裁剪数据范围前后2周
*
* @param data K线数据
* @param eventTime 事件时间ISO字符串
* @param chartType 图表类型
* @returns KLineDataPoint[] 裁剪后的数据
*/
export const trimDataByEventTime = (
data: KLineDataPoint[],
eventTime: string,
chartType: ChartType
): KLineDataPoint[] => {
if (!eventTime || !data || data.length === 0) {
return data;
}
try {
const eventTimestamp = dayjs(eventTime).valueOf();
// 根据图表类型设置不同的时间范围
let beforeDays: number;
let afterDays: number;
if (chartType === 'timeline') {
// 分时图只显示事件当天前后0天
beforeDays = 0;
afterDays = 0;
} else {
// 日K线显示前后14天2周
beforeDays = 14;
afterDays = 14;
}
const startTime = dayjs(eventTime).subtract(beforeDays, 'day').startOf('day').valueOf();
const endTime = dayjs(eventTime).add(afterDays, 'day').endOf('day').valueOf();
const trimmedData = data.filter((item) => {
return item.timestamp >= startTime && item.timestamp <= endTime;
});
logger.debug('dataAdapter', 'trimDataByEventTime', '数据时间范围裁剪完成', {
originalLength: data.length,
trimmedLength: trimmedData.length,
eventTime,
chartType,
dateRange: `${dayjs(startTime).format('YYYY-MM-DD')} ~ ${dayjs(endTime).format('YYYY-MM-DD')}`,
});
return trimmedData;
} catch (error) {
logger.error('dataAdapter', 'trimDataByEventTime', error as Error, { eventTime });
return data; // 出错时返回原始数据
}
};
/**
* 完整的数据处理流程
*
* 转换 → 验证 → 去重 → 排序
* 转换 → 验证 → 去重 → 排序 → 时间裁剪(如果有 eventTime
*
* @param rawData 后端原始数据
* @param chartType 图表类型
@@ -198,10 +255,16 @@ export const processChartData = (
// 4. 排序
data = sortDataByTime(data);
// 5. 根据事件时间裁剪范围(如果提供了 eventTime
if (eventTime) {
data = trimDataByEventTime(data, eventTime, chartType);
}
logger.debug('dataAdapter', 'processChartData', '数据处理完成', {
rawLength: rawData.length,
processedLength: data.length,
chartType,
hasEventTime: !!eventTime,
});
return data;

View File

@@ -92,6 +92,61 @@ export const createEventMarkerOverlay = (
}
};
/**
* 创建事件日K线黄色高亮覆盖层垂直矩形背景
*
* @param eventTime 事件时间ISO字符串
* @param data K线数据
* @returns OverlayCreate | null 高亮覆盖层配置
*/
export const createEventHighlightOverlay = (
eventTime: string,
data: KLineDataPoint[]
): OverlayCreate | null => {
try {
const eventTimestamp = dayjs(eventTime).valueOf();
const closestPoint = findClosestDataPoint(data, eventTimestamp);
if (!closestPoint) {
logger.warn('eventMarkerUtils', 'createEventHighlightOverlay', '未找到匹配的数据点');
return null;
}
// 创建垂直矩形覆盖层(从图表顶部到底部的黄色半透明背景)
const overlay: OverlayCreate = {
name: 'rect', // 矩形覆盖层
id: `event-highlight-${eventTimestamp}`,
points: [
{
timestamp: closestPoint.timestamp,
value: closestPoint.high * 1.05, // 顶部位置高于最高价5%
},
{
timestamp: closestPoint.timestamp,
value: closestPoint.low * 0.95, // 底部位置低于最低价5%
},
],
styles: {
style: 'fill',
color: 'rgba(255, 193, 7, 0.15)', // 黄色半透明背景15%透明度)
borderColor: '#FFD54F', // 黄色边框
borderSize: 2,
borderStyle: 'solid',
},
};
logger.debug('eventMarkerUtils', 'createEventHighlightOverlay', '创建事件高亮覆盖层', {
timestamp: closestPoint.timestamp,
eventTime,
});
return overlay;
} catch (error) {
logger.error('eventMarkerUtils', 'createEventHighlightOverlay', error as Error);
return null;
}
};
/**
* 计算标记的 Y 轴位置
*