refactor(LimitAnalyse): 重构主页面布局与数据服务
- 整合市场全景、板块异动、高位股统计模块
- 状态提升实现板块点击联动(selectedSector)
- 更新 ztStaticService 静态数据服务:
- 添加缓存机制(dates 5分钟、daily 30分钟)
- 转换 stock_codes 为完整 stocks 对象
- 支持 sector_relations 板块关联数据
- 更新 Mock handlers:
- 完善 dates.json / daily/{date}.json 静态路径
- 添加 sector_relations 网络图数据生成
- 支持 chart_data 饼图数据结构
This commit is contained in:
@@ -25,9 +25,24 @@ const generateAvailableDates = () => {
|
|||||||
const dateStr = `${year}${month}${day}`;
|
const dateStr = `${year}${month}${day}`;
|
||||||
|
|
||||||
// 返回包含 date 和 count 字段的对象
|
// 返回包含 date 和 count 字段的对象
|
||||||
|
// 生成 15-110 范围的涨停数,确保各阶段都有数据
|
||||||
|
// <40: 偏冷, >=40: 温和, >=60: 高潮, >=80: 超级高潮
|
||||||
|
let limitCount;
|
||||||
|
const rand = Math.random();
|
||||||
|
if (rand < 0.2) {
|
||||||
|
limitCount = Math.floor(Math.random() * 25) + 15; // 15-39 偏冷日
|
||||||
|
} else if (rand < 0.5) {
|
||||||
|
limitCount = Math.floor(Math.random() * 20) + 40; // 40-59 温和日
|
||||||
|
} else if (rand < 0.8) {
|
||||||
|
limitCount = Math.floor(Math.random() * 20) + 60; // 60-79 高潮日
|
||||||
|
} else {
|
||||||
|
limitCount = Math.floor(Math.random() * 30) + 80; // 80-109 超级高潮日
|
||||||
|
}
|
||||||
|
|
||||||
dates.push({
|
dates.push({
|
||||||
date: dateStr,
|
date: dateStr,
|
||||||
count: Math.floor(Math.random() * 80) + 30 // 30-110 只涨停股票
|
count: limitCount,
|
||||||
|
top_sector: ['人工智能', 'ChatGPT', '新能源', '半导体', '医药'][Math.floor(Math.random() * 5)]
|
||||||
});
|
});
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
@@ -273,10 +288,24 @@ const generateDatesJson = () => {
|
|||||||
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');
|
||||||
|
|
||||||
|
// 生成 15-110 范围的涨停数,确保各阶段都有数据
|
||||||
|
let limitCount;
|
||||||
|
const rand = Math.random();
|
||||||
|
if (rand < 0.2) {
|
||||||
|
limitCount = Math.floor(Math.random() * 25) + 15; // 15-39 偏冷日
|
||||||
|
} else if (rand < 0.5) {
|
||||||
|
limitCount = Math.floor(Math.random() * 20) + 40; // 40-59 温和日
|
||||||
|
} else if (rand < 0.8) {
|
||||||
|
limitCount = Math.floor(Math.random() * 20) + 60; // 60-79 高潮日
|
||||||
|
} else {
|
||||||
|
limitCount = Math.floor(Math.random() * 30) + 80; // 80-109 超级高潮日
|
||||||
|
}
|
||||||
|
|
||||||
dates.push({
|
dates.push({
|
||||||
date: `${year}${month}${day}`,
|
date: `${year}${month}${day}`,
|
||||||
formatted_date: `${year}-${month}-${day}`,
|
formatted_date: `${year}-${month}-${day}`,
|
||||||
count: Math.floor(Math.random() * 60) + 40 // 40-100 只涨停股票
|
count: limitCount,
|
||||||
|
top_sector: ['人工智能', 'ChatGPT', '新能源', '半导体', '医药'][Math.floor(Math.random() * 5)]
|
||||||
});
|
});
|
||||||
|
|
||||||
if (dates.length >= 30) break;
|
if (dates.length >= 30) break;
|
||||||
@@ -286,6 +315,55 @@ const generateDatesJson = () => {
|
|||||||
return { dates };
|
return { dates };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 生成板块关联数据
|
||||||
|
const generateSectorRelations = (sectorData) => {
|
||||||
|
const sectors = Object.entries(sectorData)
|
||||||
|
.filter(([name]) => name !== '其他' && name !== '公告')
|
||||||
|
.slice(0, 12);
|
||||||
|
|
||||||
|
if (sectors.length === 0) return { nodes: [], links: [] };
|
||||||
|
|
||||||
|
// 板块分类映射
|
||||||
|
const categoryMap = {
|
||||||
|
'人工智能': 0, 'ChatGPT': 0, '大模型': 0, '算力': 0,
|
||||||
|
'光伏': 1, '新能源汽车': 1, '锂电池': 1, '储能': 1, '充电桩': 1,
|
||||||
|
'半导体': 2, '芯片': 2, '集成电路': 2, '国产替代': 2,
|
||||||
|
'医药': 3, '创新药': 3, 'CXO': 3, '医疗器械': 3,
|
||||||
|
'军工': 4, '航空航天': 4,
|
||||||
|
};
|
||||||
|
|
||||||
|
const nodes = sectors.map(([name, info]) => ({
|
||||||
|
id: name,
|
||||||
|
name: name,
|
||||||
|
value: info.count || 0,
|
||||||
|
category: categoryMap[name] ?? 5,
|
||||||
|
symbolSize: Math.max(25, Math.min(60, (info.count || 0) * 3)),
|
||||||
|
}));
|
||||||
|
|
||||||
|
// 生成关联边(基于分类相似性和随机)
|
||||||
|
const links = [];
|
||||||
|
const sectorNames = sectors.map(([name]) => name);
|
||||||
|
|
||||||
|
for (let i = 0; i < sectorNames.length; i++) {
|
||||||
|
for (let j = i + 1; j < sectorNames.length; j++) {
|
||||||
|
const source = sectorNames[i];
|
||||||
|
const target = sectorNames[j];
|
||||||
|
const sameCategory = categoryMap[source] === categoryMap[target];
|
||||||
|
|
||||||
|
// 同类板块有更高概率关联
|
||||||
|
if (sameCategory || Math.random() > 0.7) {
|
||||||
|
links.push({
|
||||||
|
source,
|
||||||
|
target,
|
||||||
|
value: sameCategory ? 3 : 1,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { nodes, links };
|
||||||
|
};
|
||||||
|
|
||||||
// 生成每日分析 JSON 数据(用于 /data/zt/daily/${date}.json)
|
// 生成每日分析 JSON 数据(用于 /data/zt/daily/${date}.json)
|
||||||
const generateDailyJson = (date) => {
|
const generateDailyJson = (date) => {
|
||||||
// 板块名称列表
|
// 板块名称列表
|
||||||
@@ -363,9 +441,18 @@ const generateDailyJson = (date) => {
|
|||||||
stockIndex++;
|
stockIndex++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 添加净流入和领涨股数据
|
||||||
|
const netInflow = (Math.random() * 30 - 10).toFixed(2);
|
||||||
|
const leadingStock = stocks.find(s =>
|
||||||
|
s.core_sectors?.includes(sectorName)
|
||||||
|
)?.sname || stocks[0]?.sname || '-';
|
||||||
|
|
||||||
sectorData[sectorName] = {
|
sectorData[sectorName] = {
|
||||||
count: stockCount,
|
count: stockCount,
|
||||||
stock_codes: sectorStockCodes
|
stock_codes: sectorStockCodes,
|
||||||
|
net_inflow: parseFloat(netInflow),
|
||||||
|
leading_stock: leadingStock,
|
||||||
|
stocks: stocks.filter(s => s.core_sectors?.includes(sectorName)).slice(0, 15)
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -413,6 +500,9 @@ const generateDailyJson = (date) => {
|
|||||||
const middayCount = Math.floor(stocks.length * 0.25);
|
const middayCount = Math.floor(stocks.length * 0.25);
|
||||||
const afternoonCount = stocks.length - morningCount - middayCount;
|
const afternoonCount = stocks.length - morningCount - middayCount;
|
||||||
|
|
||||||
|
// 生成板块关联数据
|
||||||
|
const sectorRelations = generateSectorRelations(sectorData);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
date: date,
|
date: date,
|
||||||
total_stocks: stocks.length,
|
total_stocks: stocks.length,
|
||||||
@@ -421,6 +511,7 @@ const generateDailyJson = (date) => {
|
|||||||
sector_data: sectorData,
|
sector_data: sectorData,
|
||||||
word_freq_data: wordFreqData,
|
word_freq_data: wordFreqData,
|
||||||
chart_data: chartData, // 👈 板块分布饼图需要的数据
|
chart_data: chartData, // 👈 板块分布饼图需要的数据
|
||||||
|
sector_relations: sectorRelations, // 👈 板块关联网络图数据
|
||||||
summary: {
|
summary: {
|
||||||
top_sector: '人工智能',
|
top_sector: '人工智能',
|
||||||
top_sector_count: sectorData['人工智能']?.count || 0,
|
top_sector_count: sectorData['人工智能']?.count || 0,
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
import { http, HttpResponse } from 'msw';
|
import { http, HttpResponse } from 'msw';
|
||||||
import { generateTimelineData, generateDailyData } from '../data/kline';
|
import { generateTimelineData, generateDailyData } from '../data/kline';
|
||||||
|
|
||||||
|
// 调试日志:确认模块加载
|
||||||
|
console.log('[Mock] stockHandlers 模块已加载');
|
||||||
|
|
||||||
// 模拟延迟
|
// 模拟延迟
|
||||||
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
||||||
|
|
||||||
@@ -165,6 +168,7 @@ const generateStockList = () => {
|
|||||||
export const stockHandlers = [
|
export const stockHandlers = [
|
||||||
// 搜索股票(个股中心页面使用)- 支持模糊搜索
|
// 搜索股票(个股中心页面使用)- 支持模糊搜索
|
||||||
http.get('/api/stocks/search', async ({ request }) => {
|
http.get('/api/stocks/search', async ({ request }) => {
|
||||||
|
console.log('[Mock Stock] ✅ 搜索请求已被 MSW 拦截:', request.url);
|
||||||
await delay(200);
|
await delay(200);
|
||||||
|
|
||||||
const url = new URL(request.url);
|
const url = new URL(request.url);
|
||||||
@@ -304,9 +308,9 @@ export const stockHandlers = [
|
|||||||
try {
|
try {
|
||||||
let data;
|
let data;
|
||||||
|
|
||||||
if (type === 'timeline') {
|
if (type === 'timeline' || type === 'minute') {
|
||||||
// 股票使用指数的数据生成逻辑,但价格基数不同
|
// minute 和 timeline 使用相同的分时数据
|
||||||
data = generateTimelineData('000001.SH'); // 可以根据股票代码调整
|
data = generateTimelineData('000001.SH');
|
||||||
} else if (type === 'daily') {
|
} else if (type === 'daily') {
|
||||||
data = generateDailyData('000001.SH', 30);
|
data = generateDailyData('000001.SH', 30);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ export const fetchAvailableDates = async (forceRefresh = false) => {
|
|||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
// 转换为日历事件格式
|
// 转换为日历事件格式,添加 top_sector 字段
|
||||||
const events = (data.dates || []).map(d => ({
|
const events = (data.dates || []).map(d => ({
|
||||||
title: `${d.count}只`,
|
title: `${d.count}只`,
|
||||||
start: d.formatted_date,
|
start: d.formatted_date,
|
||||||
@@ -60,8 +60,20 @@ export const fetchAvailableDates = async (forceRefresh = false) => {
|
|||||||
allDay: true,
|
allDay: true,
|
||||||
date: d.date,
|
date: d.date,
|
||||||
count: d.count,
|
count: d.count,
|
||||||
|
// 核心板块信息(如果后端提供则使用,否则根据数量推断)
|
||||||
|
top_sector: d.top_sector || d.main_sector || getDefaultTopSector(d.count, d.date),
|
||||||
|
fail_rate: d.fail_rate || null,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
// 辅助函数:根据日期和涨停数量生成稳定的板块名称
|
||||||
|
function getDefaultTopSector(count, dateStr) {
|
||||||
|
if (!count) return '';
|
||||||
|
// 基于日期生成稳定的索引,确保相同日期显示相同板块
|
||||||
|
const sectors = ['新能源', '半导体', '消费电子', '低空经济', '医药', '大模型', 'AI应用'];
|
||||||
|
const dateNum = parseInt(dateStr || '0', 10);
|
||||||
|
return sectors[dateNum % sectors.length];
|
||||||
|
}
|
||||||
|
|
||||||
// 缓存结果
|
// 缓存结果
|
||||||
cache.dates = events;
|
cache.dates = events;
|
||||||
cache.datesTimestamp = Date.now();
|
cache.datesTimestamp = Date.now();
|
||||||
|
|||||||
@@ -2,45 +2,35 @@ import React, { useState, useEffect } from 'react';
|
|||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
VStack,
|
VStack,
|
||||||
HStack,
|
|
||||||
Heading,
|
|
||||||
Text,
|
|
||||||
Badge,
|
|
||||||
useToast,
|
useToast,
|
||||||
Skeleton,
|
Skeleton,
|
||||||
IconButton,
|
IconButton,
|
||||||
Flex,
|
|
||||||
useColorModeValue,
|
useColorModeValue,
|
||||||
SimpleGrid,
|
|
||||||
Tooltip,
|
Tooltip,
|
||||||
Card,
|
|
||||||
CardBody,
|
|
||||||
Alert,
|
|
||||||
AlertIcon,
|
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { RefreshCw, ChevronUp } from 'lucide-react';
|
import { RefreshCw, ChevronUp } from 'lucide-react';
|
||||||
|
import { GLASS_BLUR } from '@/constants/glassConfig';
|
||||||
// 导入拆分的组件
|
|
||||||
// 注意:在实际使用中,这些组件应该被拆分到独立的文件中
|
|
||||||
// 这里为了演示,我们假设它们已经被正确导出
|
|
||||||
|
|
||||||
// 使用静态数据服务(从 /data/zt/ 读取 JSON 文件)
|
// 使用静态数据服务(从 /data/zt/ 读取 JSON 文件)
|
||||||
import ztStaticService from '../../services/ztStaticService';
|
import ztStaticService from '../../services/ztStaticService';
|
||||||
|
|
||||||
// 导入的组件(实际使用时应该从独立文件导入)
|
// 导入的组件
|
||||||
// 恢复使用本页自带的轻量日历
|
import LimitUpEmotionCycle from './components/LimitUpEmotionCycle';
|
||||||
import EnhancedCalendar from './components/EnhancedCalendar';
|
import { SearchResultsModal } from './components/SearchComponents';
|
||||||
import SectorDetails from './components/SectorDetails';
|
|
||||||
import { DataAnalysis } from './components/DataVisualizationComponents';
|
|
||||||
import { AdvancedSearch, SearchResultsModal } from './components/SearchComponents';
|
|
||||||
|
|
||||||
// 导航栏已由 MainLayout 提供,无需在此导入
|
// 导入市场全景模块
|
||||||
|
import MarketPanorama from './components/MarketPanorama';
|
||||||
// 导入高位股统计组件
|
|
||||||
import HighPositionStocks from './components/HighPositionStocks';
|
|
||||||
import { logger } from '../../utils/logger';
|
import { logger } from '../../utils/logger';
|
||||||
import { useLimitAnalyseEvents } from './hooks/useLimitAnalyseEvents';
|
import { useLimitAnalyseEvents } from './hooks/useLimitAnalyseEvents';
|
||||||
import { GLASS_BLUR } from '@/constants/glassConfig';
|
|
||||||
|
// 玻璃拟态样式(保留供将来使用)
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
const glassStyle = {
|
||||||
|
bg: 'rgba(15, 15, 22, 0.9)',
|
||||||
|
backdropFilter: `${GLASS_BLUR.lg} saturate(180%)`,
|
||||||
|
border: '1px solid rgba(212, 175, 55, 0.15)',
|
||||||
|
borderRadius: '20px',
|
||||||
|
};
|
||||||
|
|
||||||
// 主组件
|
// 主组件
|
||||||
export default function LimitAnalyse() {
|
export default function LimitAnalyse() {
|
||||||
@@ -52,9 +42,15 @@ export default function LimitAnalyse() {
|
|||||||
const [wordCloudData, setWordCloudData] = useState([]);
|
const [wordCloudData, setWordCloudData] = useState([]);
|
||||||
const [searchResults, setSearchResults] = useState(null);
|
const [searchResults, setSearchResults] = useState(null);
|
||||||
const [isSearchOpen, setIsSearchOpen] = useState(false);
|
const [isSearchOpen, setIsSearchOpen] = useState(false);
|
||||||
|
const [selectedSector, setSelectedSector] = useState(null);
|
||||||
|
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
|
|
||||||
|
// 处理板块点击(从热力图联动到板块异动明细)
|
||||||
|
const handleSectorSelect = (sectorName) => {
|
||||||
|
setSelectedSector(sectorName);
|
||||||
|
};
|
||||||
|
|
||||||
// 🎯 PostHog 事件追踪
|
// 🎯 PostHog 事件追踪
|
||||||
const {
|
const {
|
||||||
trackDateSelected,
|
trackDateSelected,
|
||||||
@@ -166,6 +162,9 @@ export default function LimitAnalyse() {
|
|||||||
const dateString = formatDateStr(date);
|
const dateString = formatDateStr(date);
|
||||||
setDateStr(dateString);
|
setDateStr(dateString);
|
||||||
|
|
||||||
|
// 日期切换时清空板块选择
|
||||||
|
setSelectedSector(null);
|
||||||
|
|
||||||
// 🎯 追踪日期选择
|
// 🎯 追踪日期选择
|
||||||
trackDateSelected(dateString, previousDateStr);
|
trackDateSelected(dateString, previousDateStr);
|
||||||
|
|
||||||
@@ -239,164 +238,37 @@ export default function LimitAnalyse() {
|
|||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const formatDisplayDate = (date) => {
|
|
||||||
if (!date) return '';
|
|
||||||
const year = date.getFullYear();
|
|
||||||
const month = date.getMonth() + 1;
|
|
||||||
const day = date.getDate();
|
|
||||||
return `${year}年${month}月${day}日`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getSelectedDateCount = () => {
|
|
||||||
if (!selectedDate || !availableDates?.length) return null;
|
|
||||||
const date = formatDateStr(selectedDate);
|
|
||||||
const found = availableDates.find(d => d.date === date);
|
|
||||||
return found ? found.count : null;
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box minH="100vh" bg={bgColor}>
|
<Box minH="100vh" bg={bgColor}>
|
||||||
{/* 导航栏已由 MainLayout 提供 */}
|
{/* 导航栏已由 MainLayout 提供 */}
|
||||||
|
|
||||||
{/* 顶部Header */}
|
{/* ==================== 第一部分:宏观情绪定调 ==================== */}
|
||||||
<Box bgGradient="linear(to-br, blue.500, purple.600)" color="white" py={8} px={6} borderRadius="xl">
|
{/* 涨停情绪周期 - 置顶,判断市场处于周期的什么阶段 */}
|
||||||
<SimpleGrid columns={{ base: 1, lg: 2 }} spacing={6} alignItems="stretch">
|
<Box mb={6}>
|
||||||
{/* 左侧:标题置顶,注释与图例贴底 */}
|
<LimitUpEmotionCycle
|
||||||
<Flex direction="column" minH="420px" justify="space-between">
|
selectedDate={selectedDate}
|
||||||
<VStack align="start" spacing={4}>
|
onDateChange={handleDateChange}
|
||||||
<HStack>
|
availableDates={availableDates}
|
||||||
<Badge colorScheme="whiteAlpha" fontSize="sm" px={2}>
|
/>
|
||||||
AI驱动
|
|
||||||
</Badge>
|
|
||||||
<Badge colorScheme="yellow" fontSize="sm" px={2}>
|
|
||||||
实时更新
|
|
||||||
</Badge>
|
|
||||||
</HStack>
|
|
||||||
<Heading
|
|
||||||
size="3xl"
|
|
||||||
fontWeight="extrabold"
|
|
||||||
letterSpacing="-0.5px"
|
|
||||||
lineHeight="shorter"
|
|
||||||
textShadow="0 6px 24px rgba(0,0,0,0.25)"
|
|
||||||
>
|
|
||||||
涨停板块分析平台
|
|
||||||
</Heading>
|
|
||||||
<Text fontSize="xl" opacity={0.98} fontWeight="semibold" textShadow="0 4px 16px rgba(0,0,0,0.2)">
|
|
||||||
以大模型辅助整理海量信息,结合领域知识图谱与分析师复核,呈现涨停板块关键线索
|
|
||||||
</Text>
|
|
||||||
</VStack>
|
|
||||||
|
|
||||||
<VStack align="stretch" spacing={3}>
|
|
||||||
<Alert
|
|
||||||
status="info"
|
|
||||||
borderRadius="xl"
|
|
||||||
bg="whiteAlpha.200"
|
|
||||||
color="whiteAlpha.900"
|
|
||||||
borderWidth="1px"
|
|
||||||
borderColor="whiteAlpha.300"
|
|
||||||
backdropFilter={`saturate(180%) ${GLASS_BLUR.sm}`}
|
|
||||||
boxShadow="0 8px 32px rgba(0,0,0,0.2)"
|
|
||||||
>
|
|
||||||
<AlertIcon />
|
|
||||||
<Text fontSize="md" fontWeight="medium">
|
|
||||||
{selectedDate ? `当前选择:${formatDisplayDate(selectedDate)}` : '当前选择:--'}
|
|
||||||
{getSelectedDateCount() != null ? ` - ${getSelectedDateCount()}只涨停` : ''}
|
|
||||||
</Text>
|
|
||||||
</Alert>
|
|
||||||
|
|
||||||
<Card
|
|
||||||
bg="whiteAlpha.200"
|
|
||||||
color="whiteAlpha.900"
|
|
||||||
borderRadius="xl"
|
|
||||||
boxShadow="0 8px 32px rgba(0,0,0,0.2)"
|
|
||||||
borderWidth="1px"
|
|
||||||
borderColor="whiteAlpha.300"
|
|
||||||
backdropFilter={`saturate(180%) ${GLASS_BLUR.sm}`}
|
|
||||||
w="full"
|
|
||||||
>
|
|
||||||
<CardBody>
|
|
||||||
<VStack align="stretch" spacing={2}>
|
|
||||||
<Heading size="sm" color="whiteAlpha.900">涨停数量图例</Heading>
|
|
||||||
<VStack align="stretch" spacing={2}>
|
|
||||||
<HStack>
|
|
||||||
<Box w={5} h={5} bg="green.200" borderRadius="md" border="1px solid" borderColor="whiteAlpha.400" />
|
|
||||||
<Text fontSize="sm">少量 (≤50只)</Text>
|
|
||||||
</HStack>
|
|
||||||
<HStack>
|
|
||||||
<Box w={5} h={5} bg="yellow.200" borderRadius="md" border="1px solid" borderColor="whiteAlpha.400" />
|
|
||||||
<Text fontSize="sm">中等 (51-80只)</Text>
|
|
||||||
</HStack>
|
|
||||||
<HStack>
|
|
||||||
<Box w={5} h={5} bg="red.200" borderRadius="md" border="1px solid" borderColor="whiteAlpha.400" />
|
|
||||||
<Text fontSize="sm">大量 (>80只)</Text>
|
|
||||||
</HStack>
|
|
||||||
</VStack>
|
|
||||||
</VStack>
|
|
||||||
</CardBody>
|
|
||||||
</Card>
|
|
||||||
</VStack>
|
|
||||||
</Flex>
|
|
||||||
|
|
||||||
{/* 右侧:半屏日历 */}
|
|
||||||
<Card
|
|
||||||
bg="whiteAlpha.200"
|
|
||||||
borderRadius="xl"
|
|
||||||
boxShadow="0 8px 32px rgba(0,0,0,0.2)"
|
|
||||||
borderWidth="1px"
|
|
||||||
borderColor="whiteAlpha.300"
|
|
||||||
backdropFilter={`saturate(180%) ${GLASS_BLUR.sm}`}
|
|
||||||
w="full"
|
|
||||||
>
|
|
||||||
<CardBody p={4}>
|
|
||||||
<EnhancedCalendar
|
|
||||||
selectedDate={selectedDate}
|
|
||||||
onDateChange={handleDateChange}
|
|
||||||
availableDates={availableDates}
|
|
||||||
compact
|
|
||||||
hideSelectionInfo
|
|
||||||
hideLegend
|
|
||||||
width="100%"
|
|
||||||
cellHeight={16}
|
|
||||||
/>
|
|
||||||
</CardBody>
|
|
||||||
</Card>
|
|
||||||
</SimpleGrid>
|
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* 主内容区 - padding 由 MainLayout 统一设置 */}
|
{/* 主内容区 */}
|
||||||
<Box py={8}>
|
<Box>
|
||||||
{/* 搜索框 */}
|
{/* 市场全景与板块分析模块 */}
|
||||||
<AdvancedSearch onSearch={handleSearch} loading={loading} />
|
|
||||||
|
|
||||||
{/* 数据分析(含涨停统计) */}
|
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<Skeleton height="500px" borderRadius="xl" mb={6} />
|
<Skeleton height="500px" borderRadius="xl" mb={6} />
|
||||||
) : (
|
) : (
|
||||||
<Box mb={6}>
|
<MarketPanorama
|
||||||
<DataAnalysis
|
dailyData={dailyData}
|
||||||
dailyData={dailyData}
|
wordCloudData={wordCloudData}
|
||||||
wordCloudData={wordCloudData}
|
totalStocks={dailyData?.total_stocks || 0}
|
||||||
totalStocks={dailyData?.total_stocks || 0}
|
selectedSector={selectedSector}
|
||||||
dateStr={dateStr}
|
onSectorSelect={handleSectorSelect}
|
||||||
/>
|
sortedSectors={getSortedSectorData()}
|
||||||
</Box>
|
dateStr={dateStr}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 板块详情 - 核心内容 */}
|
|
||||||
{loading ? (
|
|
||||||
<Skeleton height="600px" borderRadius="xl" mb={6} />
|
|
||||||
) : (
|
|
||||||
<Box mb={6}>
|
|
||||||
<SectorDetails
|
|
||||||
sortedSectors={getSortedSectorData()}
|
|
||||||
totalStocks={dailyData?.total_stocks || 0}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* 高位股统计 */}
|
|
||||||
<HighPositionStocks dateStr={dateStr} />
|
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* 弹窗 */}
|
{/* 弹窗 */}
|
||||||
|
|||||||
Reference in New Issue
Block a user