feat: 访问"概念中心"页面
2. 点击任意概念卡片进入概念详情
3. 点击"历史时间轴"按钮(需要Max会员权限)
4. 查看弹窗底部是否显示风险提示 & mock数据处理
This commit is contained in:
@@ -259,8 +259,8 @@ export const conceptHandlers = [
|
|||||||
// 跳过周末
|
// 跳过周末
|
||||||
if (date.getDay() !== 0 && date.getDay() !== 6) {
|
if (date.getDay() !== 0 && date.getDay() !== 6) {
|
||||||
timeseries.push({
|
timeseries.push({
|
||||||
date: date.toISOString().split('T')[0],
|
trade_date: date.toISOString().split('T')[0], // 改为 trade_date
|
||||||
avg_change_pct: (Math.random() * 8 - 2).toFixed(2),
|
avg_change_pct: parseFloat((Math.random() * 8 - 2).toFixed(2)), // 转为数值
|
||||||
stock_count: Math.floor(Math.random() * 30) + 10,
|
stock_count: Math.floor(Math.random() * 30) + 10,
|
||||||
volume: Math.floor(Math.random() * 1000000000)
|
volume: Math.floor(Math.random() * 1000000000)
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -22,7 +22,13 @@ const generateAvailableDates = () => {
|
|||||||
const year = date.getFullYear();
|
const year = date.getFullYear();
|
||||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||||
const day = String(date.getDate()).padStart(2, '0');
|
const day = String(date.getDate()).padStart(2, '0');
|
||||||
dates.push(`${year}${month}${day}`);
|
const dateStr = `${year}${month}${day}`;
|
||||||
|
|
||||||
|
// 返回包含 date 和 count 字段的对象
|
||||||
|
dates.push({
|
||||||
|
date: dateStr,
|
||||||
|
count: Math.floor(Math.random() * 80) + 30 // 30-110 只涨停股票
|
||||||
|
});
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -73,37 +79,65 @@ const generateSectors = (count = 8) => {
|
|||||||
return sectors;
|
return sectors;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 生成高位股数据
|
// 生成高位股数据(用于 HighPositionStocks 组件)
|
||||||
const generateHighPositionStocks = () => {
|
const generateHighPositionStocks = () => {
|
||||||
const stocks = [];
|
const stocks = [];
|
||||||
const stockNames = [
|
const stockNames = [
|
||||||
'宁德时代', '比亚迪', '隆基绿能', '东方财富', '中际旭创',
|
'宁德时代', '比亚迪', '隆基绿能', '东方财富', '中际旭创',
|
||||||
'京东方A', '海康威视', '立讯精密', '三一重工', '恒瑞医药'
|
'京东方A', '海康威视', '立讯精密', '三一重工', '恒瑞医药',
|
||||||
|
'三六零', '东方通信', '贵州茅台', '五粮液', '中国平安'
|
||||||
|
];
|
||||||
|
const industries = [
|
||||||
|
'锂电池', '新能源汽车', '光伏', '金融科技', '通信设备',
|
||||||
|
'显示器件', '安防设备', '电子元件', '工程机械', '医药制造',
|
||||||
|
'网络安全', '通信服务', '白酒', '食品饮料', '保险'
|
||||||
];
|
];
|
||||||
|
|
||||||
for (let i = 0; i < stockNames.length; i++) {
|
for (let i = 0; i < stockNames.length; i++) {
|
||||||
const code = `${Math.random() > 0.5 ? '6' : '0'}${String(Math.floor(Math.random() * 100000)).padStart(5, '0')}`;
|
const code = `${Math.random() > 0.5 ? '6' : '0'}${String(Math.floor(Math.random() * 100000)).padStart(5, '0')}`;
|
||||||
const continuousDays = Math.floor(Math.random() * 8) + 3;
|
const continuousDays = Math.floor(Math.random() * 8) + 2; // 2-9连板
|
||||||
|
const price = parseFloat((Math.random() * 100 + 20).toFixed(2));
|
||||||
|
const increaseRate = parseFloat((Math.random() * 3 + 8).toFixed(2)); // 8%-11%
|
||||||
|
const turnoverRate = parseFloat((Math.random() * 20 + 5).toFixed(2)); // 5%-25%
|
||||||
|
|
||||||
stocks.push({
|
stocks.push({
|
||||||
code: code,
|
stock_code: code,
|
||||||
name: stockNames[i],
|
stock_name: stockNames[i],
|
||||||
continuous_limit_days: continuousDays,
|
price: price,
|
||||||
total_gain_pct: (continuousDays * 10).toFixed(2),
|
increase_rate: increaseRate,
|
||||||
recent_limit_dates: Array.from({ length: Math.min(continuousDays, 5) }, (_, j) => {
|
continuous_limit_up: continuousDays,
|
||||||
const date = new Date();
|
industry: industries[i],
|
||||||
date.setDate(date.getDate() - j);
|
turnover_rate: turnoverRate,
|
||||||
return date.toISOString().split('T')[0].replace(/-/g, '');
|
|
||||||
}),
|
|
||||||
sector: ['人工智能', 'ChatGPT', '数字经济'][Math.floor(Math.random() * 3)],
|
|
||||||
current_price: (Math.random() * 100 + 20).toFixed(2),
|
|
||||||
market_cap: (Math.random() * 5000 + 500).toFixed(2) + '亿',
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 按连板天数降序排序
|
||||||
|
stocks.sort((a, b) => b.continuous_limit_up - a.continuous_limit_up);
|
||||||
|
|
||||||
return stocks;
|
return stocks;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 生成高位股统计数据
|
||||||
|
const generateHighPositionStatistics = (stocks) => {
|
||||||
|
if (!stocks || stocks.length === 0) {
|
||||||
|
return {
|
||||||
|
total_count: 0,
|
||||||
|
avg_continuous_days: 0,
|
||||||
|
max_continuous_days: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalCount = stocks.length;
|
||||||
|
const sumDays = stocks.reduce((sum, stock) => sum + stock.continuous_limit_up, 0);
|
||||||
|
const maxDays = Math.max(...stocks.map(s => s.continuous_limit_up));
|
||||||
|
|
||||||
|
return {
|
||||||
|
total_count: totalCount,
|
||||||
|
avg_continuous_days: parseFloat((sumDays / totalCount).toFixed(1)),
|
||||||
|
max_continuous_days: maxDays,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
// 生成词云数据
|
// 生成词云数据
|
||||||
const generateWordCloudData = () => {
|
const generateWordCloudData = () => {
|
||||||
const keywords = [
|
const keywords = [
|
||||||
@@ -124,24 +158,83 @@ const generateWordCloudData = () => {
|
|||||||
|
|
||||||
// 生成每日分析数据
|
// 生成每日分析数据
|
||||||
const generateDailyAnalysis = (date) => {
|
const generateDailyAnalysis = (date) => {
|
||||||
const sectors = generateSectors();
|
const sectorNames = [
|
||||||
const totalStocks = sectors.reduce((sum, sector) => sum + sector.stock_count, 0);
|
'公告', '人工智能', 'ChatGPT', '数字经济',
|
||||||
|
'新能源汽车', '光伏', '锂电池',
|
||||||
|
'半导体', '芯片', '5G通信',
|
||||||
|
'医疗器械', '创新药', '其他'
|
||||||
|
];
|
||||||
|
|
||||||
|
const stockNameTemplates = [
|
||||||
|
'龙头', '科技', '新能源', '智能', '数字', '云计算', '创新',
|
||||||
|
'生物', '医疗', '通信', '电子', '材料', '能源', '互联'
|
||||||
|
];
|
||||||
|
|
||||||
|
// 生成 sector_data(SectorDetails 组件需要的格式)
|
||||||
|
const sectorData = {};
|
||||||
|
let totalStocks = 0;
|
||||||
|
|
||||||
|
sectorNames.forEach((sectorName, sectorIdx) => {
|
||||||
|
const stockCount = Math.floor(Math.random() * 12) + 3; // 每个板块 3-15 只股票
|
||||||
|
const stocks = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < stockCount; i++) {
|
||||||
|
const code = `${Math.random() > 0.5 ? '6' : '0'}${String(Math.floor(Math.random() * 100000)).padStart(5, '0')}`;
|
||||||
|
const continuousDays = Math.floor(Math.random() * 6) + 1; // 1-6连板
|
||||||
|
const ztHour = Math.floor(Math.random() * 5) + 9; // 9-13点
|
||||||
|
const ztMinute = Math.floor(Math.random() * 60);
|
||||||
|
const ztSecond = Math.floor(Math.random() * 60);
|
||||||
|
const ztTime = `2024-10-28 ${String(ztHour).padStart(2, '0')}:${String(ztMinute).padStart(2, '0')}:${String(ztSecond).padStart(2, '0')}`;
|
||||||
|
|
||||||
|
const stockName = `${stockNameTemplates[i % stockNameTemplates.length]}${sectorName === '公告' ? '公告' : ''}股份${i + 1}`;
|
||||||
|
|
||||||
|
stocks.push({
|
||||||
|
scode: code,
|
||||||
|
sname: stockName,
|
||||||
|
zt_time: ztTime,
|
||||||
|
formatted_time: `${String(ztHour).padStart(2, '0')}:${String(ztMinute).padStart(2, '0')}`,
|
||||||
|
continuous_days: continuousDays === 1 ? '首板' : `${continuousDays}连板`,
|
||||||
|
brief: `${sectorName}板块异动,${stockName}因${sectorName === '公告' ? '重大公告利好' : '板块热点'}涨停。公司是${sectorName}行业龙头企业之一。`,
|
||||||
|
summary: `${sectorName}概念持续活跃`,
|
||||||
|
first_time: `2024-10-${String(28 - (continuousDays - 1)).padStart(2, '0')}`,
|
||||||
|
change_pct: parseFloat((Math.random() * 2 + 9).toFixed(2)), // 9%-11%
|
||||||
|
core_sectors: [
|
||||||
|
sectorName,
|
||||||
|
sectorNames[Math.floor(Math.random() * sectorNames.length)],
|
||||||
|
sectorNames[Math.floor(Math.random() * sectorNames.length)]
|
||||||
|
].filter((v, i, a) => a.indexOf(v) === i) // 去重
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
sectorData[sectorName] = {
|
||||||
|
count: stockCount,
|
||||||
|
stocks: stocks.sort((a, b) => a.zt_time.localeCompare(b.zt_time)) // 按涨停时间排序
|
||||||
|
};
|
||||||
|
|
||||||
|
totalStocks += stockCount;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 统计数据
|
||||||
|
const morningCount = Math.floor(totalStocks * 0.35); // 早盘涨停
|
||||||
|
const announcementCount = sectorData['公告']?.count || 0;
|
||||||
|
const topSector = sectorNames.filter(s => s !== '公告' && s !== '其他')
|
||||||
|
.reduce((max, name) =>
|
||||||
|
(sectorData[name]?.count || 0) > (sectorData[max]?.count || 0) ? name : max
|
||||||
|
, '人工智能');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
date: date,
|
date: date,
|
||||||
total_stocks: totalStocks,
|
total_stocks: totalStocks,
|
||||||
total_sectors: sectors.length,
|
total_sectors: Object.keys(sectorData).length,
|
||||||
avg_limit_time: '10:35:24',
|
sector_data: sectorData, // 👈 SectorDetails 组件需要的数据
|
||||||
market_sentiment: Math.random() > 0.5 ? '强势' : '震荡',
|
|
||||||
limit_up_ratio: (Math.random() * 3 + 1).toFixed(2) + '%',
|
|
||||||
sectors: sectors,
|
|
||||||
high_position_stocks: generateHighPositionStocks(),
|
|
||||||
summary: {
|
summary: {
|
||||||
early_limit_count: Math.floor(totalStocks * 0.3),
|
top_sector: topSector,
|
||||||
mid_limit_count: Math.floor(totalStocks * 0.5),
|
top_sector_count: sectorData[topSector]?.count || 0,
|
||||||
late_limit_count: Math.floor(totalStocks * 0.2),
|
announcement_stocks: announcementCount,
|
||||||
one_word_board: Math.floor(totalStocks * 0.15),
|
zt_time_distribution: {
|
||||||
continuous_limit: generateHighPositionStocks().length,
|
morning: morningCount,
|
||||||
|
afternoon: totalStocks - morningCount,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -225,4 +318,27 @@ export const limitAnalyseHandlers = [
|
|||||||
message: '搜索完成',
|
message: '搜索完成',
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
// 5. 获取高位股列表(涨停股票列表)
|
||||||
|
http.get('http://111.198.58.126:5001/api/limit-analyse/high-position-stocks', async ({ request }) => {
|
||||||
|
await delay(400);
|
||||||
|
|
||||||
|
const url = new URL(request.url);
|
||||||
|
const date = url.searchParams.get('date');
|
||||||
|
|
||||||
|
console.log('[Mock LimitAnalyse] 获取高位股列表:', { date });
|
||||||
|
|
||||||
|
const stocks = generateHighPositionStocks();
|
||||||
|
const statistics = generateHighPositionStatistics(stocks);
|
||||||
|
|
||||||
|
return HttpResponse.json({
|
||||||
|
success: true,
|
||||||
|
data: {
|
||||||
|
stocks: stocks,
|
||||||
|
statistics: statistics,
|
||||||
|
date: date,
|
||||||
|
},
|
||||||
|
message: '高位股数据获取成功',
|
||||||
|
});
|
||||||
|
}),
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { logger } from '../../utils/logger';
|
import { logger } from '../../utils/logger';
|
||||||
import { useConceptTimelineEvents } from './hooks/useConceptTimelineEvents';
|
import { useConceptTimelineEvents } from './hooks/useConceptTimelineEvents';
|
||||||
|
import RiskDisclaimer from '../../components/RiskDisclaimer';
|
||||||
import {
|
import {
|
||||||
Modal,
|
Modal,
|
||||||
ModalOverlay,
|
ModalOverlay,
|
||||||
@@ -825,6 +826,11 @@ const ConceptTimelineModal = ({
|
|||||||
</VStack>
|
</VStack>
|
||||||
</Center>
|
</Center>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* 风险提示 */}
|
||||||
|
<Box px={6}>
|
||||||
|
<RiskDisclaimer variant="default" />
|
||||||
|
</Box>
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
|
|
||||||
<ModalFooter borderTop="1px solid" borderColor="gray.200">
|
<ModalFooter borderTop="1px solid" borderColor="gray.200">
|
||||||
|
|||||||
Reference in New Issue
Block a user