update pay ui
This commit is contained in:
@@ -6,30 +6,6 @@ import { logger } from '@utils/logger';
|
|||||||
import axios from '@utils/axiosConfig';
|
import axios from '@utils/axiosConfig';
|
||||||
import NewsEventsTab from '../../CompanyOverview/NewsEventsTab';
|
import NewsEventsTab from '../../CompanyOverview/NewsEventsTab';
|
||||||
|
|
||||||
/**
|
|
||||||
* 标准化股票代码(补全后缀)
|
|
||||||
* @param {string} code - 股票代码(可能不带后缀)
|
|
||||||
* @returns {string} 带后缀的股票代码
|
|
||||||
*/
|
|
||||||
const normalizeStockCode = (code) => {
|
|
||||||
if (!code) return code;
|
|
||||||
// 已有后缀则直接返回
|
|
||||||
if (code.includes('.')) return code;
|
|
||||||
|
|
||||||
const pureCode = code.replace(/\D/g, '');
|
|
||||||
if (pureCode.length !== 6) return code;
|
|
||||||
|
|
||||||
const prefix = pureCode[0];
|
|
||||||
// 上交所:6、9 开头
|
|
||||||
if (prefix === '6' || prefix === '9') return `${pureCode}.SH`;
|
|
||||||
// 深交所:0、2、3 开头
|
|
||||||
if (prefix === '0' || prefix === '2' || prefix === '3') return `${pureCode}.SZ`;
|
|
||||||
// 北交所:8、4 开头
|
|
||||||
if (prefix === '8' || prefix === '4') return `${pureCode}.BJ`;
|
|
||||||
|
|
||||||
return code;
|
|
||||||
};
|
|
||||||
|
|
||||||
const NewsPanel = ({ stockCode }) => {
|
const NewsPanel = ({ stockCode }) => {
|
||||||
const [newsEvents, setNewsEvents] = useState([]);
|
const [newsEvents, setNewsEvents] = useState([]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
@@ -42,39 +18,22 @@ const NewsPanel = ({ stockCode }) => {
|
|||||||
has_prev: false,
|
has_prev: false,
|
||||||
});
|
});
|
||||||
const [searchQuery, setSearchQuery] = useState('');
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
const [stockName, setStockName] = useState('');
|
|
||||||
|
|
||||||
// 获取股票名称
|
|
||||||
const fetchStockName = useCallback(async () => {
|
|
||||||
try {
|
|
||||||
const { data: result } = await axios.get(
|
|
||||||
`/api/stock/${stockCode}/basic-info`
|
|
||||||
);
|
|
||||||
if (result.success && result.data) {
|
|
||||||
const name = result.data.SECNAME || result.data.ORGNAME || stockCode;
|
|
||||||
setStockName(name);
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
return stockCode;
|
|
||||||
} catch (err) {
|
|
||||||
logger.error('NewsPanel', 'fetchStockName', err, { stockCode });
|
|
||||||
return stockCode;
|
|
||||||
}
|
|
||||||
}, [stockCode]);
|
|
||||||
|
|
||||||
// 加载新闻事件
|
// 加载新闻事件
|
||||||
const loadNewsEvents = useCallback(
|
const loadNewsEvents = useCallback(
|
||||||
async (query, page = 1) => {
|
async (query, page = 1) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
// 优先使用自定义查询,否则用股票名称,最后用带后缀的股票代码
|
// 优先使用自定义查询,否则用股票代码(6位纯数字即可)
|
||||||
const searchTerm = query || stockName || normalizeStockCode(stockCode);
|
const searchTerm = query || stockCode.replace(/\.\w+$/, ''); // 移除后缀如 .SZ
|
||||||
const { data: result } = await axios.get(
|
const { data: result } = await axios.get(
|
||||||
`/api/events?q=${encodeURIComponent(searchTerm)}&page=${page}&per_page=10`
|
`/api/events?sort=new&importance=all&q=${encodeURIComponent(searchTerm)}&page=${page}&mode=vertical&per_page=10`
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
setNewsEvents(result.data || []);
|
// API 返回结构: { data: { events: [...] }, pagination: {...} }
|
||||||
|
const events = result.data?.events || result.data || [];
|
||||||
|
setNewsEvents(events);
|
||||||
setPagination({
|
setPagination({
|
||||||
page: result.pagination?.page || page,
|
page: result.pagination?.page || page,
|
||||||
per_page: result.pagination?.per_page || 10,
|
per_page: result.pagination?.per_page || 10,
|
||||||
@@ -91,19 +50,15 @@ const NewsPanel = ({ stockCode }) => {
|
|||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[stockCode, stockName]
|
[stockCode]
|
||||||
);
|
);
|
||||||
|
|
||||||
// 首次加载
|
// 首次加载 - 直接用股票代码搜索
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const initLoad = async () => {
|
|
||||||
if (stockCode) {
|
if (stockCode) {
|
||||||
const name = await fetchStockName();
|
loadNewsEvents(null, 1);
|
||||||
await loadNewsEvents(name, 1);
|
|
||||||
}
|
}
|
||||||
};
|
}, [stockCode, loadNewsEvents]);
|
||||||
initLoad();
|
|
||||||
}, [stockCode, fetchStockName, loadNewsEvents]);
|
|
||||||
|
|
||||||
// 搜索处理
|
// 搜索处理
|
||||||
const handleSearchChange = (value) => {
|
const handleSearchChange = (value) => {
|
||||||
@@ -111,12 +66,12 @@ const NewsPanel = ({ stockCode }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleSearch = () => {
|
const handleSearch = () => {
|
||||||
loadNewsEvents(searchQuery || stockName, 1);
|
loadNewsEvents(searchQuery || null, 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 分页处理
|
// 分页处理
|
||||||
const handlePageChange = (page) => {
|
const handlePageChange = (page) => {
|
||||||
loadNewsEvents(searchQuery || stockName, page);
|
loadNewsEvents(searchQuery || null, page);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -41,6 +41,9 @@ export const useMarketData = (
|
|||||||
const [minuteData, setMinuteData] = useState<MinuteData | null>(null);
|
const [minuteData, setMinuteData] = useState<MinuteData | null>(null);
|
||||||
const [minuteLoading, setMinuteLoading] = useState(false);
|
const [minuteLoading, setMinuteLoading] = useState(false);
|
||||||
|
|
||||||
|
// 涨幅分析懒加载状态
|
||||||
|
const [analysisLoading, setAnalysisLoading] = useState(false);
|
||||||
|
|
||||||
// 记录是否已完成首次加载
|
// 记录是否已完成首次加载
|
||||||
const isInitializedRef = useRef(false);
|
const isInitializedRef = useRef(false);
|
||||||
// 记录上一次的 stockCode,用于判断是否需要重新加载所有数据
|
// 记录上一次的 stockCode,用于判断是否需要重新加载所有数据
|
||||||
@@ -49,15 +52,50 @@ export const useMarketData = (
|
|||||||
const prevPeriodRef = useRef(period);
|
const prevPeriodRef = useRef(period);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 加载所有市场数据
|
* 加载涨幅分析数据(懒加载)
|
||||||
|
* 需要 tradeData 来建立日期索引映射
|
||||||
|
*/
|
||||||
|
const loadRiseAnalysis = useCallback(async (tradeDataForMapping: TradeDayData[]) => {
|
||||||
|
if (!stockCode || tradeDataForMapping.length === 0) return;
|
||||||
|
|
||||||
|
logger.debug('useMarketData', '开始懒加载涨幅分析', { stockCode });
|
||||||
|
setAnalysisLoading(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const riseAnalysisRes = await marketService.getRiseAnalysis(stockCode);
|
||||||
|
|
||||||
|
if (riseAnalysisRes.success && riseAnalysisRes.data) {
|
||||||
|
const tempAnalysisMap: Record<number, RiseAnalysis> = {};
|
||||||
|
riseAnalysisRes.data.forEach((analysis) => {
|
||||||
|
const dateIndex = tradeDataForMapping.findIndex(
|
||||||
|
(item) => item.date.substring(0, 10) === analysis.trade_date
|
||||||
|
);
|
||||||
|
if (dateIndex !== -1) {
|
||||||
|
tempAnalysisMap[dateIndex] = analysis;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setAnalysisMap(tempAnalysisMap);
|
||||||
|
logger.info('useMarketData', '涨幅分析加载成功', { stockCode, count: Object.keys(tempAnalysisMap).length });
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('useMarketData', 'loadRiseAnalysis', error, { stockCode });
|
||||||
|
} finally {
|
||||||
|
setAnalysisLoading(false);
|
||||||
|
}
|
||||||
|
}, [stockCode]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载所有市场数据(涨幅分析延迟加载)
|
||||||
*/
|
*/
|
||||||
const loadMarketData = useCallback(async () => {
|
const loadMarketData = useCallback(async () => {
|
||||||
if (!stockCode) return;
|
if (!stockCode) return;
|
||||||
|
|
||||||
logger.debug('useMarketData', '开始加载市场数据', { stockCode, period });
|
logger.debug('useMarketData', '开始加载市场数据', { stockCode, period });
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
setAnalysisMap({}); // 清空旧的分析数据
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// 先加载核心数据(不含涨幅分析)
|
||||||
const [
|
const [
|
||||||
summaryRes,
|
summaryRes,
|
||||||
tradeRes,
|
tradeRes,
|
||||||
@@ -65,7 +103,6 @@ export const useMarketData = (
|
|||||||
bigDealRes,
|
bigDealRes,
|
||||||
unusualRes,
|
unusualRes,
|
||||||
pledgeRes,
|
pledgeRes,
|
||||||
riseAnalysisRes,
|
|
||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
marketService.getMarketSummary(stockCode),
|
marketService.getMarketSummary(stockCode),
|
||||||
marketService.getTradeData(stockCode, period),
|
marketService.getTradeData(stockCode, period),
|
||||||
@@ -73,7 +110,6 @@ export const useMarketData = (
|
|||||||
marketService.getBigDealData(stockCode, 30),
|
marketService.getBigDealData(stockCode, 30),
|
||||||
marketService.getUnusualData(stockCode, 30),
|
marketService.getUnusualData(stockCode, 30),
|
||||||
marketService.getPledgeData(stockCode),
|
marketService.getPledgeData(stockCode),
|
||||||
marketService.getRiseAnalysis(stockCode),
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// 设置概览数据
|
// 设置概览数据
|
||||||
@@ -82,8 +118,10 @@ export const useMarketData = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 设置交易数据
|
// 设置交易数据
|
||||||
|
let loadedTradeData: TradeDayData[] = [];
|
||||||
if (tradeRes.success) {
|
if (tradeRes.success) {
|
||||||
setTradeData(tradeRes.data);
|
loadedTradeData = tradeRes.data;
|
||||||
|
setTradeData(loadedTradeData);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置融资融券数据
|
// 设置融资融券数据
|
||||||
@@ -106,31 +144,18 @@ export const useMarketData = (
|
|||||||
setPledgeData(pledgeRes.data);
|
setPledgeData(pledgeRes.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置涨幅分析数据并创建映射
|
|
||||||
if (riseAnalysisRes.success) {
|
|
||||||
const tempAnalysisMap: Record<number, RiseAnalysis> = {};
|
|
||||||
|
|
||||||
if (tradeRes.success && tradeRes.data && riseAnalysisRes.data) {
|
|
||||||
riseAnalysisRes.data.forEach((analysis) => {
|
|
||||||
const dateIndex = tradeRes.data.findIndex(
|
|
||||||
(item) => item.date.substring(0, 10) === analysis.trade_date
|
|
||||||
);
|
|
||||||
if (dateIndex !== -1) {
|
|
||||||
tempAnalysisMap[dateIndex] = analysis;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
setAnalysisMap(tempAnalysisMap);
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info('useMarketData', '市场数据加载成功', { stockCode });
|
logger.info('useMarketData', '市场数据加载成功', { stockCode });
|
||||||
|
|
||||||
|
// 核心数据加载完成后,异步加载涨幅分析(不阻塞界面)
|
||||||
|
if (loadedTradeData.length > 0) {
|
||||||
|
loadRiseAnalysis(loadedTradeData);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('useMarketData', 'loadMarketData', error, { stockCode, period });
|
logger.error('useMarketData', 'loadMarketData', error, { stockCode, period });
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
}, [stockCode, period]);
|
}, [stockCode, period, loadRiseAnalysis]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 加载分钟K线数据
|
* 加载分钟K线数据
|
||||||
@@ -168,7 +193,7 @@ export const useMarketData = (
|
|||||||
}, [stockCode]);
|
}, [stockCode]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 单独刷新日K线数据(只刷新交易数据和涨幅分析)
|
* 单独刷新日K线数据(涨幅分析懒加载)
|
||||||
* 用于切换时间周期时,避免重新加载所有数据
|
* 用于切换时间周期时,避免重新加载所有数据
|
||||||
*/
|
*/
|
||||||
const refreshTradeData = useCallback(async () => {
|
const refreshTradeData = useCallback(async () => {
|
||||||
@@ -176,40 +201,31 @@ export const useMarketData = (
|
|||||||
|
|
||||||
logger.debug('useMarketData', '刷新日K线数据', { stockCode, period });
|
logger.debug('useMarketData', '刷新日K线数据', { stockCode, period });
|
||||||
setTradeLoading(true);
|
setTradeLoading(true);
|
||||||
|
setAnalysisMap({}); // 清空旧的分析数据
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 并行获取交易数据和涨幅分析
|
// 先加载交易数据
|
||||||
const [tradeRes, riseAnalysisRes] = await Promise.all([
|
const tradeRes = await marketService.getTradeData(stockCode, period);
|
||||||
marketService.getTradeData(stockCode, period),
|
|
||||||
marketService.getRiseAnalysis(stockCode),
|
|
||||||
]);
|
|
||||||
|
|
||||||
// 更新交易数据
|
// 更新交易数据
|
||||||
|
let loadedTradeData: TradeDayData[] = [];
|
||||||
if (tradeRes.success && tradeRes.data) {
|
if (tradeRes.success && tradeRes.data) {
|
||||||
setTradeData(tradeRes.data);
|
loadedTradeData = tradeRes.data;
|
||||||
|
setTradeData(loadedTradeData);
|
||||||
// 重建涨幅分析映射
|
|
||||||
if (riseAnalysisRes.success && riseAnalysisRes.data) {
|
|
||||||
const tempAnalysisMap: Record<number, RiseAnalysis> = {};
|
|
||||||
riseAnalysisRes.data.forEach((analysis) => {
|
|
||||||
const dateIndex = tradeRes.data.findIndex(
|
|
||||||
(item) => item.date.substring(0, 10) === analysis.trade_date
|
|
||||||
);
|
|
||||||
if (dateIndex !== -1) {
|
|
||||||
tempAnalysisMap[dateIndex] = analysis;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
setAnalysisMap(tempAnalysisMap);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info('useMarketData', '日K线数据刷新成功', { stockCode, period });
|
logger.info('useMarketData', '日K线数据刷新成功', { stockCode, period });
|
||||||
|
|
||||||
|
// K线数据加载完成后,异步加载涨幅分析(不阻塞界面)
|
||||||
|
if (loadedTradeData.length > 0) {
|
||||||
|
loadRiseAnalysis(loadedTradeData);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('useMarketData', 'refreshTradeData', error, { stockCode, period });
|
logger.error('useMarketData', 'refreshTradeData', error, { stockCode, period });
|
||||||
} finally {
|
} finally {
|
||||||
setTradeLoading(false);
|
setTradeLoading(false);
|
||||||
}
|
}
|
||||||
}, [stockCode, period]);
|
}, [stockCode, period, loadRiseAnalysis]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 刷新所有数据
|
* 刷新所有数据
|
||||||
@@ -253,6 +269,7 @@ export const useMarketData = (
|
|||||||
minuteData,
|
minuteData,
|
||||||
minuteLoading,
|
minuteLoading,
|
||||||
analysisMap,
|
analysisMap,
|
||||||
|
analysisLoading,
|
||||||
refetch,
|
refetch,
|
||||||
loadMinuteData,
|
loadMinuteData,
|
||||||
refreshTradeData,
|
refreshTradeData,
|
||||||
|
|||||||
@@ -366,6 +366,7 @@ export interface UseMarketDataReturn {
|
|||||||
minuteData: MinuteData | null;
|
minuteData: MinuteData | null;
|
||||||
minuteLoading: boolean;
|
minuteLoading: boolean;
|
||||||
analysisMap: Record<number, RiseAnalysis>;
|
analysisMap: Record<number, RiseAnalysis>;
|
||||||
|
analysisLoading: boolean;
|
||||||
refetch: () => Promise<void>;
|
refetch: () => Promise<void>;
|
||||||
loadMinuteData: () => Promise<void>;
|
loadMinuteData: () => Promise<void>;
|
||||||
refreshTradeData: () => Promise<void>;
|
refreshTradeData: () => Promise<void>;
|
||||||
|
|||||||
Reference in New Issue
Block a user