update pay function

This commit is contained in:
2025-11-27 11:28:57 +08:00
parent 62cf0a6c7d
commit 900aff17df
5 changed files with 1469 additions and 121 deletions

View File

@@ -93,16 +93,60 @@ const InvestmentCalendar = () => {
return code.split('.')[0];
};
/**
* 归一化股票数据格式
* 支持两种格式:
* 1. 旧格式数组:[code, name, description, score]
* 2. 新格式对象:{ code, name, description, score, report }
* 返回统一的对象格式
*/
const normalizeStock = (stock) => {
if (!stock) return null;
// 新格式:对象
if (typeof stock === 'object' && !Array.isArray(stock)) {
return {
code: stock.code || '',
name: stock.name || '',
description: stock.description || '',
score: stock.score || 0,
report: stock.report || null // 研报引用信息
};
}
// 旧格式:数组 [code, name, description, score]
if (Array.isArray(stock)) {
return {
code: stock[0] || '',
name: stock[1] || '',
description: stock[2] || '',
score: stock[3] || 0,
report: null
};
}
return null;
};
/**
* 归一化股票列表
*/
const normalizeStocks = (stocks) => {
if (!stocks || !Array.isArray(stocks)) return [];
return stocks.map(normalizeStock).filter(Boolean);
};
// 加载股票行情
const loadStockQuotes = async (stocks, eventTime) => {
try {
const codes = stocks.map(stock => getSixDigitCode(stock[0])); // 确保使用六位代码
const normalizedStocks = normalizeStocks(stocks);
const codes = normalizedStocks.map(stock => getSixDigitCode(stock.code));
const quotes = {};
// 使用市场API获取最新行情数据
for (let i = 0; i < codes.length; i++) {
const code = codes[i];
const originalCode = stocks[i][0]; // 保持原始代码作为key
const originalCode = normalizedStocks[i].code; // 使用归一化后的代码作为key
try {
const response = await fetch(`/api/market/trade/${code}?days=1`);
if (response.ok) {
@@ -257,11 +301,13 @@ const InvestmentCalendar = () => {
message.info('暂无相关股票');
return;
}
// 按相关度排序(限降序)
const sortedStocks = [...stocks].sort((a, b) => (b[3] || 0) - (a[3] || 0));
// 归一化数据后按相关度排序(降序)
const normalizedList = normalizeStocks(stocks);
const sortedStocks = normalizedList.sort((a, b) => (b.score || 0) - (a.score || 0));
setSelectedStocks(sortedStocks);
setStockModalVisible(true);
loadStockQuotes(sortedStocks, eventTime);
loadStockQuotes(stocks, eventTime); // 传原始数据给 loadStockQuotes它内部会归一化
};
// 添加交易所后缀
@@ -281,24 +327,27 @@ const InvestmentCalendar = () => {
return sixDigitCode;
};
// 显示K线图
// 显示K线图(支持新旧格式)
const showKline = (stock) => {
const stockCode = addExchangeSuffix(stock[0]);
// 兼容新旧格式
const code = stock.code || stock[0];
const name = stock.name || stock[1];
const stockCode = addExchangeSuffix(code);
// 将 selectedDate 转换为 YYYY-MM-DD 格式日K线只需要日期不需要时间
const formattedEventTime = selectedDate ? selectedDate.format('YYYY-MM-DD') : null;
console.log('[InvestmentCalendar] 打开K线图:', {
originalCode: stock[0],
originalCode: code,
processedCode: stockCode,
stockName: stock[1],
stockName: name,
selectedDate: selectedDate?.format('YYYY-MM-DD'),
formattedEventTime: formattedEventTime
});
setSelectedStock({
stock_code: stockCode, // 添加交易所后缀
stock_name: stock[1]
stock_name: name
});
setSelectedEventTime(formattedEventTime);
setKlineModalVisible(true);
@@ -330,10 +379,13 @@ const InvestmentCalendar = () => {
}
};
// 添加单只股票到自选
// 添加单只股票到自选(支持新旧格式)
const addSingleToWatchlist = async (stock) => {
const stockCode = getSixDigitCode(stock[0]);
// 兼容新旧格式
const code = stock.code || stock[0];
const name = stock.name || stock[1];
const stockCode = getSixDigitCode(code);
setAddingToWatchlist(prev => ({ ...prev, [stockCode]: true }));
try {
@@ -345,20 +397,20 @@ const InvestmentCalendar = () => {
credentials: 'include',
body: JSON.stringify({
stock_code: stockCode, // 使用六位代码
stock_name: stock[1] // 股票名称
stock_name: name // 股票名称
})
});
const data = await response.json();
if (data.success) {
message.success(`已将 ${stock[1]}(${stockCode}) 添加到自选`);
message.success(`已将 ${name}(${stockCode}) 添加到自选`);
} else {
message.error(data.error || '添加失败');
}
} catch (error) {
logger.error('InvestmentCalendar', 'addSingleToWatchlist', error, {
stockCode,
stockName: stock[1]
stockName: name
});
message.error('添加失败,请重试');
} finally {
@@ -415,7 +467,23 @@ const InvestmentCalendar = () => {
</Button>
)
},
{
title: '未来推演',
dataIndex: 'forecast',
key: 'forecast',
width: 80,
render: (text) => (
<Button
type="link"
size="small"
icon={<RobotOutlined />}
onClick={() => showContentDetail(text, '未来推演')}
disabled={!text}
>
{text ? '查看' : '无'}
</Button>
)
},
{
title: (
<span>
@@ -484,17 +552,17 @@ const InvestmentCalendar = () => {
}
];
// 股票表格列定义
// 股票表格列定义(使用归一化后的对象格式)
const stockColumns = [
{
title: '代码',
dataIndex: '0',
dataIndex: 'code',
key: 'code',
width: 100,
render: (code) => {
const sixDigitCode = getSixDigitCode(code);
return (
<a
<a
href={`https://valuefrontier.cn/company?scode=${sixDigitCode}`}
target="_blank"
rel="noopener noreferrer"
@@ -506,13 +574,13 @@ const InvestmentCalendar = () => {
},
{
title: '名称',
dataIndex: '1',
dataIndex: 'name',
key: 'name',
width: 100,
render: (name, record) => {
const sixDigitCode = getSixDigitCode(record[0]);
const sixDigitCode = getSixDigitCode(record.code);
return (
<a
<a
href={`https://valuefrontier.cn/company?scode=${sixDigitCode}`}
target="_blank"
rel="noopener noreferrer"
@@ -527,7 +595,7 @@ const InvestmentCalendar = () => {
key: 'price',
width: 80,
render: (_, record) => {
const quote = stockQuotes[record[0]];
const quote = stockQuotes[record.code];
if (quote && quote.price !== undefined) {
return (
<Text type={quote.change > 0 ? 'danger' : 'success'}>
@@ -543,7 +611,7 @@ const InvestmentCalendar = () => {
key: 'change',
width: 100,
render: (_, record) => {
const quote = stockQuotes[record[0]];
const quote = stockQuotes[record.code];
if (quote && quote.changePercent !== undefined) {
const changePercent = quote.changePercent || 0;
return (
@@ -557,11 +625,12 @@ const InvestmentCalendar = () => {
},
{
title: '关联理由',
dataIndex: '2',
dataIndex: 'description',
key: 'reason',
render: (reason, record) => {
const stockCode = record[0];
render: (description, record) => {
const stockCode = record.code;
const isExpanded = expandedReasons[stockCode] || false;
const reason = description || '';
const shouldTruncate = reason && reason.length > 100;
const toggleExpanded = () => {
@@ -571,8 +640,8 @@ const InvestmentCalendar = () => {
}));
};
// 检查是否有引用数据reason 就是 record[2]
const citationData = reason;
// 检查是否有引用数据
const citationData = description;
const hasCitation = citationData && citationData.data && Array.isArray(citationData.data);
if (hasCitation) {
@@ -582,11 +651,11 @@ const InvestmentCalendar = () => {
if (processed) {
// 计算所有段落的总长度
const totalLength = processed.segments.reduce((sum, seg) => sum + seg.text.length, 0);
const shouldTruncate = totalLength > 100;
const shouldTruncateProcessed = totalLength > 100;
// 确定要显示的段落
let displaySegments = processed.segments;
if (shouldTruncate && !isExpanded) {
if (shouldTruncateProcessed && !isExpanded) {
// 需要截断:计算应该显示到哪个段落
let charCount = 0;
displaySegments = [];
@@ -621,7 +690,7 @@ const InvestmentCalendar = () => {
</React.Fragment>
))}
</div>
{shouldTruncate && (
{shouldTruncateProcessed && (
<Button
type="link"
size="small"
@@ -665,7 +734,44 @@ const InvestmentCalendar = () => {
);
}
},
{
title: '研报引用',
dataIndex: 'report',
key: 'report',
width: 200,
render: (report, record) => {
if (!report || !report.title) {
return <Text type="secondary">-</Text>;
}
return (
<div style={{ fontSize: '12px' }}>
<Tooltip title={report.sentences || report.title}>
<div>
<Text strong style={{ display: 'block', marginBottom: 2 }}>
{report.title.length > 20 ? `${report.title.slice(0, 20)}...` : report.title}
</Text>
{report.author && (
<Text type="secondary" style={{ display: 'block', fontSize: '11px' }}>
{report.author}
</Text>
)}
{report.declare_date && (
<Text type="secondary" style={{ fontSize: '11px' }}>
{dayjs(report.declare_date).format('YYYY-MM-DD')}
</Text>
)}
{report.match_score && (
<Tag color={report.match_score === '好' ? 'green' : 'blue'} style={{ marginLeft: 4, fontSize: '10px' }}>
匹配度: {report.match_score}
</Tag>
)}
</div>
</Tooltip>
</div>
);
}
},
{
title: 'K线图',
key: 'kline',
@@ -685,9 +791,9 @@ const InvestmentCalendar = () => {
key: 'action',
width: 100,
render: (_, record) => {
const stockCode = getSixDigitCode(record[0]);
const stockCode = getSixDigitCode(record.code);
const isAdding = addingToWatchlist[stockCode] || false;
return (
<Button
type="default"