feat: StockChartModal.tsx 替换 KLine 实现
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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 轴位置
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user