feat: 添加相关股票模块

This commit is contained in:
zdl
2025-11-01 12:19:47 +08:00
parent 571d5e68bc
commit 3b8b749eb1
6 changed files with 663 additions and 15 deletions

View File

@@ -0,0 +1,184 @@
// src/views/Community/components/DynamicNewsDetail/MiniKLineChart.js
import React, { useState, useEffect, useMemo, useRef } from 'react';
import ReactECharts from 'echarts-for-react';
import moment from 'moment';
import {
fetchKlineData,
getCacheKey,
klineDataCache
} from '../StockDetailPanel/utils/klineDataCache';
/**
* 迷你K线图组件
* 显示股票的K线走势蜡烛图支持事件时间标记
*
* @param {string} stockCode - 股票代码
* @param {string} eventTime - 事件时间(可选)
* @param {Function} onClick - 点击回调(可选)
* @returns {JSX.Element}
*/
const MiniKLineChart = React.memo(function MiniKLineChart({ stockCode, eventTime, onClick }) {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(false);
const mountedRef = useRef(true);
const loadedRef = useRef(false);
const dataFetchedRef = useRef(false);
// 稳定的事件时间
const stableEventTime = useMemo(() => {
return eventTime ? moment(eventTime).format('YYYY-MM-DD HH:mm') : '';
}, [eventTime]);
useEffect(() => {
mountedRef.current = true;
return () => {
mountedRef.current = false;
};
}, []);
useEffect(() => {
if (!stockCode) {
setData([]);
loadedRef.current = false;
dataFetchedRef.current = false;
return;
}
if (dataFetchedRef.current) {
return;
}
// 检查缓存K线图使用 'daily' 类型)
const cacheKey = getCacheKey(stockCode, stableEventTime, 'daily');
const cachedData = klineDataCache.get(cacheKey);
if (cachedData && cachedData.length > 0) {
setData(cachedData);
loadedRef.current = true;
dataFetchedRef.current = true;
return;
}
dataFetchedRef.current = true;
setLoading(true);
// 获取日K线数据
fetchKlineData(stockCode, stableEventTime, 'daily')
.then((result) => {
if (mountedRef.current) {
setData(result);
setLoading(false);
loadedRef.current = true;
}
})
.catch(() => {
if (mountedRef.current) {
setData([]);
setLoading(false);
loadedRef.current = true;
}
});
}, [stockCode, stableEventTime]);
const chartOption = useMemo(() => {
// 提取K线数据 [open, close, low, high]
const klineData = data
.filter(item => item.open && item.close && item.low && item.high)
.map(item => [item.open, item.close, item.low, item.high]);
// 日K线使用 date 字段
const dates = data.map(item => item.date || item.time);
const hasData = klineData.length > 0;
if (!hasData) {
return {
title: {
text: loading ? '加载中...' : '无数据',
left: 'center',
top: 'middle',
textStyle: { color: '#999', fontSize: 10 }
}
};
}
// 计算事件时间标记
let eventMarkLineData = [];
if (stableEventTime && Array.isArray(dates) && dates.length > 0) {
try {
const eventDate = moment(stableEventTime).format('YYYY-MM-DD');
const eventIdx = dates.findIndex(d => {
const dateStr = typeof d === 'object' ? moment(d).format('YYYY-MM-DD') : String(d);
return dateStr.includes(eventDate);
});
if (eventIdx >= 0) {
eventMarkLineData.push({
xAxis: eventIdx,
lineStyle: { color: '#FFD700', type: 'solid', width: 1.5 },
label: { show: false }
});
}
} catch (e) {
// 忽略异常
}
}
return {
grid: { left: 2, right: 2, top: 2, bottom: 2, containLabel: false },
xAxis: {
type: 'category',
data: dates,
show: false,
boundaryGap: true
},
yAxis: {
type: 'value',
show: false,
scale: true
},
series: [{
type: 'candlestick',
data: klineData,
itemStyle: {
color: '#ef5350', // 涨(阳线)
color0: '#26a69a', // 跌(阴线)
borderColor: '#ef5350', // 涨(边框)
borderColor0: '#26a69a' // 跌(边框)
},
barWidth: '60%',
markLine: {
silent: true,
symbol: 'none',
label: { show: false },
data: eventMarkLineData
}
}],
tooltip: { show: false },
animation: false
};
}, [data, loading, stableEventTime]);
return (
<div
style={{
width: 140,
height: 40,
cursor: onClick ? 'pointer' : 'default'
}}
onClick={onClick}
>
<ReactECharts
option={chartOption}
style={{ width: '100%', height: '100%' }}
notMerge={true}
lazyUpdate={true}
/>
</div>
);
}, (prevProps, nextProps) => {
return prevProps.stockCode === nextProps.stockCode &&
prevProps.eventTime === nextProps.eventTime &&
prevProps.onClick === nextProps.onClick;
});
export default MiniKLineChart;