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) {
|
||||
timeseries.push({
|
||||
date: date.toISOString().split('T')[0],
|
||||
avg_change_pct: (Math.random() * 8 - 2).toFixed(2),
|
||||
trade_date: date.toISOString().split('T')[0], // 改为 trade_date
|
||||
avg_change_pct: parseFloat((Math.random() * 8 - 2).toFixed(2)), // 转为数值
|
||||
stock_count: Math.floor(Math.random() * 30) + 10,
|
||||
volume: Math.floor(Math.random() * 1000000000)
|
||||
});
|
||||
|
||||
@@ -22,7 +22,13 @@ const generateAvailableDates = () => {
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).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++;
|
||||
}
|
||||
}
|
||||
@@ -73,37 +79,65 @@ const generateSectors = (count = 8) => {
|
||||
return sectors;
|
||||
};
|
||||
|
||||
// 生成高位股数据
|
||||
// 生成高位股数据(用于 HighPositionStocks 组件)
|
||||
const generateHighPositionStocks = () => {
|
||||
const stocks = [];
|
||||
const stockNames = [
|
||||
'宁德时代', '比亚迪', '隆基绿能', '东方财富', '中际旭创',
|
||||
'京东方A', '海康威视', '立讯精密', '三一重工', '恒瑞医药'
|
||||
'京东方A', '海康威视', '立讯精密', '三一重工', '恒瑞医药',
|
||||
'三六零', '东方通信', '贵州茅台', '五粮液', '中国平安'
|
||||
];
|
||||
const industries = [
|
||||
'锂电池', '新能源汽车', '光伏', '金融科技', '通信设备',
|
||||
'显示器件', '安防设备', '电子元件', '工程机械', '医药制造',
|
||||
'网络安全', '通信服务', '白酒', '食品饮料', '保险'
|
||||
];
|
||||
|
||||
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 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({
|
||||
code: code,
|
||||
name: stockNames[i],
|
||||
continuous_limit_days: continuousDays,
|
||||
total_gain_pct: (continuousDays * 10).toFixed(2),
|
||||
recent_limit_dates: Array.from({ length: Math.min(continuousDays, 5) }, (_, j) => {
|
||||
const date = new Date();
|
||||
date.setDate(date.getDate() - j);
|
||||
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) + '亿',
|
||||
stock_code: code,
|
||||
stock_name: stockNames[i],
|
||||
price: price,
|
||||
increase_rate: increaseRate,
|
||||
continuous_limit_up: continuousDays,
|
||||
industry: industries[i],
|
||||
turnover_rate: turnoverRate,
|
||||
});
|
||||
}
|
||||
|
||||
// 按连板天数降序排序
|
||||
stocks.sort((a, b) => b.continuous_limit_up - a.continuous_limit_up);
|
||||
|
||||
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 keywords = [
|
||||
@@ -124,24 +158,83 @@ const generateWordCloudData = () => {
|
||||
|
||||
// 生成每日分析数据
|
||||
const generateDailyAnalysis = (date) => {
|
||||
const sectors = generateSectors();
|
||||
const totalStocks = sectors.reduce((sum, sector) => sum + sector.stock_count, 0);
|
||||
const sectorNames = [
|
||||
'公告', '人工智能', '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 {
|
||||
date: date,
|
||||
total_stocks: totalStocks,
|
||||
total_sectors: sectors.length,
|
||||
avg_limit_time: '10:35:24',
|
||||
market_sentiment: Math.random() > 0.5 ? '强势' : '震荡',
|
||||
limit_up_ratio: (Math.random() * 3 + 1).toFixed(2) + '%',
|
||||
sectors: sectors,
|
||||
high_position_stocks: generateHighPositionStocks(),
|
||||
total_sectors: Object.keys(sectorData).length,
|
||||
sector_data: sectorData, // 👈 SectorDetails 组件需要的数据
|
||||
summary: {
|
||||
early_limit_count: Math.floor(totalStocks * 0.3),
|
||||
mid_limit_count: Math.floor(totalStocks * 0.5),
|
||||
late_limit_count: Math.floor(totalStocks * 0.2),
|
||||
one_word_board: Math.floor(totalStocks * 0.15),
|
||||
continuous_limit: generateHighPositionStocks().length,
|
||||
top_sector: topSector,
|
||||
top_sector_count: sectorData[topSector]?.count || 0,
|
||||
announcement_stocks: announcementCount,
|
||||
zt_time_distribution: {
|
||||
morning: morningCount,
|
||||
afternoon: totalStocks - morningCount,
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -225,4 +318,27 @@ export const limitAnalyseHandlers = [
|
||||
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 { logger } from '../../utils/logger';
|
||||
import { useConceptTimelineEvents } from './hooks/useConceptTimelineEvents';
|
||||
import RiskDisclaimer from '../../components/RiskDisclaimer';
|
||||
import {
|
||||
Modal,
|
||||
ModalOverlay,
|
||||
@@ -825,6 +826,11 @@ const ConceptTimelineModal = ({
|
||||
</VStack>
|
||||
</Center>
|
||||
)}
|
||||
|
||||
{/* 风险提示 */}
|
||||
<Box px={6}>
|
||||
<RiskDisclaimer variant="default" />
|
||||
</Box>
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter borderTop="1px solid" borderColor="gray.200">
|
||||
|
||||
Reference in New Issue
Block a user